Compare commits
107
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3296fd06e2 | ||
|
|
9102630227 | ||
|
|
e72bf3e86f | ||
|
|
4a97f065af | ||
|
|
0bc279c387 | ||
|
|
6ce0707aae | ||
|
|
75ed0cd57a | ||
|
|
1f885014b8 | ||
|
|
b7fc4fc7af | ||
|
|
56287f697a | ||
|
|
32dac53ce1 | ||
|
|
8e5b1bc07e | ||
|
|
332a57d9d6 | ||
|
|
50e1563078 | ||
|
|
4775fa6079 | ||
|
|
fb4516e3e9 | ||
|
|
a532de5526 | ||
|
|
acca53b4f8 | ||
|
|
7c172d99a4 | ||
|
|
86e975ba58 | ||
|
|
1a3a7b7b10 | ||
|
|
ca21615c13 | ||
|
|
cf2f1493b9 | ||
|
|
b94efdd07d | ||
|
|
e332cc54d6 | ||
|
|
90c02c309d | ||
|
|
63a052f4b4 | ||
|
|
c1b7dd251a | ||
|
|
0dda5483b5 | ||
|
|
4ed2573179 | ||
|
|
a6a9dcca29 | ||
|
|
d8c9ee5c5a | ||
|
|
ff3709c618 | ||
|
|
c58736a68b | ||
|
|
31a6a29a6f | ||
|
|
defd4de2cf | ||
|
|
ecd3f084d6 | ||
|
|
d257946b18 | ||
|
|
bce1c3aaa1 | ||
|
|
0130f7a20b | ||
|
|
6d65d9e840 | ||
|
|
c157c94c14 | ||
|
|
4d923768fa | ||
|
|
7a1e66396d | ||
|
|
f9754140ba | ||
|
|
2ea583eafd | ||
|
|
88a4f90569 | ||
|
|
c7fee6b08a | ||
|
|
83c29033f2 | ||
|
|
153c7ce5b1 | ||
|
|
03ed5c8f00 | ||
|
|
f22bf6a8f7 | ||
|
|
0e9bccd205 | ||
|
|
3c6f121493 | ||
|
|
68b26c4579 | ||
|
|
3183842375 | ||
|
|
2dcabf97dd | ||
|
|
efa513a569 | ||
|
|
dd67710848 | ||
|
|
2523040b08 | ||
|
|
fdca38c531 | ||
|
|
bd6f6dafdf | ||
|
|
5b8d2a1532 | ||
|
|
1f46b91f30 | ||
|
|
0ae36dd0a8 | ||
|
|
54b8bf219a | ||
|
|
bbd6924f77 | ||
|
|
546347cad4 | ||
|
|
04cbd98322 | ||
|
|
da8a5ccaa4 | ||
|
|
a9079f5b2d | ||
|
|
a5c231cd55 | ||
|
|
a46e808da1 | ||
|
|
33401147f2 | ||
|
|
90729a6445 | ||
|
|
d43b5583ac | ||
|
|
8159467dd5 | ||
|
|
60fa1b861d | ||
|
|
cac4e20dcd | ||
|
|
84fc1e6ec4 | ||
|
|
226ac86da2 | ||
|
|
85bd0e52cd | ||
|
|
d68d0914fc | ||
|
|
c0afc0bf56 | ||
|
|
c372647a36 | ||
|
|
32dcb29a25 | ||
|
|
cbfcd732e7 | ||
|
|
716b3574a7 | ||
|
|
f4d870243d | ||
|
|
ffa10203b0 | ||
|
|
7191b429e6 | ||
|
|
bf87a9c112 | ||
|
|
d6b69a0ecd | ||
|
|
e7d62d92e8 | ||
|
|
31b11b0a02 | ||
|
|
61c21ccd4a | ||
|
|
1839dbdd82 | ||
|
|
b575a4250d | ||
|
|
03d071df7a | ||
|
|
e1cd518163 | ||
|
|
d5da5b0864 | ||
|
|
bab28c6ad5 | ||
|
|
e79508c7c8 | ||
|
|
48395b2b5c | ||
|
|
41c0f7a224 | ||
|
|
482a795d6a | ||
|
|
a952846eab |
@@ -19,6 +19,8 @@ on:
|
||||
- 'pendingchanges/**'
|
||||
- '**.md'
|
||||
pull_request:
|
||||
# Add 'ready_for_review' to run CI when a draft PR is marked ready.
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- '**'
|
||||
paths-ignore:
|
||||
@@ -53,9 +55,10 @@ jobs:
|
||||
# VFX platform jobs. These are run on the appropriate CentOS images from
|
||||
# the provided ASWF docker containers
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'linux'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'linux')))
|
||||
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
|
||||
name: >
|
||||
linux:${{ matrix.config.image }}-abi:${{ matrix.config.abi }}-cxx:${{ matrix.config.cxx }}-type:${{ matrix.config.build }}
|
||||
@@ -119,9 +122,10 @@ jobs:
|
||||
windows:
|
||||
# Windows CI. Tests a dynamic build with MD.
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'win'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'win')))
|
||||
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'windows-2022-8c-32g-300h') || 'windows-latest' }}
|
||||
name: windows
|
||||
env:
|
||||
@@ -163,9 +167,10 @@ jobs:
|
||||
|
||||
macos:
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'mac'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'mac')))
|
||||
runs-on: ${{ matrix.config.image }}
|
||||
name: ${{ matrix.config.image }}
|
||||
env:
|
||||
|
||||
@@ -11,7 +11,6 @@ on:
|
||||
- 'CHANGES'
|
||||
- 'openvdb_maya/**'
|
||||
- 'pendingchanges/**'
|
||||
- '**.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
@@ -19,7 +18,6 @@ on:
|
||||
- 'CHANGES'
|
||||
- 'openvdb_maya/**'
|
||||
- 'pendingchanges/**'
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deploy:
|
||||
@@ -33,7 +31,24 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
validate_docs:
|
||||
# Cheap static checks that run before the heavier doxygen build:
|
||||
# * every #include <...> referenced from a fenced code block in doc/ resolves
|
||||
# * every github.com/.../blob/<branch>/<path> URL points to a file on disk
|
||||
# Catches the class of docs-rot from issue #2161 (e.g. nanovdb::util ->
|
||||
# nanovdb::tools rename) before the doxygen job runs.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: set_up_python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: run_validator
|
||||
run: python3 ci/validate_docs_references.py
|
||||
|
||||
doxygen:
|
||||
needs: validate_docs
|
||||
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
|
||||
env:
|
||||
CXX: g++
|
||||
@@ -45,9 +60,12 @@ jobs:
|
||||
run: ./ci/install_doxygen.sh 1_14_0
|
||||
- name: nanobind
|
||||
run: ./ci/install_nanobind.sh 2.5.0
|
||||
- name: install_latex
|
||||
run: ./ci/install_texlive.sh
|
||||
- name: build
|
||||
# Doxygen's native WARN_AS_ERROR=FAIL_ON_WARNINGS gates the docs build:
|
||||
# any warning fails the job. FAIL_ON_WARNINGS still generates the docs and
|
||||
# writes the full warning log first, so the artifact step below captures
|
||||
# it for debugging. Enabled via OPENVDB_DOXYGEN_WARN_AS_ERROR (see
|
||||
# doc/CMakeLists.txt).
|
||||
run: >
|
||||
./ci/build.sh -v
|
||||
--build-type=Release
|
||||
@@ -58,7 +76,18 @@ jobs:
|
||||
-DOPENVDB_PYTHON_WRAP_ALL_GRID_TYPES=ON
|
||||
-DUSE_EXPLICIT_INSTANTIATION=OFF
|
||||
-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON
|
||||
-DOPENVDB_DOXYGEN_WARN_AS_ERROR=ON
|
||||
\"
|
||||
- name: upload_doxygen_warning_log
|
||||
# Always upload so contributors can debug failed PR builds without
|
||||
# having to reproduce the doxygen environment locally.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: doxygen-warnings
|
||||
path: build/doc/doxygen.warnings.log
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
- name: pre_deploy
|
||||
# Overwrite global SSH configuration
|
||||
# https://github.com/peaceiris/actions-gh-pages/issues/719
|
||||
@@ -147,4 +176,3 @@ jobs:
|
||||
- Deployed from: AcademySoftwareFoundation/openvdb ${{ github.sha }}
|
||||
|
||||
Signed-off-by: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>"
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ on:
|
||||
- 'pendingchanges/**'
|
||||
- '**.md'
|
||||
pull_request:
|
||||
# Add 'ready_for_review' to run CI when a draft PR is marked ready.
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches:
|
||||
- '**'
|
||||
paths-ignore:
|
||||
@@ -44,10 +46,12 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
linux-nanovdb:
|
||||
# push OR non-draft PR OR matching manual run
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'linux'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'linux')))
|
||||
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
|
||||
name: >
|
||||
linux-nanovdb:cxx:${{ matrix.config.cxx }}-${{ matrix.config.build }}
|
||||
@@ -92,9 +96,10 @@ jobs:
|
||||
|
||||
windows-nanovdb:
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'win'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'win')))
|
||||
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'windows-2022-8c-32g-300h') || 'windows-latest' }}
|
||||
env:
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-windows'
|
||||
@@ -115,6 +120,9 @@ jobs:
|
||||
- name: install
|
||||
shell: powershell
|
||||
run: .\ci\install_windows.ps1
|
||||
- name: install_numpy
|
||||
shell: powershell
|
||||
run: .\ci\install_windows_numpy.ps1
|
||||
- name: build
|
||||
# nvcc doesn't set _WIN32 when run in bash so we need to set it manually
|
||||
shell: bash
|
||||
@@ -139,9 +147,10 @@ jobs:
|
||||
|
||||
macos-nanovdb:
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'mac'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || github.event.inputs.type == 'mac')))
|
||||
runs-on: ${{ matrix.config.runner }}
|
||||
env:
|
||||
CXX: ${{ matrix.config.cxx }}
|
||||
@@ -168,9 +177,10 @@ jobs:
|
||||
|
||||
nanovdb-lite:
|
||||
if: |
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
github.event.inputs.type == 'all' ||
|
||||
github.event.inputs.type == 'linux'
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' &&
|
||||
(github.event.inputs.type == 'all' || 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-clang17.2
|
||||
|
||||
@@ -145,16 +145,15 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- { 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' }
|
||||
- { 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: 'simd', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_X86_INSTRSET=7 -DUSE_VCL=ON' } # 7=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
|
||||
|
||||
+50
-29
@@ -80,6 +80,10 @@ option(OPENVDB_BUILD_NANOVDB "Build the NanoVDB library. Turns ON if USE_NANOVDB
|
||||
# Global options
|
||||
option(OPENVDB_ENABLE_RPATH "Build with RPATH information" ON)
|
||||
option(OPENVDB_ENABLE_ASSERTS "Build with asserts in OpenVDB code enabled" OFF)
|
||||
option(OPENVDB_ENABLE_VEC_RELATIONAL_OPERATIONS [=[
|
||||
Enabled deprecated relational < and > operators on Vector types. This option
|
||||
will be switched to OFF and the feature will be subsequently removed in future
|
||||
releases.]=] ON)
|
||||
option(OPENVDB_USE_DELAYED_LOADING "Build the core OpenVDB library with delayed-loading." ON)
|
||||
option(OPENVDB_CXX_STRICT "Enable or disable pre-defined compiler warnings" OFF)
|
||||
cmake_dependent_option(OPENVDB_INSTALL_CMAKE_MODULES
|
||||
@@ -131,6 +135,30 @@ required.]=] ${USE_EXR})
|
||||
option(USE_PNG "Use PNG while building openvdb components." OFF)
|
||||
option(USE_AX "Use OpenVDB AX while building openvdb components." ${OPENVDB_BUILD_AX})
|
||||
option(USE_NANOVDB "Use NanoVDB while building openvdb components." ${OPENVDB_BUILD_NANOVDB})
|
||||
option(USE_VCL [=[
|
||||
Use the internally supplied copy of Agner Fog's vectorclass (VCL) SIMD wrapper
|
||||
library to instrument OpenVDB tools. This also requires VCL to be installed
|
||||
along with OpenVDB headers. Only applicable which compiling for x86 targets.]=] OFF)
|
||||
set(OPENVDB_X86_INSTRSET 0 CACHE STRING [=[
|
||||
The x86 instruction set to use. When unset, if USE_VCL is ON, this is
|
||||
automatically configured based on provided build flags. When unset and USE_VCL
|
||||
is OFF, defaults to 0. It is expected for you to provide this flag. Available
|
||||
values are:
|
||||
0: Unknown or unspecified
|
||||
1: SSE -msse
|
||||
2: SSE2 -msse2 # Minimum on x86_64
|
||||
3: SSE3 -msse3
|
||||
4: SSSE3 -mssse3
|
||||
5: SSE4.1 -msse4.1
|
||||
6: SSE4.2 -msse4.2 # Minimum recommended where supported
|
||||
7: AVX -mavx
|
||||
8: AVX2 -mavx2
|
||||
9: AVX512F -mavx512f
|
||||
10: AVX512BW/DQ/VL -mavx512bw -mavx512dq -mavx512vl
|
||||
Note that (while not strictly enforced by vendors) each option implies all
|
||||
preceeding flags. If you need more granular control you can provide your
|
||||
desired flags via CXX_FLAGS. Only applicable which compiling for x86 targets.
|
||||
]=])
|
||||
|
||||
cmake_dependent_option(OPENVDB_DISABLE_BOOST_IMPLICIT_LINKING
|
||||
"Disable the implicit linking of Boost libraries on Windows" ON "WIN32" OFF)
|
||||
@@ -172,19 +200,6 @@ elseif(NOT ${CONCURRENT_MALLOC} IN_LIST _CONCURRENT_MALLOC_OPTIONS)
|
||||
set(CONCURRENT_MALLOC Auto CACHE STRING FORCE)
|
||||
endif()
|
||||
|
||||
set(_OPENVDB_SIMD_OPTIONS None SSE42 AVX)
|
||||
if(NOT OPENVDB_SIMD)
|
||||
set(OPENVDB_SIMD None CACHE STRING
|
||||
"Choose whether to enable SIMD compiler flags or not, options are: None SSE42 AVX.
|
||||
Although not required, it is strongly recommended to enable SIMD. AVX implies SSE42.
|
||||
None is the default." FORCE
|
||||
)
|
||||
elseif(NOT ${OPENVDB_SIMD} IN_LIST _OPENVDB_SIMD_OPTIONS)
|
||||
message(WARNING "Unrecognized or unsupported value for OPENVDB_SIMD, "
|
||||
"using None instead.")
|
||||
set(OPENVDB_SIMD None CACHE STRING FORCE)
|
||||
endif()
|
||||
|
||||
if(USE_BLOSC AND NOT USE_ZLIB)
|
||||
message(WARNING "ZLib can only be disabled if Blosc is also disabled. Enabling ZLib.")
|
||||
endif()
|
||||
@@ -385,6 +400,28 @@ endif()
|
||||
|
||||
#########################################################################
|
||||
|
||||
# Still support -DOPENVDB_SIMD=SSE42 and -DOPENVDB_SIMD=AVX, these alias to
|
||||
# their respective OPENVDB_X86_INSTRSET value but, as a build option, are
|
||||
# deprecated. Not that the defines in version.h need to persist in case
|
||||
# downstream software was using them before including VDB headers
|
||||
set(_OPENVDB_SIMD_OPTIONS None SSE42 AVX)
|
||||
if(NOT OPENVDB_SIMD)
|
||||
set(OPENVDB_SIMD None CACHE STRING "Deprecated, see OPENVDB_X86_INSTRSET." FORCE)
|
||||
else()
|
||||
if(NOT ${OPENVDB_SIMD} IN_LIST _OPENVDB_SIMD_OPTIONS)
|
||||
message(WARNING "Unrecognized or unsupported value for OPENVDB_SIMD, using None instead.")
|
||||
set(OPENVDB_SIMD None CACHE STRING "Deprecated, see OPENVDB_X86_INSTRSET." FORCE)
|
||||
endif()
|
||||
message(DEPRECATION "The OPENVDB_SIMD option is deprecated and will be removed. Use OPENVDB_X86_INSTRSET.")
|
||||
if(OPENVDB_SIMD STREQUAL "AVX")
|
||||
set(OPENVDB_X86_INSTRSET 7)
|
||||
elseif(OPENVDB_SIMD STREQUAL "SSE42")
|
||||
set(OPENVDB_X86_INSTRSET 6)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#########################################################################
|
||||
|
||||
# Configure general CMake and CXX settings
|
||||
|
||||
include(cmake/config/OpenVDBCXX.cmake)
|
||||
@@ -404,22 +441,6 @@ if(CONCURRENT_MALLOC STREQUAL "Auto")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Configure SIMD. AVX implies SSE 4.2.
|
||||
|
||||
if(OPENVDB_SIMD STREQUAL "AVX")
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx>")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse4.2>")
|
||||
endif()
|
||||
add_compile_definitions("$<$<COMPILE_LANGUAGE:CXX>:OPENVDB_USE_AVX>")
|
||||
add_compile_definitions("$<$<COMPILE_LANGUAGE:CXX>:OPENVDB_USE_SSE42>")
|
||||
elseif(OPENVDB_SIMD STREQUAL "SSE42")
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse4.2>")
|
||||
endif()
|
||||
add_compile_definitions("$<$<COMPILE_LANGUAGE:CXX>:OPENVDB_USE_SSE42>")
|
||||
endif()
|
||||
|
||||
#########################################################################
|
||||
|
||||
# Configure our cmake modules to only search for static libraries
|
||||
|
||||
@@ -57,6 +57,10 @@ code and has the right to release it under the
|
||||
[Apache License, version 2.0](LICENSE.md)
|
||||
license. See the [TAC documentation on contribution sign off](https://github.com/AcademySoftwareFoundation/tac/blob/master/process/contributing.md#contribution-sign-off) for more information on this requirement.
|
||||
|
||||
#### Stale PRs
|
||||
|
||||
A PR that does not have a signed CLA or DCO sign-off will be given at least one week in which to resolve the issue and then will be closed. The TSC cannot review or merge PRs without these requirements having been met. Please re-open PRs that are now expected to pass once this criteria has been met.
|
||||
|
||||
## Maintainer
|
||||
|
||||
The maintainer role is the equivalent of the "Committer" role in the charter.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
set -ex
|
||||
|
||||
# Download the TeX Live installer
|
||||
wget https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz
|
||||
wget https://mirrors.mit.edu/CTAN/systems/texlive/tlnet/install-tl-unx.tar.gz
|
||||
|
||||
# Extract
|
||||
tar xzf install-tl-unx.tar.gz
|
||||
@@ -13,7 +13,7 @@ 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
|
||||
./install-tl --no-interaction --no-doc-install --no-src-install --scheme=minimal --repository https://mirrors.mit.edu/CTAN/systems/texlive/tlnet
|
||||
|
||||
# Export the texlive PATH to GITHUB_PATH
|
||||
TEXLIVE_PATH=$(echo /usr/local/texlive/*/bin/x86_64-linux)
|
||||
|
||||
Executable
+236
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright Contributors to the OpenVDB Project
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Validate cross-references inside the OpenVDB documentation tree.
|
||||
|
||||
Run from the repo root (no arguments) or pass paths to validate explicitly.
|
||||
The script scans Markdown (`.md`) and Doxygen narrative (`.txt`) files under
|
||||
``doc/`` and checks two classes of cross-reference for staleness:
|
||||
|
||||
1. ``#include <path/to/header.h>`` lines that appear inside Markdown
|
||||
``cpp``/``cuda``/``c``/``c++`` fenced code blocks or inside Doxygen
|
||||
``@code``/``\\code`` blocks. Each include is resolved to a real file in
|
||||
``openvdb/``, ``openvdb_ax/``, ``openvdb_houdini/`` or ``nanovdb/``.
|
||||
|
||||
2. ``https://github.com/AcademySoftwareFoundation/openvdb/blob/<ref>/<path>``
|
||||
URLs. The ``<path>`` portion is checked against the local checkout.
|
||||
|
||||
The script exits non-zero on any unresolved reference and prints a
|
||||
``file:line`` style error so the failure is actionable in CI logs. It is
|
||||
intentionally dependency-free so it can run in a minimal Python container.
|
||||
|
||||
This guards against the class of bug tracked in issue #2161, where renames
|
||||
under ``nanovdb/util/ -> nanovdb/tools/`` and ``nanovdb/util/IO.h ->
|
||||
nanovdb/io/IO.h`` silently left the docs referencing paths that no longer
|
||||
exist.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Sequence, Tuple
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Map a top-level include namespace to one or more on-disk directories that it
|
||||
# resolves against. ``#include <nanovdb/io/IO.h>`` resolves to
|
||||
# ``nanovdb/nanovdb/io/IO.h`` because the repo uses a double-named layout;
|
||||
# ``#include <houdini_utils/ParmFactory.h>`` resolves to
|
||||
# ``openvdb_houdini/openvdb_houdini/ParmFactory.h`` because the Houdini plugin
|
||||
# CMake configuration synthesises a ``houdini_utils/`` subtree at build time
|
||||
# (see ``openvdb_houdini/openvdb_houdini/CMakeLists.txt``).
|
||||
INCLUDE_NAMESPACE_ROOTS = {
|
||||
"openvdb": ("openvdb/openvdb", "openvdb"),
|
||||
"openvdb_ax": ("openvdb_ax/openvdb_ax", "openvdb_ax"),
|
||||
"openvdb_houdini": ("openvdb_houdini/openvdb_houdini", "openvdb_houdini"),
|
||||
"nanovdb": ("nanovdb/nanovdb", "nanovdb"),
|
||||
"houdini_utils": ("openvdb_houdini/openvdb_houdini",),
|
||||
}
|
||||
|
||||
# Top-level include path components that come from outside this repo. When a
|
||||
# header's leading component matches one of these, we skip resolution entirely.
|
||||
# Houdini SDK headers (``UT/``, ``GA/``, ``GEO_*``, etc.) live in the Houdini
|
||||
# install, not in our checkout.
|
||||
EXTERNAL_INCLUDE_PREFIXES = (
|
||||
"boost/",
|
||||
"tbb/",
|
||||
"cuda", # cuda_runtime.h, cuda.h, ...
|
||||
"thrust/",
|
||||
"Eigen/",
|
||||
"OpenEXR/",
|
||||
"Imath/",
|
||||
"openexr/",
|
||||
"GL/",
|
||||
# Houdini SDK prefixes
|
||||
"UT/",
|
||||
"GA/",
|
||||
"GU/",
|
||||
"GEO/",
|
||||
"GT/",
|
||||
"GR/",
|
||||
"OP/",
|
||||
"PRM/",
|
||||
"SOP/",
|
||||
"SYS/",
|
||||
"CH/",
|
||||
"DEP/",
|
||||
"PI/",
|
||||
"RE/",
|
||||
# Standard library headers (no '/' is the usual signal, but list a few
|
||||
# explicitly to be safe).
|
||||
"stdio.h",
|
||||
"iostream",
|
||||
"vector",
|
||||
"string",
|
||||
"memory",
|
||||
"cstdint",
|
||||
)
|
||||
|
||||
# Match a Markdown fenced code block whose language is one of cpp/cuda/c/c++.
|
||||
_MARKDOWN_FENCE_RE = re.compile(
|
||||
r"^```(?:cpp|cuda|c\+\+|c)\s*$(?P<body>.*?)^```\s*$",
|
||||
re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
|
||||
# Doxygen ``@code``/``\code`` ... ``@endcode``/``\endcode`` blocks. The
|
||||
# language hint (``@code{.cpp}``) is optional.
|
||||
_DOXY_CODE_RE = re.compile(
|
||||
r"[@\\]code(?:\{[^}]*\})?\s*$(?P<body>.*?)^[@\\]endcode\s*$",
|
||||
re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
|
||||
_INCLUDE_RE = re.compile(r"#\s*include\s*<([^>\n]+)>")
|
||||
|
||||
# Match URLs pointing into a blob in this repo. ``<ref>`` may be a branch
|
||||
# name or a sha; ``<path>`` is the portion that must exist on disk today.
|
||||
_GITHUB_BLOB_RE = re.compile(
|
||||
r"https?://github\.com/AcademySoftwareFoundation/openvdb/blob/"
|
||||
r"(?P<ref>[^/\s]+)/(?P<path>[^\s)\"'#?]+)"
|
||||
)
|
||||
|
||||
|
||||
def is_external_include(header: str) -> bool:
|
||||
"""Return True for headers we cannot resolve from this repo."""
|
||||
head, _, _ = header.partition("/")
|
||||
if head in INCLUDE_NAMESPACE_ROOTS:
|
||||
return False
|
||||
return any(header.startswith(p) for p in EXTERNAL_INCLUDE_PREFIXES) or "/" not in header
|
||||
|
||||
|
||||
def resolve_include(header: str) -> Path | None:
|
||||
"""Resolve ``#include <header>`` to a path on disk, or None if not found."""
|
||||
head, _, rest = header.partition("/")
|
||||
roots = INCLUDE_NAMESPACE_ROOTS.get(head)
|
||||
if roots is None or not rest:
|
||||
return None
|
||||
for root in roots:
|
||||
candidate = REPO_ROOT / root / rest
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def iter_code_block_lines(text: str) -> Iterable[Tuple[int, str]]:
|
||||
"""Yield (line_number, line_text) for every line inside a code block.
|
||||
|
||||
Line numbers are 1-based and refer to the original ``text``. Handles both
|
||||
Markdown fences (```cpp ...```) and Doxygen ``@code``/``\\code`` blocks.
|
||||
"""
|
||||
for match in _MARKDOWN_FENCE_RE.finditer(text):
|
||||
body_start = match.start("body")
|
||||
yield from _lines_with_offsets(text, match.group("body"), body_start)
|
||||
for match in _DOXY_CODE_RE.finditer(text):
|
||||
body_start = match.start("body")
|
||||
yield from _lines_with_offsets(text, match.group("body"), body_start)
|
||||
|
||||
|
||||
def _lines_with_offsets(full_text: str, body: str, body_offset: int) -> Iterable[Tuple[int, str]]:
|
||||
base_line = full_text.count("\n", 0, body_offset) + 1
|
||||
for i, line in enumerate(body.splitlines()):
|
||||
yield base_line + i, line
|
||||
|
||||
|
||||
def find_doc_files(extra_paths: Sequence[str]) -> List[Path]:
|
||||
"""Return the list of doc files to scan.
|
||||
|
||||
Without arguments, scan ``doc/**/*.md`` and ``doc/**/*.txt``. With
|
||||
arguments, scan only those files (resolved relative to the repo root).
|
||||
"""
|
||||
if extra_paths:
|
||||
return [
|
||||
(Path(p) if Path(p).is_absolute() else REPO_ROOT / p).resolve()
|
||||
for p in extra_paths
|
||||
]
|
||||
doc_dir = REPO_ROOT / "doc"
|
||||
return sorted(
|
||||
list(doc_dir.rglob("*.md")) + list(doc_dir.rglob("*.txt"))
|
||||
)
|
||||
|
||||
|
||||
def check_file(path: Path) -> List[str]:
|
||||
"""Return a list of human-readable error strings for the given doc file."""
|
||||
errors: List[str] = []
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
return [f"{path}: failed to read: {exc}"]
|
||||
|
||||
rel = path.relative_to(REPO_ROOT) if path.is_relative_to(REPO_ROOT) else path
|
||||
|
||||
for line_no, line in iter_code_block_lines(text):
|
||||
for include_match in _INCLUDE_RE.finditer(line):
|
||||
header = include_match.group(1).strip()
|
||||
if is_external_include(header):
|
||||
continue
|
||||
if resolve_include(header) is None:
|
||||
errors.append(
|
||||
f"{rel}:{line_no}: include <{header}> does not resolve "
|
||||
"to a file in this checkout"
|
||||
)
|
||||
|
||||
for url_match in _GITHUB_BLOB_RE.finditer(text):
|
||||
ref = url_match.group("ref")
|
||||
url_path = url_match.group("path")
|
||||
on_disk = REPO_ROOT / url_path
|
||||
if not on_disk.exists():
|
||||
# Compute the 1-based line number of the URL for the error report.
|
||||
line_no = text.count("\n", 0, url_match.start()) + 1
|
||||
errors.append(
|
||||
f"{rel}:{line_no}: github URL "
|
||||
f"AcademySoftwareFoundation/openvdb/blob/{ref}/{url_path} "
|
||||
"does not exist in this checkout"
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> int:
|
||||
files = find_doc_files(argv[1:])
|
||||
total_errors: List[str] = []
|
||||
for path in files:
|
||||
if not path.exists():
|
||||
total_errors.append(f"{path}: file does not exist")
|
||||
continue
|
||||
total_errors.extend(check_file(path))
|
||||
|
||||
if total_errors:
|
||||
print("Documentation reference validation FAILED.", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
for err in total_errors:
|
||||
print(err, file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(
|
||||
f"{len(total_errors)} broken reference(s) across "
|
||||
f"{len(files)} file(s).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print(f"OK: {len(files)} doc file(s) validated, no broken references.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -419,9 +419,11 @@ if(NOT TARGET Blosc::blosc)
|
||||
set(${BLOSC_EXTERNAL_LIB}_LIBRARIES ${${BLOSC_EXTERNAL_LIB}_LIBRARY_DEBUG})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Blosc::blosc INTERFACE
|
||||
$<$<CONFIG:Release>:${${BLOSC_EXTERNAL_LIB}_LIBRARY_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${${BLOSC_EXTERNAL_LIB}_LIBRARY_DEBUG}>)
|
||||
if(${BLOSC_EXTERNAL_LIB}_LIBRARY_RELEASE OR ${BLOSC_EXTERNAL_LIB}_LIBRARY_DEBUG)
|
||||
target_link_libraries(Blosc::blosc INTERFACE
|
||||
$<$<CONFIG:Release>:${${BLOSC_EXTERNAL_LIB}_LIBRARY_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${${BLOSC_EXTERNAL_LIB}_LIBRARY_DEBUG}>)
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
+116
-4
@@ -83,8 +83,8 @@ function(OPENVDB_GET_VERSION_DEFINE HEADER KEY VALUE)
|
||||
endfunction()
|
||||
|
||||
|
||||
########################################################################
|
||||
########################################################################
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
|
||||
|
||||
function(OPENVDB_VERSION_FROM_HEADER OPENVDB_VERSION_FILE)
|
||||
@@ -120,8 +120,8 @@ function(OPENVDB_VERSION_FROM_HEADER OPENVDB_VERSION_FILE)
|
||||
endfunction()
|
||||
|
||||
|
||||
########################################################################
|
||||
########################################################################
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
|
||||
|
||||
function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
||||
@@ -162,3 +162,115 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
||||
set(${_VDB_ABI} ${_OpenVDB_ABI} PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
## @brief Compute the enumerated ISA value for x86/x86_64 architectures that
|
||||
## VCL will use during compilation of OpenVDB. The value is stored in the
|
||||
## VCL_INSTRSET variable. Possible values are:
|
||||
## 0: Unknown or error
|
||||
## 2: SSE2
|
||||
## 3: SSE3
|
||||
## 4: SSSE3
|
||||
## 5: SSE4.1
|
||||
## 6: SSE4.2
|
||||
## 7: AVX
|
||||
## 8: AVX2
|
||||
## 9: AVX512F
|
||||
## 10: AVX512BW/DQ/VL
|
||||
function(OPENVDB_COMPUTE_X86_INSTRSET_FROM_VCL VCL_INSTRSET)
|
||||
# Append project specific flags
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
if(OPENVDB_SIMD STREQUAL "AVX")
|
||||
set(CMAKE_REQUIRED_FLAGS "-mavx;-msse4.2")
|
||||
elseif(OPENVDB_SIMD STREQUAL "SSE42")
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2")
|
||||
endif()
|
||||
|
||||
# Figure out that the INSTRSET macro would expand to.
|
||||
# @note would be much faster with try_run but this way doesn't involve
|
||||
# invoking bad instruction sets on unsupported ISAs on the build machine.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 10); return 0; } ]] COMPUTE_VCL_INSTRSET__AVX512VL_DQ_BW__)
|
||||
if(COMPUTE_VCL_INSTRSET__AVX512VL_DQ_BW__)
|
||||
set(${VCL_INSTRSET} 10 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 9); return 0; } ]] COMPUTE_VCL_INSTRSET__AVX512F__)
|
||||
if(COMPUTE_VCL_INSTRSET__AVX512F__)
|
||||
set(${VCL_INSTRSET} 9 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 8); return 0; } ]] COMPUTE_VCL_INSTRSET__AVX2__)
|
||||
if(COMPUTE_VCL_INSTRSET__AVX2__)
|
||||
set(${VCL_INSTRSET} 8 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 7); return 0; } ]] COMPUTE_VCL_INSTRSET__AVX__)
|
||||
if(COMPUTE_VCL_INSTRSET__AVX__)
|
||||
set(${VCL_INSTRSET} 7 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 6); return 0; } ]] COMPUTE_VCL_INSTRSET__SSE4_2__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSE4_2__)
|
||||
set(${VCL_INSTRSET} 6 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 5); return 0; } ]] COMPUTE_VCL_INSTRSET__SSE4_1__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSE4_1__)
|
||||
set(${VCL_INSTRSET} 5 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 4); return 0; } ]] COMPUTE_VCL_INSTRSET__SSSE3__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSSE3__)
|
||||
set(${VCL_INSTRSET} 4 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 3); return 0; } ]] COMPUTE_VCL_INSTRSET__SSE3__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSE3__)
|
||||
set(${VCL_INSTRSET} 3 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 2); return 0; } ]] COMPUTE_VCL_INSTRSET__SSE2__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSE2__)
|
||||
set(${VCL_INSTRSET} 2 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
check_cxx_source_compiles([[
|
||||
#include "${CMAKE_SOURCE_DIR}/ext/vcl/openvdb/ext/vcl/instrset.h"
|
||||
int main() { static_assert(INSTRSET == 1); return 0; } ]] COMPUTE_VCL_INSTRSET__SSE__)
|
||||
if(COMPUTE_VCL_INSTRSET__SSE__)
|
||||
set(${VCL_INSTRSET} 1 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
return()
|
||||
endif()
|
||||
# Unknown or error
|
||||
set(${VCL_INSTRSET} 0 PARENT_SCOPE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
endfunction()
|
||||
|
||||
@@ -127,6 +127,47 @@ endif()
|
||||
|
||||
###############################################################################
|
||||
|
||||
# x86 ISA selection
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 1)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 2)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse2>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 3)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse3>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 4)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mssse3>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 5)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse4.1>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 6)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-msse4.2>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 7)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 8)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx2>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 9)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx512f>")
|
||||
endif()
|
||||
if(OPENVDB_X86_INSTRSET GREATER_EQUAL 10)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx512bw>")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx512dq>")
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-mavx512vl>")
|
||||
endif()
|
||||
elseif(OPENVDB_X86_INSTRSET GREATER 0)
|
||||
message(WARNING "OPENVDB_X86_INSTRSET has been set but has no effect when building for ${CMAKE_SYSTEM_PROCESSOR}.")
|
||||
endif()
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Increase the number of sections that an object file can contain
|
||||
add_compile_options("$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/bigobj>")
|
||||
# Enable MSVC options that make it behave like other compilers
|
||||
|
||||
+50
-4
@@ -25,6 +25,14 @@ option(OPENVDB_DOXYGEN_NANOVDB "Build NanoVDB documentation" ON)
|
||||
option(OPENVDB_DOXYGEN_INTERNAL [=[
|
||||
Enable documentation of methods and classes which have been explicitly marked as internal
|
||||
]=] OFF)
|
||||
# When enabled, Doxygen's WARN_AS_ERROR=FAIL_ON_WARNINGS makes any warning fail
|
||||
# the docs build (it still generates the docs and writes the full warning log
|
||||
# first, then exits non-zero). CI turns this ON -- see .github/workflows/docs.yml
|
||||
# -- so net-new doc warnings fail the PR that introduces them. Defaults OFF so a
|
||||
# local doc build isn't blocked by a stray warning from a different Doxygen
|
||||
# version; pass -DOPENVDB_DOXYGEN_WARN_AS_ERROR=ON to gate locally too.
|
||||
option(OPENVDB_DOXYGEN_WARN_AS_ERROR
|
||||
"Treat Doxygen warnings as errors" OFF)
|
||||
|
||||
find_package(Doxygen 1.0...<1.15.0 REQUIRED doxygen)
|
||||
if(MINIMUM_DOXYGEN_VERSION)
|
||||
@@ -64,9 +72,14 @@ if(OPENVDB_DOXYGEN_INTERNAL)
|
||||
endif()
|
||||
|
||||
if(NOT OPENVDB_DOXYGEN_INTERNAL)
|
||||
# Hide implementation-detail symbols in the lowercase `internal` namespaces
|
||||
# (the project convention for non-public helpers). Note: do NOT add a
|
||||
# capital-I "*Internal*" glob here -- it also matches the public
|
||||
# tree::InternalNode (and nanovdb::InternalNode) class, silently dropping it
|
||||
# from the docs and breaking every \link to it (e.g. InternalNode::childCount
|
||||
# in doc/changes.txt) while its RootNode/LeafNode siblings resolve fine.
|
||||
set(DOXYGEN_EXCLUDE_SYMBOLS
|
||||
"*internal*"
|
||||
"*Internal*")
|
||||
"*internal*")
|
||||
endif()
|
||||
|
||||
set(DOXYGEN_PROJECT_NAME "OpenVDB")
|
||||
@@ -77,15 +90,41 @@ set(DOXYGEN_FILE_PATTERNS *.h *h.in) # headers only
|
||||
set(DOXYGEN_EXTENSION_MAPPING .in=C) # parse CMake config headers as C
|
||||
set(DOXYGEN_IMAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/img")
|
||||
set(DOXYGEN_RECURSIVE NO)
|
||||
set(DOXYGEN_QUIET YES)
|
||||
set(DOXYGEN_NUM_PROC_THREADS 0) # detects from system
|
||||
|
||||
# Enable \snippet resolution against the on-disk example sources. Paths in
|
||||
# \snippet directives are resolved relative to EXAMPLE_PATH, so referring to
|
||||
# `nanovdb/nanovdb/examples/ex_xxx/yyy.cc` from a doc page works as written.
|
||||
set(DOXYGEN_EXAMPLE_PATH "${CMAKE_SOURCE_DIR}")
|
||||
set(DOXYGEN_EXAMPLE_PATTERNS *.cc *.cpp *.cu *.cuh *.h *.txt)
|
||||
set(DOXYGEN_EXAMPLE_RECURSIVE YES)
|
||||
|
||||
# Always capture Doxygen warnings to a logfile so CI can publish it as an
|
||||
# artifact and contributors can see exactly what's broken.
|
||||
set(DOXYGEN_WARN_LOGFILE "${CMAKE_CURRENT_BINARY_DIR}/doxygen.warnings.log")
|
||||
if(OPENVDB_DOXYGEN_WARN_AS_ERROR)
|
||||
# FAIL_ON_WARNINGS keeps generating docs but exits non-zero at the end if
|
||||
# any warning was emitted. Requires Doxygen >= 1.9.x.
|
||||
set(DOXYGEN_WARN_AS_ERROR FAIL_ON_WARNINGS)
|
||||
# Don't suppress messages so contributors can see warnings during the build.
|
||||
set(DOXYGEN_QUIET NO)
|
||||
else()
|
||||
set(DOXYGEN_QUIET YES)
|
||||
endif()
|
||||
|
||||
set(DOXYGEN_GENERATE_HTML YES)
|
||||
set(DOXYGEN_GENERATE_MAN NO)
|
||||
set(DOXYGEN_GENERATE_LATEX NO)
|
||||
set(DOXYGEN_GENERATE_TODOLIST NO)
|
||||
set(DOXYGEN_HTML_TIMESTAMP NO) # for simpler website diffs
|
||||
|
||||
# Render math formulas via MathJax instead of LaTeX -> PNG. This removes the
|
||||
# entire `latex`/`dvips`/`gs` toolchain dependency from both local doc builds
|
||||
# and CI (see .github/workflows/docs.yml), eliminates a class of cross-platform
|
||||
# install pain, and produces crisper, selectable math in the rendered HTML.
|
||||
set(DOXYGEN_USE_MATHJAX YES)
|
||||
set(DOXYGEN_MATHJAX_VERSION MathJax_3) # default JS bundle loaded from a CDN
|
||||
|
||||
set(DOXYGEN_EXTRACT_ALL YES)
|
||||
set(DOXYGEN_EXTRACT_STATIC YES)
|
||||
set(DOXYGEN_EXTRACT_LOCAL_CLASSES NO)
|
||||
@@ -177,6 +216,11 @@ set(DOXYGEN_PREDEFINED
|
||||
"OPENVDB_VERSION_NAME=v${OpenVDB_MAJOR_VERSION}_${OpenVDB_MINOR_VERSION}"
|
||||
"OPENVDB_ABI_VERSION_NUMBER=${OpenVDB_MAJOR_VERSION}"
|
||||
[[__declspec(x):= __attribute__(x):=]]
|
||||
# Strip the symbol-visibility/export macro so Doxygen doesn't fold it into a
|
||||
# declaration's name. Doxygen tolerates `class OPENVDB_API X` but mis-parses
|
||||
# `enum OPENVDB_API X` (e.g. half::FromBitsTag in ext/imath/.../Half.h),
|
||||
# reporting the enum as a documented-but-undeclared symbol.
|
||||
"OPENVDB_API="
|
||||
# Replace the deprecated macros with doxygen deprecated comments. This ensures
|
||||
# that every bit of deprecated C++ is listed in the doxygen deprecated list,
|
||||
# even if it was not properly tagged
|
||||
@@ -240,7 +284,9 @@ if(OPENVDB_DOXYGEN_HOUDINI)
|
||||
endif()
|
||||
|
||||
if(OPENVDB_DOXYGEN_AX)
|
||||
set(DOXYGEN_EXTRA_PACKAGES amsmath) # for math formatting
|
||||
# AX docs use @f$ inline and @f[ block math. Formula font size also applies
|
||||
# under MathJax (set above). The amsmath package the AX docs assume is
|
||||
# bundled by default in MathJax v3, so no extra extensions are required.
|
||||
set(DOXYGEN_FORMULA_FONTSIZE 14)
|
||||
list(APPEND DOXYGEN_ENABLED_SECTIONS "OPENVDB_AX")
|
||||
|
||||
|
||||
@@ -42,5 +42,4 @@ provided:
|
||||
- @subpage axfunctionlist "AX Functions" for a list of all available AX functions.
|
||||
- @ref build "Building OpenVDB" and @ref dependencies "OpenVDB Dependencies" for information on building OpenVDB and <b>OpenVDB AX</b>.
|
||||
|
||||
</div>
|
||||
*/
|
||||
|
||||
+37
-39
@@ -54,7 +54,7 @@ OpenVDB:
|
||||
ellipsoidal rasterizations.
|
||||
|
||||
- API Changes:
|
||||
- Added @vdblink::tree::LeafBuffer::storage() LeafBuffer::storage()@endlink
|
||||
- Added @c openvdb::tree::LeafBuffer::storage()
|
||||
for direct const access to the raw data buffer when using the bool LeafNode.
|
||||
- Removed Grid::evalMinMax(), use
|
||||
@vdblink::tools::minMax tools::minMax@endlink for superior threaded
|
||||
@@ -2807,8 +2807,8 @@ Improvements:
|
||||
<I>[Contributed by Double Negative]</I>
|
||||
- Registration of OpenVDB Points grid and attribute types is now
|
||||
handled in @vdblink::initialize() openvdb::initialize@endlink,
|
||||
and @vdblink::points::initialize() points::initialize@endlink
|
||||
and @vdblink::points::uninitialize() points::uninitialize@endlink
|
||||
and @c openvdb::points::initialize()
|
||||
and @c openvdb::points::uninitialize()
|
||||
are therefore deprecated.
|
||||
- Extended multi-pass I/O to handle a variable number of passes per leaf node.
|
||||
<I>[Contributed by Double Negative]</I>
|
||||
@@ -3526,11 +3526,10 @@ Houdini:
|
||||
- @vdblink::tools::volumeToMesh() tools::volumeToMesh@endlink now compiles
|
||||
for all grid types but throws an exception if the input grid does not
|
||||
have a scalar value type.
|
||||
- Added a
|
||||
@vdblink::io::File::readGrid(const Name&, const BBoxd&) File::readGrid@endlink
|
||||
overload and @vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&)
|
||||
readBuffers@endlink overloads to the grid, tree and node classes that allow
|
||||
one to specify a bounding box against which to clip a grid while reading it.
|
||||
- Added an <TT>openvdb::io::File::readGrid(const Name&, const BBoxd&)</TT>
|
||||
overload and <TT>openvdb::GridBase::readBuffers(std::istream&, const CoordBBox&)</TT>
|
||||
overloads to the grid, tree and node classes that allow one to specify a
|
||||
bounding box against which to clip a grid while reading it.
|
||||
For large grids, clipping while reading can result in significantly lower
|
||||
memory usage than clipping after reading.
|
||||
- Added @vdblink::GridBase::clipGrid() GridBase::clipGrid@endlink, which
|
||||
@@ -3589,10 +3588,11 @@ Houdini:
|
||||
- Added constructors and methods to both
|
||||
@vdblink::math::RandInt RandInt@endlink and
|
||||
@vdblink::math::Rand01 Rand01@endlink to set and reset the random seed value.
|
||||
- Added convenience methods for
|
||||
@vdblink::math::Transform::indexToWorld(const BBoxd&) const transforming@endlink
|
||||
@vdblink::math::Transform::worldToIndex(const BBoxd&) const bounding@endlink
|
||||
@vdblink::math::Transform::worldToIndexCellCentered(const BBoxd&) const boxes@endlink
|
||||
- Added convenience methods
|
||||
<TT>openvdb::math::Transform::indexToWorld(const BBoxd&)</TT>,
|
||||
<TT>openvdb::math::Transform::worldToIndex(const BBoxd&)</TT> and
|
||||
<TT>openvdb::math::Transform::worldToIndexCellCentered(const BBoxd&)</TT>
|
||||
for transforming bounding boxes
|
||||
to @vdblink::math::Transform math::Transform@endlink.
|
||||
- @c vdb_view is now compatible with both GLFW 2 and GLFW 3.
|
||||
- Made many small changes to address type conversion and other warnings
|
||||
@@ -3612,9 +3612,9 @@ Houdini:
|
||||
|
||||
@par
|
||||
API changes:
|
||||
- The addition of a
|
||||
@vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&)
|
||||
GridBase::readBuffers@endlink virtual function overload and the
|
||||
- The addition of an
|
||||
<TT>openvdb::GridBase::readBuffers(std::istream&, const CoordBBox&)</TT>
|
||||
virtual function overload and the
|
||||
@vdblink::GridBase::clip() GridBase::clip@endlink
|
||||
@vdblink::GridBase::readNonresidentBuffers()
|
||||
GridBase::readNonresidentBuffers@endlink and
|
||||
@@ -3719,16 +3719,15 @@ Houdini:
|
||||
extractSparseTreeWithMask@endlink, which copies data from the index-space
|
||||
intersection of a sparse tree and a dense input grid.
|
||||
- Added copy constructors to the
|
||||
@vdblink::Grid::Grid(const Grid<OtherTreeType>&) Grid@endlink,
|
||||
@vdblink::tree::Tree::Tree(const Tree<OtherRootType>&) Tree@endlink,
|
||||
@vdblink::tree::RootNode::RootNode(const RootNode<OtherChildType>&)
|
||||
RootNode@endlink,
|
||||
@vdblink::tree::InternalNode::InternalNode(const InternalNode<OtherChildNodeType, Log2Dim>&)
|
||||
InternalNode@endlink and
|
||||
@vdblink::tree::LeafNode::LeafNode(const LeafNode<OtherValueType, Log2Dim>&) LeafNode@endlink
|
||||
<TT>openvdb::Grid::Grid(const Grid<OtherTreeType>&)</TT>,
|
||||
<TT>openvdb::tree::Tree::Tree(const Tree<OtherRootType>&)</TT>,
|
||||
<TT>openvdb::tree::RootNode::RootNode(const RootNode<OtherChildType>&)</TT>,
|
||||
<TT>openvdb::tree::InternalNode::InternalNode(const InternalNode<OtherChildNodeType, Log2Dim>&)</TT>
|
||||
and
|
||||
<TT>openvdb::tree::LeafNode::LeafNode(const LeafNode<OtherValueType, Log2Dim>&)</TT>
|
||||
classes, and an assignment operator overload to
|
||||
@vdblink::tree::RootNode::operator=(const RootNode<OtherChildType>&)
|
||||
RootNode@endlink, that allow the source and destination to have different
|
||||
<TT>openvdb::tree::RootNode::operator=(const RootNode<OtherChildType>&)</TT>,
|
||||
that allow the source and destination to have different
|
||||
value types.
|
||||
- Modified @vdblink::tree::Tree::combine2() Tree::combine2@endlink to permit
|
||||
combination of trees with different value types.
|
||||
@@ -3780,7 +3779,7 @@ API changes:
|
||||
LeafManager::LeafRange::Iterator@endlink,
|
||||
@vdblink::util::BaseMaskIterator::operator=() BaseMaskIterator@endlink,
|
||||
@vdblink::util::NodeMask::operator=() NodeMask@endlink and
|
||||
@vdblink::util::RootNodeMask::operator=() RootNodeMask@endlink
|
||||
@c openvdb::util::RootNodeMask::operator=()
|
||||
now return references to the respective objects.
|
||||
- Removed a number of methods that were deprecated in version 2.0.0
|
||||
or earlier.
|
||||
@@ -4246,7 +4245,7 @@ API changes:
|
||||
- @vdblink::tree::LeafManager tree::LeafManager@endlink can now be
|
||||
instantiated with a @const tree, although buffer swapping with @const trees
|
||||
is disabled.
|
||||
- Added a @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink
|
||||
- Added an @c openvdb::Grid::signedFloodFill()
|
||||
overload that allows one to specify inside and outside values.
|
||||
- Fixed a bug in Grid::setBackground() so that now only the values of
|
||||
inactive voxels change.
|
||||
@@ -4256,9 +4255,9 @@ API changes:
|
||||
that relied on input and output grids having compatible tree topology.
|
||||
- @vdblink::math::Transform math::Transform@endlink now includes an
|
||||
@vdblink::math::Transform::isIdentity() isIdentity@endlink predicate
|
||||
and methods to @vdblink::math::Transform::preMult(const Mat4d&) pre-@endlink
|
||||
and @vdblink::math::Transform::postMult(const Mat4d&) postmultiply@endlink
|
||||
by a matrix.
|
||||
and methods <TT>openvdb::math::Transform::preMult(const Mat4d&)</TT> and
|
||||
<TT>openvdb::math::Transform::postMult(const Mat4d&)</TT>
|
||||
to pre- and postmultiply by a matrix.
|
||||
- Modified the @link NodeMasks.h node mask@endlink classes to permit
|
||||
octree-like tree configurations (i.e., with a branching factor of two)
|
||||
and to use 64-bit operations instead of 32-bit operations.
|
||||
@@ -4331,7 +4330,7 @@ API changes:
|
||||
faster for high-resolution meshes where the polygon/voxel size ratio
|
||||
is small.
|
||||
- Added @vdblink::createLevelSet() createLevelSet@endlink and
|
||||
@vdblink::createLevelSetSphere() createLevelSetSphere@endlink
|
||||
@vdblink::tools::createLevelSetSphere() createLevelSetSphere@endlink
|
||||
factory functions for level set grids.
|
||||
- @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink is now faster
|
||||
for trees of height 2, 3 and 4 (the latter is the default), and it now
|
||||
@@ -4444,7 +4443,7 @@ API changes:
|
||||
- Added @vdblink::tools::compReplace() tools::compReplace@endlink,
|
||||
which copies the active values of one grid into another, and added
|
||||
a "Replace A With Active B" mode to the Combine SOP.
|
||||
- @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink
|
||||
- @c openvdb::Grid::signedFloodFill()
|
||||
no longer enters an infinite loop when filling an empty grid.
|
||||
- Fixed a bug in the particle to level set converter that sometimes
|
||||
produced level sets with holes, and fixed a bug in the SOP that
|
||||
@@ -4707,8 +4706,8 @@ API changes:
|
||||
math library and a method to return the
|
||||
@vdblink::math::Mat3::trace trace@endlink of a @c Mat3.
|
||||
- Fixed a bug in the
|
||||
@vdblink::tree::ValueAccessor::ValueAccessor(const ValueAccessor&)
|
||||
ValueAccessor@endlink copy constructor that caused the copy to reference
|
||||
<TT>openvdb::tree::ValueAccessor::ValueAccessor(const ValueAccessor&)</TT>
|
||||
copy constructor that caused the copy to reference
|
||||
the original.
|
||||
- Fixed a bug in @vdblink::tree::RootNode::setActiveState()
|
||||
RootNode::setActiveState@endlink that caused a crash
|
||||
@@ -4716,7 +4715,7 @@ API changes:
|
||||
- Added a @c Tree::pruneLevelSet method that is similar to but faster than
|
||||
Tree::pruneInactive() for level set grids.
|
||||
- Added fast leaf node voxel access
|
||||
@vdblink::tree::LeafNode::getValue(Index) const methods@endlink
|
||||
<TT>openvdb::tree::LeafNode::getValue(Index)</TT> methods
|
||||
that index by linear offset (as returned by
|
||||
@vdblink::tree::LeafNode::ValueOnIter::pos() ValueIter::pos@endlink)
|
||||
instead of by @ijk coordinates.
|
||||
@@ -4769,7 +4768,7 @@ API changes:
|
||||
just the active states of voxels from one grid with those of
|
||||
another grid of a possibly different type.
|
||||
- Fixed an incorrect scale factor in the Laplacian diffusion
|
||||
@vdblink::tools::Filter::laplacian() filter@endlink.
|
||||
@c openvdb::tools::Filter::laplacian() filter.
|
||||
- Fixed a bug in @vdblink::tree::Tree::merge() Tree::merge@endlink
|
||||
that could leave a tree with invalid value accessors.
|
||||
- Added @vdblink::tree::TreeValueIteratorBase::setActiveState()
|
||||
@@ -4804,7 +4803,7 @@ API changes:
|
||||
@par
|
||||
<B>Version 0.92.0</B> - <I>August 23 2012</I>
|
||||
- Added a Laplacian diffusion
|
||||
@vdblink::tools::Filter::laplacian() filter@endlink.
|
||||
@c openvdb::tools::Filter::laplacian() filter.
|
||||
- Fixed a bug in the initialization of the sparse contour tracer
|
||||
that caused mesh-to-volume conversion to fail in certain cases.
|
||||
- Fixed a bug in the curvature stencil that caused mean curvature
|
||||
@@ -4832,9 +4831,8 @@ API changes:
|
||||
method that replaces a grid’s tree with a new, empty tree of the
|
||||
correct type.
|
||||
- Fixed a crash in
|
||||
@vdblink::tree::Tree::setValueOff(const Coord& xyz, const ValueType& value)
|
||||
Tree::setValueOff@endlink when the new value was equal to the
|
||||
background value.
|
||||
<TT>openvdb::tree::Tree::setValueOff(const Coord& xyz, const ValueType& value)</TT>
|
||||
when the new value was equal to the background value.
|
||||
- Fixed bugs in Tree::prune() that could result in output tiles with
|
||||
incorrect active states.
|
||||
- Added @c librt to the link dependencies to address build failures
|
||||
|
||||
+38
-28
@@ -36,7 +36,7 @@ Reference Platform, but for those that do, their specified versions are
|
||||
|
||||
Component | Requirements | Optional
|
||||
----------------------- | ----------------------------------------------- | --------
|
||||
OpenVDB Core Library | CMake, C++17 compiler, TBB::tbb | Blosc, ZLib, Log4cplus, Imath::Imath, Boost::iostream
|
||||
OpenVDB Core Library | CMake, C++17 compiler, TBB::tbb | Blosc, ZLib, Log4cplus, Imath::Imath (included), Boost::iostream, VCL (included)
|
||||
OpenVDB Print | Core Library dependencies | -
|
||||
OpenVDB LOD | Core Library dependencies | -
|
||||
OpenVDB Render | Core Library dependencies | OpenEXR, Imath::Imath, libpng
|
||||
@@ -45,41 +45,51 @@ 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, numpy* | -
|
||||
OpenVDB Documentation | Doxygen | -
|
||||
OpenVDB Documentation | Doxygen, Doxygen Awesome (included) | -
|
||||
|
||||
- @b * GLEW is only required for building the vdb_view binary on Windows.
|
||||
- @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.
|
||||
|
||||
Note that OpenVDB comes bundled with the following dependencies. Please see
|
||||
their respective license documentation in `ext/THIRD-PARTY.md` for license
|
||||
information.
|
||||
|
||||
- Doxygen Awesome
|
||||
- Imath::Imath
|
||||
- VCL
|
||||
|
||||
@subsection depDependencyTable Dependency Table
|
||||
|
||||
Package | Minimum | Recommended | Description | apt-get | Homebrew | Source
|
||||
-------------- | ------- | ----------- | ----------------------------------------------------------------- | ------- | -------- | ------
|
||||
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.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.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.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
|
||||
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.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.14.0 | Documentation generation from C++ | Y | Y | http://www.doxygen.nl
|
||||
Package | Minimum | Recommended | Description | apt-get | Homebrew | Source
|
||||
--------------- | ------- | ----------- | ----------------------------------------------------------------- | ------- | -------- | ------
|
||||
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.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.82 | >= 1.85 | Components: iostreams | Y | Y | https://www.boost.org
|
||||
VCL | 2.02.0 | >= 2.02.0 | x86 intrinsics and SIMD class wrappers and | Y | Y | https://github.com/vectorclass/version2
|
||||
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.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
|
||||
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.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.14.0 | Documentation generation from C++ | Y | Y | http://www.doxygen.nl
|
||||
Doxygen Awesome | 2.0.0 | >= 2.0.0 | Documentation generation from C++ | Y | Y | https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
- @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.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
* **Does NanoVDB depend on and require CUDA?**
|
||||
|
||||
No, NanoVDB is by design agnostic and works with most graphics APIs, e.g. OpenGL, OpenCL, OptiX, CUDA, HLSL, GLSL, DirectX, and even the CPU! The data structure itself (defined in [NanoVDB.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/NanoVDB.h) and [PNanoVDB.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/PNanoVDB.h)) only depends on C++11 or C99. However, some of the auxiliary math classes are optionally optimized with intrinsic CUDA calls (e.g. fma in Vec3) and some of the [Allocator](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/util/CudaDeviceBuffer.h) uses CUDA for explicit memory management. It should be straightforward to replace (or remove) these CUDA dependencies if you so desire.
|
||||
No, NanoVDB is by design agnostic and works with most graphics APIs, e.g. OpenGL, OpenCL, OptiX, CUDA, HLSL, GLSL, DirectX, and even the CPU! The data structure itself (defined in [NanoVDB.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/NanoVDB.h) and [PNanoVDB.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/PNanoVDB.h)) only depends on C++11 or C99. However, some of the auxiliary math classes are optionally optimized with intrinsic CUDA calls (e.g. fma in Vec3) and the [DeviceBuffer](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/cuda/DeviceBuffer.h) allocator uses CUDA for explicit memory management. It should be straightforward to replace (or remove) these CUDA dependencies if you so desire.
|
||||
|
||||
* **How does the data structure of NanoVDB compare to that of OpenVDB?**
|
||||
|
||||
|
||||
+25
-141
@@ -1,154 +1,38 @@
|
||||
# NanoVDB Hello World Examples {#NanoVDB_HelloWorld}
|
||||
|
||||
[The first example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_openvdb_to_nanovdb_accessor/openvdb_to_nanovdb_accessor.cc) shows how to convert an OpenVDB level set sphere into a NanoVDB level set, use accessors to print out multiple values from both grids, and save the NanoVDB grid to file. Note that **this example depends on both OpenVDB and NanoVDB**.
|
||||
The example snippets below are pulled directly from compiled example source
|
||||
files via Doxygen's `\snippet` mechanism, so the code shown here is always
|
||||
in sync with what is built and tested in CI. Enable
|
||||
`-DNANOVDB_BUILD_EXAMPLES=ON` to build them locally.
|
||||
|
||||
```cpp
|
||||
#include <openvdb/tools/LevelSetSphere.h> // replace with your own dependencies for generating the OpenVDB grid
|
||||
#include <nanovdb/util/CreateNanoGrid.h> // converter from OpenVDB to NanoVDB (includes NanoVDB.h and GridManager.h)
|
||||
#include <nanovdb/util/IO.h>
|
||||
## Convert an OpenVDB grid to NanoVDB and use accessors
|
||||
|
||||
// Convert an openvdb level set sphere into a nanovdb, use accessors to print out multiple values from both
|
||||
// grids and save the NanoVDB grid to file.
|
||||
// Note, main depends on BOTH OpenVDB and NanoVDB.
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
// Create an OpenVDB grid (here a level set surface but replace this with your own code)
|
||||
auto srcGrid = openvdb::tools::createLevelSetSphere<openvdb::FloatGrid>(100.0f, openvdb::Vec3f(0.0f), 1.0f);
|
||||
[The first example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_openvdb_to_nanovdb_accessor/openvdb_to_nanovdb_accessor.cc)
|
||||
shows how to convert an OpenVDB level set sphere into a NanoVDB level set, use
|
||||
accessors to print out multiple values from both grids, and save the NanoVDB
|
||||
grid to file. Note that **this example depends on both OpenVDB and NanoVDB**.
|
||||
|
||||
// Convert the OpenVDB grid, srcGrid, into a NanoVDB grid handle.
|
||||
auto handle = nanovdb::createNanoGrid(*srcGrid);
|
||||
\snippet nanovdb/nanovdb/examples/ex_openvdb_to_nanovdb_accessor/openvdb_to_nanovdb_accessor.cc openvdb_to_nanovdb_accessor
|
||||
|
||||
// Define a (raw) pointer to the NanoVDB grid on the host. Note we match the value type of the srcGrid!
|
||||
auto* dstGrid = handle.grid<float>();
|
||||
## Read a NanoVDB grid from a file (host only)
|
||||
|
||||
if (!dstGrid)
|
||||
throw std::runtime_error("GridHandle does not contain a grid with value type float");
|
||||
[The second example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor/read_nanovdb_sphere_accessor.cc)
|
||||
reads a NanoVDB grid from a file (the one saved in the previous example) and
|
||||
prints out multiple values. Note that **this example does not depend on
|
||||
OpenVDB (nor CUDA), only NanoVDB**.
|
||||
|
||||
// Get accessors for the two grids. Note that accessors only accelerate repeated access!
|
||||
auto dstAcc = dstGrid->getAccessor();
|
||||
auto srcAcc = srcGrid->getAccessor();
|
||||
\snippet nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor/read_nanovdb_sphere_accessor.cc read_nanovdb_sphere_accessor
|
||||
|
||||
// Access and print out a cross-section of the narrow-band level set from the two grids
|
||||
for (int i = 97; i < 104; ++i) {
|
||||
printf("(%3i,0,0) OpenVDB cpu: % -4.2f, NanoVDB cpu: % -4.2f\n", i, srcAcc.getValue(openvdb::Coord(i, 0, 0)), dstAcc.getValue(nanovdb::Coord(i, 0, 0)));
|
||||
}
|
||||
## Read a NanoVDB grid and access it on the CPU and GPU
|
||||
|
||||
nanovdb::io::writeGrid("data/sphere.nvdb", handle); // Write the NanoVDB grid to file and throw if writing fails
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
[The third example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda.cu)
|
||||
reads a NanoVDB grid from a file (the one saved in the first example) and
|
||||
prints out multiple values on both the CPU and GPU. Note that **this example
|
||||
does NOT depend on OpenVDB, only NanoVDB and CUDA**.
|
||||
|
||||
[The second example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor/read_nanovdb_sphere_accessor.cc) reads a NanoVDB grid from a file (the one saved in the previous example) and prints out multiple values. Note that **this example does not depend on OpenVDB (nor CUDA), only NanoVDB**.
|
||||
\snippet nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda.cu read_nanovdb_sphere_accessor_cuda
|
||||
|
||||
```cpp
|
||||
#include <nanovdb/util/IO.h> // this is required to read (and write) NanoVDB files on the host
|
||||
NVCC requires the CUDA kernel to be defined in
|
||||
[a separate .cu file](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda_kernel.cu):
|
||||
|
||||
/// @brief Read a NanoVDB grid from a file and print out multiple values.
|
||||
///
|
||||
/// @note Note This example does NOT depend on OpenVDB (nor CUDA), only NanoVDB.
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
auto handle = nanovdb::io::readGrid("data/sphere.nvdb"); // reads first grid from file
|
||||
|
||||
auto* grid = handle.grid<float>(); // get a (raw) pointer to a NanoVDB grid of value type float
|
||||
|
||||
if (!grid)
|
||||
throw std::runtime_error("File did not contain a grid with value type float");
|
||||
|
||||
auto acc = grid->getAccessor(); // create an accessor for fast access to multiple values
|
||||
for (int i = 97; i < 104; ++i) {
|
||||
printf("(%3i,0,0) NanoVDB cpu: % -4.2f\n", i, acc.getValue(nanovdb::Coord(i, 0, 0)));
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
[The third example](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda.cc) reads a NanoVDB grid from a file (the one saved in the first example) and prints out multiple values on both the CPU and GPU. Note that **this example does NOT depend on OpenVDB, only NanoVDB and CUDA**.
|
||||
|
||||
```cpp
|
||||
#include <nanovdb/util/IO.h> // this is required to read (and write) NanoVDB files on the host
|
||||
#include <nanovdb/util/cuda/CudaDeviceBuffer.h> // required for CUDA memory management
|
||||
|
||||
extern "C" void launch_kernels(const nanovdb::NanoGrid<float>*,
|
||||
const nanovdb::NanoGrid<float>*,
|
||||
cudaStream_t stream);
|
||||
|
||||
/// @brief Read a NanoVDB grid from a file and print out multiple values on both the cpu and gpu.
|
||||
///
|
||||
/// @note Note This example does NOT depend on OpenVDB, only NanoVDB and CUDA.
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
// returns a GridHandle using CUDA for memory management.
|
||||
auto handle = nanovdb::io::readGrid<nanovdb::CudaDeviceBuffer>("data/sphere.nvdb");
|
||||
|
||||
cudaStream_t stream; // Create a CUDA stream to allow for asynchronous copy of pinned CUDA memory.
|
||||
cudaStreamCreate(&stream);
|
||||
|
||||
handle.deviceUpload(stream, false); // Copy the NanoVDB grid to the GPU asynchronously
|
||||
|
||||
auto* cpuGrid = handle.grid<float>(); // get a (raw) pointer to a NanoVDB grid of value type float on the CPU
|
||||
auto* deviceGrid = handle.deviceGrid<float>(); // get a (raw) pointer to a NanoVDB grid of value type float on the GPU
|
||||
|
||||
if (!deviceGrid || !cpuGrid)
|
||||
throw std::runtime_error("GridHandle did not contain a grid with value type float");
|
||||
|
||||
launch_kernels(deviceGrid, cpuGrid, stream); // Call a host method to print a grid values on both the CPU and GPU
|
||||
|
||||
cudaStreamDestroy(stream); // Destroy the CUDA stream
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
NVCC requires the CUDA kernel to be defined in [a separate .cu file](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda.cu):
|
||||
|
||||
```cpp
|
||||
#include <nanovdb/NanoVDB.h> // this defined the core tree data structure of NanoVDB accessible on both the host and device
|
||||
#include <stdio.h> // for printf
|
||||
|
||||
// This is called by the host only
|
||||
void cpu_kernel(const nanovdb::NanoGrid<float>* cpuGrid)
|
||||
{
|
||||
auto cpuAcc = cpuGrid->getAccessor();
|
||||
for (int i = 97; i < 104; ++i) {
|
||||
printf("(%3i,0,0) NanoVDB cpu: % -4.2f\n", i, cpuAcc.getValue(nanovdb::Coord(i, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// This is called by the device only
|
||||
__global__ void gpu_kernel(const nanovdb::NanoGrid<float>* deviceGrid)
|
||||
{
|
||||
if (threadIdx.x > 6)
|
||||
return;
|
||||
int i = 97 + threadIdx.x;
|
||||
auto gpuAcc = deviceGrid->getAccessor();
|
||||
printf("(%3i,0,0) NanoVDB gpu: % -4.2f\n", i, gpuAcc.getValue(nanovdb::Coord(i, 0, 0)));
|
||||
}
|
||||
|
||||
// This is called by the client code on the host
|
||||
extern "C" void launch_kernels(const nanovdb::NanoGrid<float>* deviceGrid,
|
||||
const nanovdb::NanoGrid<float>* cpuGrid,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
// Launch the device kernel asynchronously
|
||||
gpu_kernel<<<1, 64, 0, stream>>>(deviceGrid);
|
||||
|
||||
// Launch the host "kernel" (synchronously)
|
||||
cpu_kernel(cpuGrid);
|
||||
}
|
||||
```
|
||||
\snippet nanovdb/nanovdb/examples/ex_read_nanovdb_sphere_accessor_cuda/read_nanovdb_sphere_accessor_cuda_kernel.cu read_nanovdb_sphere_accessor_cuda_kernel
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
* [math/HDDA.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/math/HDDA.h) HDDA related.
|
||||
* [math/SampleFromVoxels.h](https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/math/SampleFromVoxels.h) interpolation.
|
||||
|
||||
```bash
|
||||
foo@bar:~$ tree
|
||||
```
|
||||
$ tree
|
||||
.
|
||||
├── CMakeLists.txt
|
||||
├── cmd
|
||||
@@ -175,4 +175,5 @@ foo@bar:~$ tree
|
||||
├── SampleFromVoxels.h
|
||||
├── Stencils.h
|
||||
├── Timer.h
|
||||
└── Util.h
|
||||
└── Util.h
|
||||
```
|
||||
|
||||
@@ -76,6 +76,32 @@ SOFTWARE.
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
Apache 2.0
|
||||
|
||||
* VCL (c) Copyright 2012-2022 Agner Fog.
|
||||
https://github.com/vectorclass/version2
|
||||
|
||||
OpenVDB includes a copy of vectorclass for x86 SIMD intrinsic usage. Usage
|
||||
of VCL is disabled by default and requires the VCL headers to be shipped with
|
||||
OpenVDB installations. This behaviour and usage of VCL can be controlled
|
||||
during OpenVDB configuration. See the full LICENSE terms in:
|
||||
|
||||
vcl/openvdb/ext/vcl/LICENSE
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
Public domain
|
||||
|
||||
* The simplex noise implementation included in OpenVDB AX is Copyright (c)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#ifndef OPENVDB_MATH_HALF_HAS_BEEN_INCLUDED
|
||||
#define OPENVDB_MATH_HALF_HAS_BEEN_INCLUDED
|
||||
|
||||
/// @file half.h
|
||||
/// @file Half.h
|
||||
/// The half type is a 16-bit floating number, compatible with the
|
||||
/// IEEE 754-2008 binary16 type.
|
||||
///
|
||||
@@ -21,6 +21,7 @@
|
||||
/// We assume that a float, f, is an IEEE 754 single-precision
|
||||
/// floating point number, whose bits are arranged as follows:
|
||||
///
|
||||
/// <pre>
|
||||
/// 31 (msb)
|
||||
/// |
|
||||
/// | 30 23
|
||||
@@ -30,28 +31,36 @@
|
||||
/// X XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXX
|
||||
///
|
||||
/// s e m
|
||||
/// </pre>
|
||||
///
|
||||
/// S is the sign-bit, e is the exponent and m is the significand.
|
||||
///
|
||||
/// If e is between 1 and 254, f is a normalized number:
|
||||
///
|
||||
/// <pre>
|
||||
/// s e-127
|
||||
/// f = (-1) * 2 * 1.m
|
||||
/// </pre>
|
||||
///
|
||||
/// If e is 0, and m is not zero, f is a denormalized number:
|
||||
///
|
||||
/// <pre>
|
||||
/// s -126
|
||||
/// f = (-1) * 2 * 0.m
|
||||
/// </pre>
|
||||
///
|
||||
/// If e and m are both zero, f is zero:
|
||||
///
|
||||
/// <pre>
|
||||
/// f = 0.0
|
||||
/// </pre>
|
||||
///
|
||||
/// If e is 255, f is an "infinity" or "not a number" (NAN),
|
||||
/// depending on whether m is zero or not.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// <pre>
|
||||
/// 0 00000000 00000000000000000000000 = 0.0
|
||||
/// 0 01111110 00000000000000000000000 = 0.5
|
||||
/// 0 01111111 00000000000000000000000 = 1.0
|
||||
@@ -62,11 +71,13 @@
|
||||
/// 1 11111111 00000000000000000000000 = -infinity
|
||||
/// 0 11111111 10000000000000000000000 = NAN
|
||||
/// 1 11111111 11111111111111111111111 = NAN
|
||||
/// </pre>
|
||||
///
|
||||
/// **Representation of a 16-bit half:**
|
||||
///
|
||||
/// Here is the bit-layout for a half number, h:
|
||||
///
|
||||
/// <pre>
|
||||
/// 15 (msb)
|
||||
/// |
|
||||
/// | 14 10
|
||||
@@ -76,28 +87,36 @@
|
||||
/// X XXXXX XXXXXXXXXX
|
||||
///
|
||||
/// s e m
|
||||
/// </pre>
|
||||
///
|
||||
/// S is the sign-bit, e is the exponent and m is the significand.
|
||||
///
|
||||
/// If e is between 1 and 30, h is a normalized number:
|
||||
///
|
||||
/// <pre>
|
||||
/// s e-15
|
||||
/// h = (-1) * 2 * 1.m
|
||||
/// </pre>
|
||||
///
|
||||
/// If e is 0, and m is not zero, h is a denormalized number:
|
||||
///
|
||||
/// <pre>
|
||||
/// S -14
|
||||
/// h = (-1) * 2 * 0.m
|
||||
/// </pre>
|
||||
///
|
||||
/// If e and m are both zero, h is zero:
|
||||
///
|
||||
/// <pre>
|
||||
/// h = 0.0
|
||||
/// </pre>
|
||||
///
|
||||
/// If e is 31, h is an "infinity" or "not a number" (NAN),
|
||||
/// depending on whether m is zero or not.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// <pre>
|
||||
/// 0 00000 0000000000 = 0.0
|
||||
/// 0 01110 0000000000 = 0.5
|
||||
/// 0 01111 0000000000 = 1.0
|
||||
@@ -108,13 +127,14 @@
|
||||
/// 1 11111 0000000000 = -infinity
|
||||
/// 0 11111 1000000000 = NAN
|
||||
/// 1 11111 1111111111 = NAN
|
||||
/// </pre>
|
||||
///
|
||||
/// **Conversion via Lookup Table:**
|
||||
///
|
||||
/// Converting from half to float is performed by default using a
|
||||
/// lookup table. There are only 65,536 different half numbers; each
|
||||
/// of these numbers has been converted and stored in a table pointed
|
||||
/// to by the ``imath_half_to_float_table`` pointer.
|
||||
/// to by the `imath_half_to_float_table` pointer.
|
||||
///
|
||||
/// Prior to Imath v3.1, conversion from float to half was
|
||||
/// accomplished with the help of an exponent look table, but this is
|
||||
@@ -135,30 +155,25 @@
|
||||
/// 65,536-entry lookup table.
|
||||
///
|
||||
/// The lookup table symbol is included in the compilation even if
|
||||
/// ``IMATH_HALF_USE_LOOKUP_TABLE`` is false, because application code
|
||||
/// using the exported ``half.h`` may choose to enable the use of the table.
|
||||
/// `IMATH_HALF_USE_LOOKUP_TABLE` is false, because application code
|
||||
/// using the exported `half.h` may choose to enable the use of the table.
|
||||
///
|
||||
/// An implementation can eliminate the table from compilation by
|
||||
/// defining the ``IMATH_HALF_NO_LOOKUP_TABLE`` preprocessor symbol.
|
||||
/// Simply add:
|
||||
/// defining the `IMATH_HALF_NO_LOOKUP_TABLE` preprocessor symbol.
|
||||
/// Simply add `#define IMATH_HALF_NO_LOOKUP_TABLE` before including
|
||||
/// `half.h`, or define the symbol on the compile command line.
|
||||
///
|
||||
/// #define IMATH_HALF_NO_LOOKUP_TABLE
|
||||
///
|
||||
/// before including ``half.h``, or define the symbol on the compile
|
||||
/// command line.
|
||||
///
|
||||
/// Furthermore, an implementation wishing to receive ``FE_OVERFLOW``
|
||||
/// and ``FE_UNDERFLOW`` floating point exceptions when converting
|
||||
/// Furthermore, an implementation wishing to receive `FE_OVERFLOW`
|
||||
/// and `FE_UNDERFLOW` floating point exceptions when converting
|
||||
/// float to half by the bit-shift algorithm can define the
|
||||
/// preprocessor symbol ``IMATH_HALF_ENABLE_FP_EXCEPTIONS`` prior to
|
||||
/// including ``half.h``:
|
||||
///
|
||||
/// #define IMATH_HALF_ENABLE_FP_EXCEPTIONS
|
||||
/// preprocessor symbol `IMATH_HALF_ENABLE_FP_EXCEPTIONS` (that is,
|
||||
/// `#define IMATH_HALF_ENABLE_FP_EXCEPTIONS`) prior to including `half.h`.
|
||||
///
|
||||
/// **Conversion Performance Comparison:**
|
||||
///
|
||||
/// Testing on a Core i9, the timings are approximately:
|
||||
///
|
||||
/// <pre>
|
||||
/// half to float
|
||||
/// - table: 0.71 ns / call
|
||||
/// - no table: 1.06 ns / call
|
||||
@@ -168,6 +183,7 @@
|
||||
/// - original: 5.2 ns / call
|
||||
/// - no exp table + opt: 1.27 ns / call
|
||||
/// - f16c: 0.45 ns / call
|
||||
/// </pre>
|
||||
///
|
||||
/// **Note:** the timing above depends on the distribution of the
|
||||
/// floats in question.
|
||||
@@ -206,6 +222,12 @@ namespace internal {
|
||||
// Thus any attempt to link two versions of OpenVDB with different
|
||||
// namespaces will clash due to redefinition with a new type.
|
||||
// The default was not to use a lookup table.
|
||||
//
|
||||
/// @cond
|
||||
/// These internal half-float configuration and limit macros are excluded from
|
||||
/// the docs: the `half` type itself lives in the (undocumented) `internal`
|
||||
/// namespace, and documenting the `#define`s makes Doxygen auto-link the
|
||||
/// literal `#define` token (an unresolvable "link to 'define'" warning).
|
||||
#undef IMATH_HALF_USE_LOOKUP_TABLE
|
||||
#define IMATH_HALF_NO_LOOKUP_TABLE
|
||||
|
||||
@@ -227,7 +249,7 @@ namespace internal {
|
||||
#define VDB_HALF_MIN 6.10351562e-05f
|
||||
/// Largest positive half
|
||||
#define VDB_HALF_MAX 65504.0f
|
||||
/// Smallest positive e for which ``half(1.0 + e) != half(1.0)``
|
||||
/// Smallest positive e for which `half(1.0 + e) != half(1.0)`
|
||||
#define VDB_HALF_EPSILON 0.00097656f
|
||||
#else
|
||||
/// Smallest positive denormalized half
|
||||
@@ -238,7 +260,7 @@ namespace internal {
|
||||
#define VDB_HALF_MIN 6.10351562e-05f
|
||||
/// Largest positive half
|
||||
#define VDB_HALF_MAX 65504.0
|
||||
/// Smallest positive e for which ``half(1.0 + e) != half(1.0)``
|
||||
/// Smallest positive e for which `half(1.0 + e) != half(1.0)`
|
||||
#define VDB_HALF_EPSILON 0.00097656
|
||||
#endif
|
||||
|
||||
@@ -246,19 +268,19 @@ namespace internal {
|
||||
#define VDB_HALF_MANT_DIG 11
|
||||
/// Number of base 10 digits that can be represented without change:
|
||||
///
|
||||
/// ``floor( (HALF_MANT_DIG - 1) * log10(2) ) => 3.01... -> 3``
|
||||
/// `floor( (HALF_MANT_DIG - 1) * log10(2) ) => 3.01... -> 3`
|
||||
#define VDB_HALF_DIG 3
|
||||
/// Number of base-10 digits that are necessary to uniquely represent
|
||||
/// all distinct values:
|
||||
///
|
||||
/// ``ceil(HALF_MANT_DIG * log10(2) + 1) => 4.31... -> 5``
|
||||
/// `ceil(HALF_MANT_DIG * log10(2) + 1) => 4.31... -> 5`
|
||||
#define VDB_HALF_DECIMAL_DIG 5
|
||||
/// Base of the exponent
|
||||
#define VDB_HALF_RADIX 2
|
||||
/// Minimum negative integer such that ``HALF_RADIX`` raised to the power
|
||||
/// Minimum negative integer such that `HALF_RADIX` raised to the power
|
||||
/// of one less than that integer is a normalized half
|
||||
#define VDBB_HALF_DENORM_MIN_EXP -13
|
||||
/// Maximum positive integer such that ``HALF_RADIX`` raised to the power
|
||||
/// Maximum positive integer such that `HALF_RADIX` raised to the power
|
||||
/// of one less than that integer is a normalized half
|
||||
#define VDB_HALF_MAX_EXP 16
|
||||
/// Minimum positive integer such that 10 raised to that power is a
|
||||
@@ -267,6 +289,7 @@ namespace internal {
|
||||
/// Maximum positive integer such that 10 raised to that power is a
|
||||
/// normalized half
|
||||
#define VDB_HALF_MAX_10_EXP 4
|
||||
/// @endcond
|
||||
|
||||
/// a type for both C-only programs and C++ to use the same utilities
|
||||
typedef union imath_half_uif
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
Copyright 2012-2019 Agner Fog.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,14 @@
|
||||
# version2
|
||||
Vector Class Library, latest version
|
||||
|
||||
This is a C++ class library for using the Single Instruction Multiple Data (SIMD) instructions to improve performance on modern microprocessors with the x86 or x86/64 instruction set on Windows, Linux, and Mac platforms. There are no plans to support ARM or other instruction sets.
|
||||
|
||||
[Latest release](https://github.com/vectorclass/version2/releases)
|
||||
|
||||
[Download manual](https://github.com/vectorclass/manual/raw/master/vcl_manual.pdf)
|
||||
|
||||
[Add-on packages for particular applications](https://github.com/vectorclass/add-on)
|
||||
|
||||
[Getting-started video.](https://www.youtube.com/watch?v=TKjYdLIMTrI) Video blogger Christopher Rose has made this nice video telling how to get started with the Vector Class Library.
|
||||
|
||||
**Help:** You may ask for programming help on [StackOverflow](https://stackoverflow.com) using the tag vector-class-library.
|
||||
@@ -0,0 +1,244 @@
|
||||
Change log for Vector class library
|
||||
-----------------------------------
|
||||
|
||||
2026-04-14 version 2.02.03
|
||||
* conversions Vec8h <-> Vec8f and Vec16h <-> Vec16f converted to templates in vectorfp16e.h
|
||||
|
||||
2023-07-04 version 2.02.02
|
||||
* remove various MS compiler warnings
|
||||
|
||||
2022-06-03 version 2.02.01
|
||||
* minor bug fixes and updates
|
||||
|
||||
2022-07-20 version 2.02.00
|
||||
* support half precision floating point vectors
|
||||
* new trigonometric functions sinpi, cospi, sincospi, tanpi
|
||||
* floating point modulo and remainder functions
|
||||
* functions extend_z and concatenate2
|
||||
* functions compress_saturated with one integer parameter
|
||||
* support for Intel compiler
|
||||
* fix bug for NAN inputs in operator > and >=
|
||||
* fix bug for underflow in pow function
|
||||
* fix unsafe optimization in store_partial function
|
||||
* new add-on package with container class and matrix class templates
|
||||
* major improvements in add-on package 'complex'
|
||||
* minor improvements in add-on packages 'random' and 'vector3d'
|
||||
|
||||
2021-08-18 version 2.01.04
|
||||
* fix bug in elementtype for Vec8uq
|
||||
* improved optimization of permute functions in MS compiler by using constexpr
|
||||
|
||||
2020-11-04 version 2.01.03
|
||||
* fix overflow in sin, cos, tan for large x
|
||||
* fix bug in is_nan for instruction sets prior to AVX
|
||||
* warning for MS compiler versions with poor support for AVX512
|
||||
|
||||
2020-04-11 version 2.01.02
|
||||
* only minor fixes
|
||||
|
||||
2020-02-25 version 2.01.01
|
||||
* added function store_nt
|
||||
* New dispatch_example1.cpp, dispatch_example2.cpp
|
||||
|
||||
2019-11-23 version 2.01.00
|
||||
* problem with performance of permute and blend functions fixed by avoiding
|
||||
unions in constexpr functions
|
||||
|
||||
2019-10-31 version 2.00.02
|
||||
* bug fix in permute function
|
||||
* is_nan function improved
|
||||
* templates constant4ui etc. improved
|
||||
|
||||
2019-08-02 version 2.00
|
||||
Derived from version 1.40
|
||||
* use C++17
|
||||
* use compact boolean vectors of all sizes if AVX512VL is enabled
|
||||
* permute and blend functions improved, using C++17 metaprogramming features
|
||||
* deprecated functions removed
|
||||
|
||||
2019-08-02 version 1.40
|
||||
* hosted on github
|
||||
* license changed to Apache 2.0.
|
||||
* added classes Vec64c, Vec64uc, Vec32s, Vec32us
|
||||
* test bench and scripts for automatic testing of VCL
|
||||
* new functions: maximum, minimum, to_float, to_double
|
||||
* conversion of bitfields to boolean vectors with load_bits. This replaces to_vec4ib etc.
|
||||
* shift_bytes_up/down functions changed to templates (old versions deprecated)
|
||||
* removed VECTORMATH define. vectormath_lib.h rewritten. svmlpatch.lib added.
|
||||
* many improvements and bug fixes
|
||||
* renamed functions: round_to_int and round_to_int64 functions renamed to roundi,
|
||||
* renamed functions: the type letter is removed from all permute and blend functions,
|
||||
e.g. permute4f renamed to permute4.
|
||||
These changes are made to facilitate generic template programming
|
||||
* renamed functions: to_Vec.. Replaced by load_bits member functions
|
||||
* deprecated functions: set_bit, get_bit
|
||||
* deprecated bit vector classes: Vec128b, Vec256b, Vec512b
|
||||
|
||||
2017-07-27 version 1.30
|
||||
* fixed bug in permute8f for a particular combination of indexes
|
||||
|
||||
2017-05-10 version 1.29
|
||||
* Reversed Apple Clang patch in version 1.28 because the problem has reoccurred in
|
||||
later versions of Clang
|
||||
|
||||
2017-05-02 version 1.28
|
||||
* Fixed problem with Apple Clang version 6.2 in vectorf128.h
|
||||
* Fixed return type for Vec8sb operator > (Vec8us, Vec8us)
|
||||
* cpuid function modified in instrset_detect.cpp
|
||||
|
||||
2017-02-19 version 1.27
|
||||
* fixed problem with scatter functions in MS Visual Studio
|
||||
|
||||
2016-12-21 version 1.26
|
||||
* added constant4ui template
|
||||
* fixed error for complexvec.h with clang
|
||||
* fixed error in vectormath_exp.h for MAX_VECTOR_SIZE < 512
|
||||
|
||||
2016-11-25 version 1.25
|
||||
* scatter functions
|
||||
* new functions to_float for unsigned integer vectors
|
||||
* instrset_detect function can detect AVX512VL, AVX512BW, AVX512DQ
|
||||
* functions hasF16C and hasAVX512ER for detecting instruction set extensions
|
||||
* fix bugs in horizontal_and and pow(0,0) for AVX512
|
||||
* functions improved for AVX512 and AVX512VL: pow, approx_recipr,
|
||||
approx_rsqrt
|
||||
* functions improved for AVX512DQ: 64 bit multiplication, to_double,
|
||||
32 and 64 bit rotate_left, round_to_int64, truncate_to_int64
|
||||
* functions improved for AVX512ER: approx_recipr, approx_rsqrt,
|
||||
exponential functions
|
||||
|
||||
2016-10-31 version 1.24
|
||||
* fix bug in Vec8uq constructor in vectori512e.h
|
||||
|
||||
2016-09-27 version 1.23
|
||||
* temporary fix of a problem in Clang version 3.9 inserted in vectorf128.h
|
||||
|
||||
2016-05-03 version 1.22
|
||||
* added optional namespace
|
||||
* fixed problem with decimal.h
|
||||
|
||||
2016-04-24 version 1.21
|
||||
* fix problems with XOP option in gcc
|
||||
* improved horizontal_and/or for sse2
|
||||
* improved Vec2q and Vec4q constructor on Microsoft Visual Studio 2015
|
||||
* removed warnings by gcc option -Wcast-qual
|
||||
|
||||
2015-12-04 version 1.20
|
||||
* round functions: suppress precision exception under SSE4.1 and higher
|
||||
* fix compiler problems with AVX512 multiplication in gcc version 5.1
|
||||
* fix compiler problems with pow function in Microsoft Visual Studio 2015
|
||||
|
||||
2015-11-14 version 1.19
|
||||
* fix various problems with Clang compiler
|
||||
|
||||
2015-09-25 version 1.18
|
||||
* fix compiler error for Vec8s divide_by_i(Vec8s const & x) under Clang compiler
|
||||
* fix error in Vec4d::size() in vectorf256e.h
|
||||
|
||||
2015-07-31 version 1.17
|
||||
* improved operator > for Vec4uq
|
||||
* more special cases in blend4q
|
||||
* nan_code functions made static inline
|
||||
* template parameter BTYPE renamed to BVTYPE in mathematical functions to avoid clash
|
||||
with macro named BTYPE in winnt.h
|
||||
* fixed bug in Vec4db constructor
|
||||
|
||||
2014-10-24 version 1.16
|
||||
* workaround for problem in Clang compiler extended to version 3.09 because not
|
||||
fixed yet by Clang (vectorf128.h line 134)
|
||||
* recognize problem with Apple version of Clang reporting wrong version number
|
||||
* remove various minor problems with Clang
|
||||
* function pow(vector, int) modified to strengthen type checking and avoid compiler warnings
|
||||
* manual discusses dynamic allocation of arrays of vectors
|
||||
* various minor changes
|
||||
|
||||
2014-10-17 version 1.15
|
||||
* added files ranvec1.h and ranvec1.cpp for random number generator
|
||||
* constructors to make boolean vectors from their elements
|
||||
* constructors and = operators to broadcast boolean scalar into boolean vectors
|
||||
* various lookup functions improved
|
||||
* operators &, |, ^, ~, etc. defined for various boolean vectors to avoid converson
|
||||
to integer vectors
|
||||
* nmul_add functions
|
||||
* mul_add etc. moved to main header files
|
||||
* explicit fused multiply-and-add used in math functions to improve performance
|
||||
on compilers that don't automatically insert FMA
|
||||
|
||||
2014-07-24 version 1.14
|
||||
* support for AVX-512f instruction set and 512-bit vectors:
|
||||
Vec16i, Vec16ui, Vec8q, Vec8uq, Vec16f, Vec8d, and corresponding boolean vectors
|
||||
* new define MAX_VECTOR_SIZE, valid values are 128, 256 and 512
|
||||
* added hyperbolic functions sinh, cosh, tanh, asinh, acosh, atanh
|
||||
* size() member function on all vector classes returns the number of elements
|
||||
* functions for conversion between boolean vectors and integer bitfields
|
||||
* extracting an element from a boolean vector now returns a bool, not an int
|
||||
* improved precision in exp2 and exp10 functions
|
||||
* various bug fixes
|
||||
|
||||
2014-05-11 version 1.13
|
||||
* pow function improved
|
||||
* mul_add, mul_sub, mul_sub_x functions
|
||||
* propagation of error codes through nan_code function
|
||||
* "denormal" renamed to "subnormal" everywhere, in accordance with IEEE 754-2008 standard
|
||||
|
||||
2014-04-20 version 1.12
|
||||
* inline implementation of mathematical functions added (vectormath_exp.h vectormath_trig.h
|
||||
vectormath_common.h)
|
||||
* vectormath.h renamed to vectormath_lib.h because a new alternative is added
|
||||
* gather functions with constant indexes
|
||||
* function sign_combine
|
||||
* function pow_const(vector, const int)
|
||||
* function pow_ratio(vector, const int, const int)
|
||||
* functions horizontal_find_first, horizontal_count
|
||||
* function recipr_sqrt removed
|
||||
* functions round_to_int64_limited, truncate_to_int64_limited, to_double_limited
|
||||
* function cubic_root renamed to cbrt
|
||||
* function atan(vector,vector) renamed to atan2
|
||||
* function if_mul
|
||||
* function Vec4i round_to_int(Vec2d)
|
||||
* operator & (float vector, boolean vector)
|
||||
* operator &= (int vector, int vector)
|
||||
* removed constructor Vec128b(int) and Vec256b(int) to avoid implicit conversion
|
||||
* removed signalling nan function
|
||||
* minor improvements in various blend and lookup functions
|
||||
|
||||
2014-03-01 version 1.11
|
||||
* fixed missing unsigned operators >>= in vectori256.h
|
||||
|
||||
2013-10-04 version 1.10
|
||||
* clear distinction between boolean vectors and integer vectors for the sake of
|
||||
compatibility with mask registers in forthcoming AVX512 instruction set
|
||||
* added function if_add
|
||||
* tentative support for clang version 3.3 with workaround for bugs
|
||||
* remove ambiguity for builtin m128i operator == in clang compiler.
|
||||
* problems in clang compiler, bug reports filed at clang
|
||||
(http://llvm.org/bugs/show_bug.cgi?id=17164, 17312)
|
||||
* instrset.h fixes problem with macros named min and max in MS windows.h
|
||||
* workaround problem in MS Visual Studio 11.0. Bug report 735861 and 804274
|
||||
* minor bug fixes
|
||||
|
||||
2013-03-31 version 1.03 beta
|
||||
* bug fix for Vec2d cos (Vec2d const & x), VECTORMATH = 1
|
||||
|
||||
2012-08-01 version 1.02 beta
|
||||
* added file vector3d.h for 3-dimensional vectors
|
||||
* added file complexvec.h for complex numbers and complex vectors
|
||||
* added file quaternion.h for quaternions
|
||||
* added function change_sign for floating point vectors
|
||||
* added operators +, -, *, / between floating point vectors and scalars to remove
|
||||
overloading ambiguity
|
||||
|
||||
2012-07-08 version 1.01 beta
|
||||
* added file decimal.h with Number <-> string conversion functions:
|
||||
bin2bcd, bin2ascii, bin2hex_ascii, ascii2bin
|
||||
* added andnot function for boolean vectors
|
||||
* added functions shift_bytes_up and shift_bytes_down
|
||||
* added operators for unsigned integer vector classes: >>=, &, &&, |, ||, ^, ~
|
||||
* inteldispatchpatch.cpp removed. Use asmlib instead (www.agner.org/optimize/#asmlib)
|
||||
* prefix ++ and -- operators now return a reference, postfix operators return a value
|
||||
* various improvements in permute and blend functions
|
||||
* minor improvement in abs function
|
||||
* added version number to VECTORCLASS_H
|
||||
|
||||
2012-05-30 version 1.00 beta
|
||||
* first public release at www.agner.org
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,828 @@
|
||||
/************************** vector_convert.h *******************************
|
||||
* Author: Agner Fog
|
||||
* Date created: 2014-07-23
|
||||
* Last modified: 2022-07-20
|
||||
* Version: 2.02.00
|
||||
* Project: vector class library
|
||||
* Description:
|
||||
* Header file for conversion between different vector classes with different
|
||||
* sizes. Also includes verious generic template functions.
|
||||
*
|
||||
* (c) Copyright 2012-2022 Agner Fog.
|
||||
* Apache License version 2.0 or later.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef VECTOR_CONVERT_H
|
||||
#define VECTOR_CONVERT_H
|
||||
|
||||
#ifndef VECTORCLASS_H
|
||||
#include "vectorclass.h"
|
||||
#endif
|
||||
|
||||
#if VECTORCLASS_H < 20200
|
||||
#error Incompatible versions of vector class library mixed
|
||||
#endif
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
namespace VCL_NAMESPACE {
|
||||
#endif
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Extend from 128 to 256 bit vectors
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#if INSTRSET >= 8 // AVX2. 256 bit integer vectors
|
||||
|
||||
// sign extend
|
||||
static inline Vec16s extend (Vec16c const a) {
|
||||
return _mm256_cvtepi8_epi16(a);
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec16us extend (Vec16uc const a) {
|
||||
return _mm256_cvtepu8_epi16(a);
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec8i extend (Vec8s const a) {
|
||||
return _mm256_cvtepi16_epi32(a);
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec8ui extend (Vec8us const a) {
|
||||
return _mm256_cvtepu16_epi32(a);
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec4q extend (Vec4i const a) {
|
||||
return _mm256_cvtepi32_epi64(a);
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec4uq extend (Vec4ui const a) {
|
||||
return _mm256_cvtepu32_epi64(a);
|
||||
}
|
||||
|
||||
|
||||
#else // no AVX2. 256 bit integer vectors are emulated
|
||||
|
||||
// sign extend and zero extend functions:
|
||||
static inline Vec16s extend (Vec16c const a) {
|
||||
return Vec16s(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
static inline Vec16us extend (Vec16uc const a) {
|
||||
return Vec16us(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
static inline Vec8i extend (Vec8s const a) {
|
||||
return Vec8i(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
static inline Vec8ui extend (Vec8us const a) {
|
||||
return Vec8ui(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
static inline Vec4q extend (Vec4i const a) {
|
||||
return Vec4q(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
static inline Vec4uq extend (Vec4ui const a) {
|
||||
return Vec4uq(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
#endif // AVX2
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Conversions between float and double
|
||||
*
|
||||
*****************************************************************************/
|
||||
#if INSTRSET >= 7 // AVX. 256 bit float vectors
|
||||
|
||||
// float to double
|
||||
static inline Vec4d to_double (Vec4f const a) {
|
||||
return _mm256_cvtps_pd(a);
|
||||
}
|
||||
|
||||
// double to float
|
||||
static inline Vec4f to_float (Vec4d const a) {
|
||||
return _mm256_cvtpd_ps(a);
|
||||
}
|
||||
|
||||
#else // no AVX2. 256 bit float vectors are emulated
|
||||
|
||||
// float to double
|
||||
static inline Vec4d to_double (Vec4f const a) {
|
||||
Vec2d lo = _mm_cvtps_pd(a);
|
||||
Vec2d hi = _mm_cvtps_pd(_mm_movehl_ps(a, a));
|
||||
return Vec4d(lo,hi);
|
||||
}
|
||||
|
||||
// double to float
|
||||
static inline Vec4f to_float (Vec4d const a) {
|
||||
Vec4f lo = _mm_cvtpd_ps(a.get_low());
|
||||
Vec4f hi = _mm_cvtpd_ps(a.get_high());
|
||||
return _mm_movelh_ps(lo, hi);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Reduce from 256 to 128 bit vectors
|
||||
*
|
||||
*****************************************************************************/
|
||||
#if INSTRSET >= 10 // AVX512VL
|
||||
|
||||
// compress functions. overflow wraps around
|
||||
static inline Vec16c compress (Vec16s const a) {
|
||||
return _mm256_cvtepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec16uc compress (Vec16us const a) {
|
||||
return _mm256_cvtepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec8s compress (Vec8i const a) {
|
||||
return _mm256_cvtepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec8us compress (Vec8ui const a) {
|
||||
return _mm256_cvtepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec4i compress (Vec4q const a) {
|
||||
return _mm256_cvtepi64_epi32(a);
|
||||
}
|
||||
|
||||
static inline Vec4ui compress (Vec4uq const a) {
|
||||
return _mm256_cvtepi64_epi32(a);
|
||||
}
|
||||
|
||||
// compress_saturated functions. overflow saturates
|
||||
static inline Vec16c compress_saturated (Vec16s const a) {
|
||||
return _mm256_cvtsepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec16uc compress_saturated (Vec16us const a) {
|
||||
return _mm256_cvtusepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec8s compress_saturated (Vec8i const a) {
|
||||
return _mm256_cvtsepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec8us compress_saturated (Vec8ui const a) {
|
||||
return _mm256_cvtusepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec4i compress_saturated (Vec4q const a) {
|
||||
return _mm256_cvtsepi64_epi32(a);
|
||||
}
|
||||
|
||||
static inline Vec4ui compress_saturated (Vec4uq const a) {
|
||||
return _mm256_cvtusepi64_epi32(a);
|
||||
}
|
||||
|
||||
|
||||
#else // no AVX512
|
||||
|
||||
// compress functions. overflow wraps around
|
||||
static inline Vec16c compress (Vec16s const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16uc compress (Vec16us const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8s compress (Vec8i const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8us compress (Vec8ui const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec4i compress (Vec4q const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec4ui compress (Vec4uq const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
// compress_saturated functions. overflow saturates
|
||||
static inline Vec16c compress_saturated (Vec16s const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16uc compress_saturated (Vec16us const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8s compress_saturated (Vec8i const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8us compress_saturated (Vec8ui const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec4i compress_saturated (Vec4q const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec4ui compress_saturated (Vec4uq const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
#endif // AVX512
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Reduce from 512 to 256 bit vectors
|
||||
*
|
||||
*****************************************************************************/
|
||||
#if INSTRSET >= 10 // AVX512VL
|
||||
|
||||
// compress_saturated functions. overflow saturates
|
||||
static inline Vec32c compress_saturated (Vec32s const a) {
|
||||
return _mm512_cvtsepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec32uc compress_saturated (Vec32us const a) {
|
||||
return _mm512_cvtusepi16_epi8(a);
|
||||
}
|
||||
|
||||
static inline Vec16s compress_saturated (Vec16i const a) {
|
||||
return _mm512_cvtsepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec16us compress_saturated (Vec16ui const a) {
|
||||
return _mm512_cvtusepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec8i compress_saturated (Vec8q const a) {
|
||||
return _mm512_cvtsepi64_epi32(a);
|
||||
}
|
||||
|
||||
static inline Vec8ui compress_saturated (Vec8uq const a) {
|
||||
return _mm512_cvtusepi64_epi32(a);
|
||||
}
|
||||
|
||||
#else // no AVX512
|
||||
|
||||
// compress_saturated functions. overflow saturates
|
||||
static inline Vec32c compress_saturated (Vec32s const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec32uc compress_saturated (Vec32us const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16s compress_saturated (Vec16i const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16us compress_saturated (Vec16ui const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8i compress_saturated (Vec8q const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8ui compress_saturated (Vec8uq const a) {
|
||||
return compress_saturated(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
#endif // AVX512
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Extend from 256 to 512 bit vectors
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#if INSTRSET >= 9 // AVX512. 512 bit integer vectors
|
||||
|
||||
// sign extend
|
||||
static inline Vec32s extend (Vec32c const a) {
|
||||
#if INSTRSET >= 10
|
||||
return _mm512_cvtepi8_epi16(a);
|
||||
#else
|
||||
return Vec32s(extend_low(a), extend_high(a));
|
||||
#endif
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec32us extend (Vec32uc const a) {
|
||||
#if INSTRSET >= 10
|
||||
return _mm512_cvtepu8_epi16(a);
|
||||
#else
|
||||
return Vec32us(extend_low(a), extend_high(a));
|
||||
#endif
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec16i extend (Vec16s const a) {
|
||||
return _mm512_cvtepi16_epi32(a);
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec16ui extend (Vec16us const a) {
|
||||
return _mm512_cvtepu16_epi32(a);
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec8q extend (Vec8i const a) {
|
||||
return _mm512_cvtepi32_epi64(a);
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec8uq extend (Vec8ui const a) {
|
||||
return _mm512_cvtepu32_epi64(a);
|
||||
}
|
||||
|
||||
#else // no AVX512. 512 bit vectors are emulated
|
||||
|
||||
|
||||
|
||||
// sign extend
|
||||
static inline Vec32s extend (Vec32c const a) {
|
||||
return Vec32s(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec32us extend (Vec32uc const a) {
|
||||
return Vec32us(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec16i extend (Vec16s const a) {
|
||||
return Vec16i(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec16ui extend (Vec16us const a) {
|
||||
return Vec16ui(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
// sign extend
|
||||
static inline Vec8q extend (Vec8i const a) {
|
||||
return Vec8q(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
// zero extend
|
||||
static inline Vec8uq extend (Vec8ui const a) {
|
||||
return Vec8uq(extend_low(a), extend_high(a));
|
||||
}
|
||||
|
||||
#endif // AVX512
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Reduce from 512 to 256 bit vectors
|
||||
*
|
||||
*****************************************************************************/
|
||||
#if INSTRSET >= 9 // AVX512F
|
||||
|
||||
// compress functions. overflow wraps around
|
||||
static inline Vec32c compress (Vec32s const a) {
|
||||
#if INSTRSET >= 10 // AVVX512BW
|
||||
return _mm512_cvtepi16_epi8(a);
|
||||
#else
|
||||
return compress(a.get_low(), a.get_high());
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline Vec32uc compress (Vec32us const a) {
|
||||
return Vec32uc(compress(Vec32s(a)));
|
||||
}
|
||||
|
||||
static inline Vec16s compress (Vec16i const a) {
|
||||
return _mm512_cvtepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec16us compress (Vec16ui const a) {
|
||||
return _mm512_cvtepi32_epi16(a);
|
||||
}
|
||||
|
||||
static inline Vec8i compress (Vec8q const a) {
|
||||
return _mm512_cvtepi64_epi32(a);
|
||||
}
|
||||
|
||||
static inline Vec8ui compress (Vec8uq const a) {
|
||||
return _mm512_cvtepi64_epi32(a);
|
||||
}
|
||||
|
||||
#else // no AVX512
|
||||
|
||||
// compress functions. overflow wraps around
|
||||
static inline Vec32c compress (Vec32s const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec32uc compress (Vec32us const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16s compress (Vec16i const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec16us compress (Vec16ui const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8i compress (Vec8q const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
static inline Vec8ui compress (Vec8uq const a) {
|
||||
return compress(a.get_low(), a.get_high());
|
||||
}
|
||||
|
||||
#endif // AVX512
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Conversions between float and double
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#if INSTRSET >= 9 // AVX512. 512 bit float vectors
|
||||
|
||||
// float to double
|
||||
static inline Vec8d to_double (Vec8f const a) {
|
||||
return _mm512_cvtps_pd(a);
|
||||
}
|
||||
|
||||
// double to float
|
||||
static inline Vec8f to_float (Vec8d const a) {
|
||||
return _mm512_cvtpd_ps(a);
|
||||
}
|
||||
|
||||
#else // no AVX512. 512 bit float vectors are emulated
|
||||
|
||||
// float to double
|
||||
static inline Vec8d to_double (Vec8f const a) {
|
||||
Vec4d lo = to_double(a.get_low());
|
||||
Vec4d hi = to_double(a.get_high());
|
||||
return Vec8d(lo,hi);
|
||||
}
|
||||
|
||||
// double to float
|
||||
static inline Vec8f to_float (Vec8d const a) {
|
||||
Vec4f lo = to_float(a.get_low());
|
||||
Vec4f hi = to_float(a.get_high());
|
||||
return Vec8f(lo, hi);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
// double to float
|
||||
static inline Vec4f to_float (Vec2d const a) {
|
||||
return _mm_cvtpd_ps(a);
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Generic template functions
|
||||
*
|
||||
* These templates define functions for multiple vector types in one template
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
// concatenate two vectors into one vector of double size
|
||||
template <typename T> auto concatenate2(T const a, T const b) {
|
||||
static_assert(sizeof(T) * 8 < MAX_VECTOR_SIZE, "Maximum vector size exceeded");
|
||||
return decltype(extend_z(a))(a, b); // call constructor for double size vector type
|
||||
}
|
||||
|
||||
|
||||
// horizontal min/max of vector elements
|
||||
// implemented with universal template, works for all vector types:
|
||||
|
||||
template <typename T> auto horizontal_min(T const x) {
|
||||
if constexpr (T::elementtype() >= 15) {
|
||||
// T is a float or double vector
|
||||
if (horizontal_or(is_nan(x))) {
|
||||
// check for NAN because min does not guarantee NAN propagation
|
||||
return x[horizontal_find_first(is_nan(x))];
|
||||
}
|
||||
}
|
||||
return horizontal_min1(x);
|
||||
}
|
||||
|
||||
template <typename T> auto horizontal_min1(T const x) {
|
||||
if constexpr (T::elementtype() <= 3) { // boolean vector type
|
||||
return horizontal_and(x);
|
||||
}
|
||||
else if constexpr (sizeof(T) >= 32) {
|
||||
// split recursively into smaller vectors
|
||||
return horizontal_min1(min(x.get_low(), x.get_high()));
|
||||
}
|
||||
else if constexpr (T::size() == 2) {
|
||||
T a = permute2 <1, V_DC>(x); // high half
|
||||
T b = min(a, x);
|
||||
return b[0];
|
||||
}
|
||||
else if constexpr (T::size() == 4) {
|
||||
T a = permute4<2, 3, V_DC, V_DC>(x); // high half
|
||||
T b = min(a, x);
|
||||
a = permute4<1, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
return b[0];
|
||||
}
|
||||
else if constexpr (T::size() == 8) {
|
||||
T a = permute8<4, 5, 6, 7, V_DC, V_DC, V_DC, V_DC>(x); // high half
|
||||
T b = min(a, x);
|
||||
a = permute8<2, 3, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
a = permute8<1, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
return b[0];
|
||||
}
|
||||
else {
|
||||
static_assert(T::size() == 16); // no other size is allowed
|
||||
T a = permute16<8, 9, 10, 11, 12, 13, 14, 15, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC >(x); // high half
|
||||
T b = min(a, x);
|
||||
a = permute16<4, 5, 6, 7, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
a = permute16<2, 3, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
a = permute16<1, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = min(a, b);
|
||||
return b[0];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> auto horizontal_max(T const x) {
|
||||
if constexpr (T::elementtype() >= 15) {
|
||||
// T is a float or double vector
|
||||
if (horizontal_or(is_nan(x))) {
|
||||
// check for NAN because max does not guarantee NAN propagation
|
||||
return x[horizontal_find_first(is_nan(x))];
|
||||
}
|
||||
}
|
||||
return horizontal_max1(x);
|
||||
}
|
||||
|
||||
template <typename T> auto horizontal_max1(T const x) {
|
||||
if constexpr (T::elementtype() <= 3) { // boolean vector type
|
||||
return horizontal_or(x);
|
||||
}
|
||||
else if constexpr (sizeof(T) >= 32) {
|
||||
// split recursively into smaller vectors
|
||||
return horizontal_max1(max(x.get_low(), x.get_high()));
|
||||
}
|
||||
else if constexpr (T::size() == 2) {
|
||||
T a = permute2 <1, V_DC>(x); // high half
|
||||
T b = max(a, x);
|
||||
return b[0];
|
||||
}
|
||||
else if constexpr (T::size() == 4) {
|
||||
T a = permute4<2, 3, V_DC, V_DC>(x); // high half
|
||||
T b = max(a, x);
|
||||
a = permute4<1, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
return b[0];
|
||||
}
|
||||
else if constexpr (T::size() == 8) {
|
||||
T a = permute8<4, 5, 6, 7, V_DC, V_DC, V_DC, V_DC>(x); // high half
|
||||
T b = max(a, x);
|
||||
a = permute8<2, 3, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
a = permute8<1, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
return b[0];
|
||||
}
|
||||
else {
|
||||
static_assert(T::size() == 16); // no other size is allowed
|
||||
T a = permute16<8, 9, 10, 11, 12, 13, 14, 15, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC >(x); // high half
|
||||
T b = max(a, x);
|
||||
a = permute16<4, 5, 6, 7, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
a = permute16<2, 3, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
a = permute16<1, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC, V_DC>(b);
|
||||
b = max(a, b);
|
||||
return b[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Find first element that is true in a boolean vector
|
||||
template <typename V>
|
||||
static inline int horizontal_find_first(V const x) {
|
||||
static_assert(V::elementtype() == 2 || V::elementtype() == 3, "Boolean vector expected");
|
||||
auto bits = to_bits(x); // convert to bits
|
||||
if (bits == 0) return -1;
|
||||
if constexpr (V::size() < 32) {
|
||||
return bit_scan_forward((uint32_t)bits);
|
||||
}
|
||||
else {
|
||||
return bit_scan_forward(bits);
|
||||
}
|
||||
}
|
||||
|
||||
// Count the number of elements that are true in a boolean vector
|
||||
template <typename V>
|
||||
static inline int horizontal_count(V const x) {
|
||||
static_assert(V::elementtype() == 2 || V::elementtype() == 3, "Boolean vector expected");
|
||||
auto bits = to_bits(x); // convert to bits
|
||||
if constexpr (V::size() < 32) {
|
||||
return vml_popcnt((uint32_t)bits);
|
||||
}
|
||||
else {
|
||||
return (int)vml_popcnt(bits);
|
||||
}
|
||||
}
|
||||
|
||||
// maximum and minimum functions. This version is sure to propagate NANs,
|
||||
// conforming to the new IEEE-754 2019 standard
|
||||
template <typename V>
|
||||
static inline V maximum(V const a, V const b) {
|
||||
if constexpr (V::elementtype() < 15) {
|
||||
return max(a, b); // integer type
|
||||
}
|
||||
else { // float or double vector
|
||||
V y = select(is_nan(a), a, max(a, b));
|
||||
#ifdef SIGNED_ZERO // pedantic about signed zero
|
||||
y = select(a == b, a & b, y); // maximum(+0, -0) = +0
|
||||
#endif
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
static inline V minimum(V const a, V const b) {
|
||||
if constexpr (V::elementtype() < 15) {
|
||||
return min(a, b); // integer type
|
||||
}
|
||||
else { // float or double vector
|
||||
V y = select(is_nan(a), a, min(a, b));
|
||||
#ifdef SIGNED_ZERO // pedantic about signed zero
|
||||
y = select(a == b, a | b, y); // minimum(+0, -0) = -0
|
||||
#endif
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
// floating point remainder
|
||||
// -denominator/2 <= result < denominator/2
|
||||
template <typename V>
|
||||
static inline V fremainder(V const numerator, double const denominator) {
|
||||
// (Optimization notice: Calculation of 1/denominator and constants for extended precision reduction
|
||||
// may be optimized by a compiler moving loop-invariant code. This is intended)
|
||||
static_assert(V::elementtype() == 16 || V::elementtype() == 17, "wrong vector type"); // supports only float and double
|
||||
if (denominator > 0.) { // denominator must be positive
|
||||
if constexpr (V::elementtype() == 16) { // float
|
||||
#ifdef __FMA__
|
||||
float recipd = float(1.0 / denominator); // reciprocal denominator
|
||||
float fd = float(denominator); // denominator rounded to single precision
|
||||
float d2 = float(denominator - fd); // remaining bits for double precision
|
||||
V q = round(numerator * recipd); // divide and round
|
||||
V m = nmul_add(q, d2, nmul_add(q, fd, numerator));// double precision reduction
|
||||
#else // no FMA. Use extended precision reduction
|
||||
union {
|
||||
float f;
|
||||
uint32_t i;
|
||||
} u;
|
||||
u.f = float(denominator);
|
||||
u.i &= 0xFFFFF000; // remove 12 least significant bits for extended precision reduction
|
||||
float d2 = denominator - u.f; // remaining bits
|
||||
float recipd = float(1.0 / denominator); // reciprocal
|
||||
V q = round(numerator * recipd); // divide and round
|
||||
V m = nmul_add(q, d2, nmul_add(q, u.f, numerator));// extended precision reduction
|
||||
#endif // FMA
|
||||
if (true) { // Check that result is within desired interval. This may be omitted if not essential:
|
||||
// This check may be needed in extreme cases of numerator > 1.E5 * denominator
|
||||
auto too_high = m >= float( denominator * 0.5);
|
||||
auto too_low = m < float(-denominator * 0.5);
|
||||
m = if_sub(too_high, m, float(denominator));
|
||||
m = if_add(too_low, m, float(denominator));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
else if constexpr (V::elementtype() == 17) { // double precision
|
||||
#ifdef __FMA__
|
||||
double recipd = 1.0 / denominator; // reciprocal
|
||||
V q = round(numerator * recipd); // divide and round
|
||||
V m = nmul_add(q, denominator, numerator); // nmul_add has extended precision
|
||||
#else // no FMA. Use extended precision reduction
|
||||
union {
|
||||
double f;
|
||||
uint64_t i;
|
||||
} u;
|
||||
u.f = denominator;
|
||||
u.i &= 0xFFFFFFFFFF000000; // remove 24 least significant bits for extended precision reduction
|
||||
double d2 = denominator - u.f; // remaining bits
|
||||
double recipd = 1.0 / denominator; // reciprocal
|
||||
V q = round(numerator * recipd); // divide and round
|
||||
V m = nmul_add(q, d2, nmul_add(q, u.f, numerator));// extended precision reduction
|
||||
#endif // FMA
|
||||
if (true) { // Check that result is within desired interval. This may be omitted if not essential:
|
||||
// This check is rarely needed except in extreme cases of numerator > 1.E14 * denominator
|
||||
auto too_high = m >= denominator * 0.5;
|
||||
auto too_low = m < -denominator * 0.5;
|
||||
m = if_sub(too_high, m, denominator);
|
||||
m = if_add(too_low, m, denominator);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nan_vec<V>(1); // denominator is not positive
|
||||
}
|
||||
}
|
||||
|
||||
// floating point modulo
|
||||
// 0 <= result < denominator
|
||||
template <typename V>
|
||||
static inline V fmodulo(V const numerator, double const denominator) {
|
||||
// (Optimization notice: Calculation of 1/denominator and constants for extended precision reduction
|
||||
// may be optimized by a compiler moving loop-invariant code. This is intended)
|
||||
static_assert(V::elementtype() == 16 || V::elementtype() == 17, "wrong vector type"); // supports only float and double
|
||||
if (denominator > 0.) { // denominator must be positive
|
||||
if constexpr (V::elementtype() == 16) { // float
|
||||
#ifdef __FMA__
|
||||
float recipd = float(1.0 / denominator); // reciprocal denominator
|
||||
float fd = float(denominator); // denominator rounded to single precision
|
||||
float d2 = float(denominator - fd); // remaining bits for double precision
|
||||
V q = floor(numerator * recipd); // divide and floor
|
||||
V m = nmul_add(q, d2, nmul_add(q, fd, numerator));// double precision reduction
|
||||
#else // no FMA. Use extended precision reduction
|
||||
union {
|
||||
float f;
|
||||
uint32_t i;
|
||||
} u;
|
||||
u.f = float(denominator);
|
||||
u.i &= 0xFFFFF000; // remove 12 least significant bits for extended precision reduction
|
||||
float d2 = denominator - u.f; // remaining bits
|
||||
float recipd = float(1.0 / denominator); // reciprocal
|
||||
V q = floor(numerator * recipd); // divide and floor
|
||||
V m = nmul_add(q, d2, nmul_add(q, u.f, numerator));// extended precision reduction
|
||||
#endif // FMA
|
||||
if (true) { // Check that result is within desired interval. This may be omitted if not essential:
|
||||
// This check may be needed in extreme cases of numerator > 1.E5 * denominator
|
||||
auto too_high = m >= float(denominator);
|
||||
auto too_low = m < 0.f;
|
||||
m = if_sub(too_high, m, float(denominator));
|
||||
m = if_add(too_low, m, float(denominator));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
else if constexpr (V::elementtype() == 17) { // double precision
|
||||
#ifdef __FMA__
|
||||
double recipd = 1.0 / denominator; // reciprocal
|
||||
V q = floor(numerator * recipd); // divide and floor
|
||||
V m = nmul_add(q, denominator, numerator); // nmul_add has extended precision
|
||||
#else // no FMA. Use extended precision reduction
|
||||
union {
|
||||
double f;
|
||||
uint64_t i;
|
||||
} u;
|
||||
u.f = denominator;
|
||||
u.i &= 0xFFFFFFFFFF000000; // remove 24 least significant bits for extended precision reduction
|
||||
double d2 = denominator - u.f; // remaining bits
|
||||
double recipd = 1.0 / denominator; // reciprocal
|
||||
V q = floor(numerator * recipd); // divide and floor
|
||||
V m = nmul_add(q, d2, nmul_add(q, u.f, numerator));// extended precision reduction
|
||||
#endif // FMA
|
||||
if (true) { // Check that result is within desired interval. This may be omitted if not essential:
|
||||
// This check is rarely needed except in extreme cases of numerator > 1.E14 * denominator
|
||||
auto too_high = m >= denominator;
|
||||
auto too_low = m < 0.;
|
||||
m = if_sub(too_high, m, denominator);
|
||||
m = if_add(too_low, m, denominator);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return nan_vec<V>(1); // denominator is not positive
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // VECTOR_CONVERT_H
|
||||
@@ -0,0 +1,87 @@
|
||||
/**************************** vectorclass.h ********************************
|
||||
* Author: Agner Fog
|
||||
* Date created: 2012-05-30
|
||||
* Last modified: 2026-04-14
|
||||
* Version: 2.02.03
|
||||
* Project: vector class library
|
||||
* Home: https://github.com/vectorclass
|
||||
* Description:
|
||||
* Header file defining vector classes as interface to intrinsic functions
|
||||
* in x86 and x86-64 microprocessors with SSE2 and later instruction sets.
|
||||
*
|
||||
* Instructions:
|
||||
* Use Gnu, Clang, Microsoft, or Intel C++ compiler. Compile for the desired
|
||||
* instruction set, which must be at least SSE2. Specify the supported
|
||||
* instruction set by a command line define, e.g. __SSE4_1__ if the
|
||||
* compiler does not automatically do so.
|
||||
* For detailed instructions, see vcl_manual.pdf
|
||||
*
|
||||
* Each vector object is represented internally in the CPU as a vector
|
||||
* register with 128, 256 or 512 bits.
|
||||
*
|
||||
* This header file includes the appropriate header files depending on the
|
||||
* selected instruction set.
|
||||
*
|
||||
* (c) Copyright 2012-2026 Agner Fog.
|
||||
* Apache License version 2.0 or later.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef VECTORCLASS_H
|
||||
#define VECTORCLASS_H 20203
|
||||
|
||||
// Maximum vector size, bits. Allowed values are 128, 256, 512
|
||||
#ifndef MAX_VECTOR_SIZE
|
||||
#define MAX_VECTOR_SIZE 512
|
||||
#endif
|
||||
|
||||
// Determine instruction set, and define platform-dependent functions
|
||||
#include "instrset.h" // Select supported instruction set
|
||||
|
||||
#if INSTRSET < 2 // instruction set SSE2 is the minimum
|
||||
#error Please compile for the SSE2 instruction set or higher
|
||||
#else
|
||||
|
||||
// Select appropriate .h files depending on instruction set
|
||||
#include "vectori128.h" // 128-bit integer vectors
|
||||
#include "vectorf128.h" // 128-bit floating point vectors
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
#if INSTRSET >= 8
|
||||
#include "vectori256.h" // 256-bit integer vectors, requires AVX2 instruction set
|
||||
#else
|
||||
#include "vectori256e.h" // 256-bit integer vectors, emulated
|
||||
#endif // INSTRSET >= 8
|
||||
#if INSTRSET >= 7
|
||||
#include "vectorf256.h" // 256-bit floating point vectors, requires AVX instruction set
|
||||
#else
|
||||
#include "vectorf256e.h" // 256-bit floating point vectors, emulated
|
||||
#endif // INSTRSET >= 7
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
#if INSTRSET >= 9
|
||||
#include "vectori512.h" // 512-bit vectors of 32 and 64 bit integers, requires AVX512F instruction set
|
||||
#include "vectorf512.h" // 512-bit floating point vectors, requires AVX512F instruction set
|
||||
#else
|
||||
#include "vectori512e.h" // 512-bit integer vectors, emulated
|
||||
#include "vectorf512e.h" // 512-bit floating point vectors, emulated
|
||||
#endif // INSTRSET >= 9
|
||||
#if INSTRSET >= 10
|
||||
#include "vectori512s.h" // 512-bit vectors of 8 and 16 bit integers, requires AVX512BW instruction set
|
||||
#else
|
||||
#include "vectori512se.h" // 512-bit vectors of 8 and 16 bit integers, emulated
|
||||
#endif
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
#include "vector_convert.h" // conversion between different vector sizes, and common templates
|
||||
|
||||
#endif // INSTRSET >= 2
|
||||
|
||||
|
||||
#else // VECTORCLASS_H
|
||||
|
||||
#if VECTORCLASS_H < 20000
|
||||
#error Mixed versions of vector class library
|
||||
#endif
|
||||
|
||||
#endif // VECTORCLASS_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,327 @@
|
||||
/*************************** vectormath_common.h ****************************
|
||||
* Author: Agner Fog
|
||||
* Date created: 2014-04-18
|
||||
* Last modified: 2022-07-20
|
||||
* Version: 2.02.00
|
||||
* Project: vector classes
|
||||
* Description:
|
||||
* Header file containing common code for inline version of mathematical functions.
|
||||
*
|
||||
* For detailed instructions, see VectorClass.pdf
|
||||
*
|
||||
* (c) Copyright 2014-2022 Agner Fog.
|
||||
* Apache License version 2.0 or later.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef VECTORMATH_COMMON_H
|
||||
#define VECTORMATH_COMMON_H 2
|
||||
|
||||
#ifdef VECTORMATH_LIB_H
|
||||
#error conflicting header files. More than one implementation of mathematical functions included
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#ifndef VECTORCLASS_H
|
||||
#include "vectorclass.h"
|
||||
#endif
|
||||
|
||||
#if VECTORCLASS_H < 20200
|
||||
#error Incompatible versions of vector class library mixed
|
||||
#endif
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
Define NAN payload values
|
||||
******************************************************************************/
|
||||
#define NAN_LOG 0x101 // logarithm for x<0
|
||||
#define NAN_POW 0x102 // negative number raised to non-integer power
|
||||
#define NAN_HYP 0x104 // acosh for x<1 and atanh for abs(x)>1
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
Define mathematical constants
|
||||
******************************************************************************/
|
||||
#define VM_PI 3.14159265358979323846 // pi
|
||||
#define VM_PI_2 1.57079632679489661923 // pi / 2
|
||||
#define VM_PI_4 0.785398163397448309616 // pi / 4
|
||||
#define VM_SQRT2 1.41421356237309504880 // sqrt(2)
|
||||
#define VM_LOG2E 1.44269504088896340736 // 1/log(2)
|
||||
#define VM_LOG10E 0.434294481903251827651 // 1/log(10)
|
||||
#define VM_LOG210 3.321928094887362347808 // log2(10)
|
||||
#define VM_LN2 0.693147180559945309417 // log(2)
|
||||
#define VM_LN10 2.30258509299404568402 // log(10)
|
||||
#define VM_SMALLEST_NORMAL 2.2250738585072014E-308 // smallest normal number, double
|
||||
#define VM_SMALLEST_NORMALF 1.17549435E-38f // smallest normal number, float
|
||||
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
namespace VCL_NAMESPACE {
|
||||
#endif
|
||||
|
||||
/******************************************************************************
|
||||
templates for producing infinite and nan in desired vector type
|
||||
******************************************************************************/
|
||||
template <typename VTYPE>
|
||||
static inline VTYPE infinite_vec();
|
||||
|
||||
template <>
|
||||
inline Vec2d infinite_vec<Vec2d>() {
|
||||
return infinite2d();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline Vec4f infinite_vec<Vec4f>() {
|
||||
return infinite4f();
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
|
||||
template <>
|
||||
inline Vec4d infinite_vec<Vec4d>() {
|
||||
return infinite4d();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline Vec8f infinite_vec<Vec8f>() {
|
||||
return infinite8f();
|
||||
}
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
|
||||
template <>
|
||||
inline Vec8d infinite_vec<Vec8d>() {
|
||||
return infinite8d();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline Vec16f infinite_vec<Vec16f>() {
|
||||
return infinite16f();
|
||||
}
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Detect NAN codes
|
||||
*
|
||||
* These functions return the code hidden in a NAN. The sign bit is ignored
|
||||
******************************************************************************/
|
||||
|
||||
static inline Vec4ui nan_code(Vec4f const x) {
|
||||
Vec4ui a = Vec4ui(reinterpret_i(x));
|
||||
Vec4ui const n = 0x007FFFFF;
|
||||
return select(Vec4ib(is_nan(x)), a & n, 0);
|
||||
}
|
||||
|
||||
// This function returns the code hidden in a NAN. The sign bit is ignored
|
||||
static inline Vec2uq nan_code(Vec2d const x) {
|
||||
Vec2uq a = Vec2uq(reinterpret_i(x));
|
||||
return select(Vec2qb(is_nan(x)), a << 12 >> (12+29), 0);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
|
||||
// This function returns the code hidden in a NAN. The sign bit is ignored
|
||||
static inline Vec8ui nan_code(Vec8f const x) {
|
||||
Vec8ui a = Vec8ui(reinterpret_i(x));
|
||||
Vec8ui const n = 0x007FFFFF;
|
||||
return select(Vec8ib(is_nan(x)), a & n, 0);
|
||||
}
|
||||
|
||||
// This function returns the code hidden in a NAN. The sign bit is ignored
|
||||
static inline Vec4uq nan_code(Vec4d const x) {
|
||||
Vec4uq a = Vec4uq(reinterpret_i(x));
|
||||
return select(Vec4qb(is_nan(x)), a << 12 >> (12+29), 0);
|
||||
}
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
|
||||
// This function returns the code hidden in a NAN. The sign bit is ignored
|
||||
static inline Vec16ui nan_code(Vec16f const x) {
|
||||
Vec16ui a = Vec16ui(reinterpret_i(x));
|
||||
Vec16ui const n = 0x007FFFFF;
|
||||
return select(Vec16ib(is_nan(x)), a & n, 0);
|
||||
}
|
||||
|
||||
// This function returns the code hidden in a NAN. The sign bit is ignored
|
||||
static inline Vec8uq nan_code(Vec8d const x) {
|
||||
Vec8uq a = Vec8uq(reinterpret_i(x));
|
||||
return select(Vec8qb(is_nan(x)), a << 12 >> (12+29), 0);
|
||||
}
|
||||
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
templates for polynomials
|
||||
Using Estrin's scheme to make shorter dependency chains and use FMA, starting
|
||||
longest dependency chains first.
|
||||
******************************************************************************/
|
||||
|
||||
// template <typedef VECTYPE, typedef CTYPE>
|
||||
template <class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_2(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2) {
|
||||
// calculates polynomial c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
//return = x2 * c2 + (x * c1 + c0);
|
||||
return mul_add(x2, c2, mul_add(x, c1, c0));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_3(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3) {
|
||||
// calculates polynomial c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
//return (c2 + c3*x)*x2 + (c1*x + c0);
|
||||
return mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_4(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4) {
|
||||
// calculates polynomial c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c2+c3*x)*x2 + ((c0+c1*x) + c4*x4);
|
||||
return mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0) + c4*x4);
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_4n(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3) {
|
||||
// calculates polynomial 1*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c2+c3*x)*x2 + ((c0+c1*x) + x4);
|
||||
return mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0) + x4);
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_5(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5) {
|
||||
// calculates polynomial c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c2+c3*x)*x2 + ((c4+c5*x)*x4 + (c0+c1*x));
|
||||
return mul_add(mul_add(c3, x, c2), x2, mul_add(mul_add(c5, x, c4), x4, mul_add(c1, x, c0)));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_5n(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4) {
|
||||
// calculates polynomial 1*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c2+c3*x)*x2 + ((c4+x)*x4 + (c0+c1*x));
|
||||
return mul_add(mul_add(c3, x, c2), x2, mul_add(c4 + x, x4, mul_add(c1, x, c0)));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_6(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6) {
|
||||
// calculates polynomial c6*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c4+c5*x+c6*x2)*x4 + ((c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(c6, x2, mul_add(c5, x, c4)), x4, mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0)));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_6n(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5) {
|
||||
// calculates polynomial 1*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return (c4+c5*x+x2)*x4 + ((c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(c5, x, c4 + x2), x4, mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0)));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_7(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7) {
|
||||
// calculates polynomial c7*x^7 + c6*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
//return ((c6+c7*x)*x2 + (c4+c5*x))*x4 + ((c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4, mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0)));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_8(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7, CTYPE c8) {
|
||||
// calculates polynomial c8*x^8 + c7*x^7 + c6*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
VTYPE x8 = x4 * x4;
|
||||
//return ((c6+c7*x)*x2 + (c4+c5*x))*x4 + (c8*x8 + (c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4,
|
||||
mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0) + c8*x8));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_9(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7, CTYPE c8, CTYPE c9) {
|
||||
// calculates polynomial c9*x^9 + c8*x^8 + c7*x^7 + c6*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
VTYPE x8 = x4 * x4;
|
||||
//return (((c6+c7*x)*x2 + (c4+c5*x))*x4 + (c8+c9*x)*x8) + ((c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(c9, x, c8), x8, mul_add(
|
||||
mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4,
|
||||
mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0))));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_10(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7, CTYPE c8, CTYPE c9, CTYPE c10) {
|
||||
// calculates polynomial c10*x^10 + c9*x^9 + c8*x^8 + c7*x^7 + c6*x^6 + c5*x^5 + c4*x^4 + c3*x^3 + c2*x^2 + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
VTYPE x8 = x4 * x4;
|
||||
//return (((c6+c7*x)*x2 + (c4+c5*x))*x4 + (c8+c9*x+c10*x2)*x8) + ((c2+c3*x)*x2 + (c0+c1*x));
|
||||
return mul_add(mul_add(x2, c10, mul_add(c9, x, c8)), x8,
|
||||
mul_add(mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4,
|
||||
mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0))));
|
||||
}
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_13(VTYPE const x, CTYPE c0, CTYPE c1, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7, CTYPE c8, CTYPE c9, CTYPE c10, CTYPE c11, CTYPE c12, CTYPE c13) {
|
||||
// calculates polynomial c13*x^13 + c12*x^12 + ... + c1*x + c0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
VTYPE x8 = x4 * x4;
|
||||
return mul_add(
|
||||
mul_add(
|
||||
mul_add(c13, x, c12), x4,
|
||||
mul_add(mul_add(c11, x, c10), x2, mul_add(c9, x, c8))), x8,
|
||||
mul_add(
|
||||
mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4,
|
||||
mul_add(mul_add(c3, x, c2), x2, mul_add(c1, x, c0))));
|
||||
}
|
||||
|
||||
|
||||
template<class VTYPE, class CTYPE>
|
||||
static inline VTYPE polynomial_13m(VTYPE const x, CTYPE c2, CTYPE c3, CTYPE c4, CTYPE c5, CTYPE c6, CTYPE c7, CTYPE c8, CTYPE c9, CTYPE c10, CTYPE c11, CTYPE c12, CTYPE c13) {
|
||||
// calculates polynomial c13*x^13 + c12*x^12 + ... + x + 0
|
||||
// VTYPE may be a vector type, CTYPE is a scalar type
|
||||
VTYPE x2 = x * x;
|
||||
VTYPE x4 = x2 * x2;
|
||||
VTYPE x8 = x4 * x4;
|
||||
// return ((c8+c9*x) + (c10+c11*x)*x2 + (c12+c13*x)*x4)*x8 + (((c6+c7*x)*x2 + (c4+c5*x))*x4 + ((c2+c3*x)*x2 + x));
|
||||
return mul_add(
|
||||
mul_add(mul_add(c13, x, c12), x4, mul_add(mul_add(c11, x, c10), x2, mul_add(c9, x, c8))), x8,
|
||||
mul_add(mul_add(mul_add(c7, x, c6), x2, mul_add(c5, x, c4)), x4, mul_add(mul_add(c3, x, c2), x2, x)));
|
||||
}
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,717 @@
|
||||
/**************************** vectormath_hyp.h ******************************
|
||||
* Author: Agner Fog
|
||||
* Date created: 2014-07-09
|
||||
* Last modified: 2022-07-20
|
||||
* Version: 2.02.00
|
||||
* Project: vector class library
|
||||
* Description:
|
||||
* Header file containing inline vector functions of hyperbolic and inverse
|
||||
* hyperbolic functions:
|
||||
* sinh hyperbolic sine
|
||||
* cosh hyperbolic cosine
|
||||
* tanh hyperbolic tangent
|
||||
* asinh inverse hyperbolic sine
|
||||
* acosh inverse hyperbolic cosine
|
||||
* atanh inverse hyperbolic tangent
|
||||
*
|
||||
* Theory, methods and inspiration based partially on these sources:
|
||||
* > Moshier, Stephen Lloyd Baluk: Methods and programs for mathematical functions.
|
||||
* Ellis Horwood, 1989.
|
||||
* > VDT library developed on CERN by Danilo Piparo, Thomas Hauth and
|
||||
* Vincenzo Innocente, 2012, https://svnweb.cern.ch/trac/vdt
|
||||
* > Cephes math library by Stephen L. Moshier 1992,
|
||||
* http://www.netlib.org/cephes/
|
||||
*
|
||||
* For detailed instructions, see vectormath_common.h and vcl_manual.pdf
|
||||
*
|
||||
* (c) Copyright 2014-2022 Agner Fog.
|
||||
* Apache License version 2.0 or later.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef VECTORMATH_HYP_H
|
||||
#define VECTORMATH_HYP_H 202
|
||||
|
||||
#include "vectormath_exp.h"
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
namespace VCL_NAMESPACE {
|
||||
#endif
|
||||
|
||||
/******************************************************************************
|
||||
* Hyperbolic functions
|
||||
******************************************************************************/
|
||||
|
||||
// Template for sinh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE sinh_d(VTYPE const x0) {
|
||||
// The limit of abs(x) is 709.7, as defined by max_x in vectormath_exp.h for 0.5*exp(x).
|
||||
|
||||
// Coefficients
|
||||
const double p0 = -3.51754964808151394800E5;
|
||||
const double p1 = -1.15614435765005216044E4;
|
||||
const double p2 = -1.63725857525983828727E2;
|
||||
const double p3 = -7.89474443963537015605E-1;
|
||||
|
||||
const double q0 = -2.11052978884890840399E6;
|
||||
const double q1 = 3.61578279834431989373E4;
|
||||
const double q2 = -2.77711081420602794433E2;
|
||||
const double q3 = 1.0;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 1.0; // use Pade approximation if abs(x) <= 1
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x*x;
|
||||
y1 = polynomial_3(x2, p0, p1, p2, p3) / polynomial_3(x2, q0, q1, q2, q3);
|
||||
y1 = mul_add(y1, x*x2, x); // y1 = x + x2*(x*y1);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = exp_d<VTYPE, 0, 1>(x); // 0.5 * exp(x)
|
||||
y2 -= 0.25 / y2; // - 0.5 * exp(-x)
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
// you can avoid the sign_combine by replacing x by x0 above, but at a loss of precision
|
||||
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of sinh_d template
|
||||
static inline Vec2d sinh(Vec2d const x) {
|
||||
return sinh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d sinh(Vec4d const x) {
|
||||
return sinh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d sinh(Vec8d const x) {
|
||||
return sinh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for sinh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE sinh_f(VTYPE const x0) {
|
||||
// The limit of abs(x) is 89.0, as defined by max_x in vectormath_exp.h for 0.5*exp(x).
|
||||
|
||||
// Coefficients
|
||||
const float r0 = 1.66667160211E-1f;
|
||||
const float r1 = 8.33028376239E-3f;
|
||||
const float r2 = 2.03721912945E-4f;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 1.0f; // use polynomial approximation if abs(x) <= 1
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x*x;
|
||||
y1 = polynomial_2(x2, r0, r1, r2);
|
||||
y1 = mul_add(y1, x2*x, x); // y1 = x + x2*(x*y1);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = exp_f<VTYPE, 0, 1>(x); // 0.5 * exp(x)
|
||||
y2 -= 0.25f / y2; // - 0.5 * exp(-x)
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
// you can avoid the sign_combine by replacing x by x0 above, but at a loss of precision
|
||||
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of sinh_f template
|
||||
static inline Vec4f sinh(Vec4f const x) {
|
||||
return sinh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f sinh(Vec8f const x) {
|
||||
return sinh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f sinh(Vec16f const x) {
|
||||
return sinh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for cosh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE cosh_d(VTYPE const x0) {
|
||||
// The limit of abs(x) is 709.7, as defined by max_x in vectormath_exp.h for 0.5*exp(x).
|
||||
|
||||
// data vectors
|
||||
VTYPE x, y;
|
||||
x = abs(x0);
|
||||
y = exp_d<VTYPE, 0, 1>(x); // 0.5 * exp(x)
|
||||
y += 0.25 / y; // + 0.5 * exp(-x)
|
||||
return y;
|
||||
}
|
||||
|
||||
// instances of sinh_d template
|
||||
static inline Vec2d cosh(Vec2d const x) {
|
||||
return cosh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d cosh(Vec4d const x) {
|
||||
return cosh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d cosh(Vec8d const x) {
|
||||
return cosh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for cosh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE cosh_f(VTYPE const x0) {
|
||||
// The limit of abs(x) is 89.0, as defined by max_x in vectormath_exp.h for 0.5*exp(x).
|
||||
|
||||
// data vectors
|
||||
VTYPE x, y;
|
||||
x = abs(x0);
|
||||
y = exp_f<VTYPE, 0, 1>(x); // 0.5 * exp(x)
|
||||
y += 0.25f / y; // + 0.5 * exp(-x)
|
||||
return y;
|
||||
}
|
||||
|
||||
// instances of sinh_d template
|
||||
static inline Vec4f cosh(Vec4f const x) {
|
||||
return cosh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f cosh(Vec8f const x) {
|
||||
return cosh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f cosh(Vec16f const x) {
|
||||
return cosh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for tanh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE tanh_d(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const double p0 = -1.61468768441708447952E3;
|
||||
const double p1 = -9.92877231001918586564E1;
|
||||
const double p2 = -9.64399179425052238628E-1;
|
||||
|
||||
const double q0 = 4.84406305325125486048E3;
|
||||
const double q1 = 2.23548839060100448583E3;
|
||||
const double q2 = 1.12811678491632931402E2;
|
||||
const double q3 = 1.0;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 0.625; // use Pade approximation if abs(x) <= 5/8
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x*x;
|
||||
y1 = polynomial_2(x2, p0, p1, p2) / polynomial_3(x2, q0, q1, q2, q3);
|
||||
y1 = mul_add(y1, x2*x, x); // y1 = x + x2*(x*y1);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = exp(x+x); // exp(2*x)
|
||||
y2 = 1.0 - 2.0 / (y2 + 1.0); // tanh(x)
|
||||
}
|
||||
auto x_big = x > 350.;
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = select(x_big, 1.0, y1); // avoid overflow
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of tanh_d template
|
||||
static inline Vec2d tanh(Vec2d const x) {
|
||||
return tanh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d tanh(Vec4d const x) {
|
||||
return tanh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d tanh(Vec8d const x) {
|
||||
return tanh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for tanh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE tanh_f(VTYPE const x0) {
|
||||
// The limit of abs(x) is 89.0, as defined by max_x in vectormath_exp.h for 0.5*exp(x).
|
||||
|
||||
// Coefficients
|
||||
const float r0 = -3.33332819422E-1f;
|
||||
const float r1 = 1.33314422036E-1f;
|
||||
const float r2 = -5.37397155531E-2f;
|
||||
const float r3 = 2.06390887954E-2f;
|
||||
const float r4 = -5.70498872745E-3f;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 0.625f; // use polynomial approximation if abs(x) <= 5/8
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x*x;
|
||||
y1 = polynomial_4(x2, r0, r1, r2, r3, r4);
|
||||
y1 = mul_add(y1, x2*x, x); // y1 = x + (x2*x)*y1;
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = exp(x+x); // exp(2*x)
|
||||
y2 = 1.0f - 2.0f / (y2 + 1.0f); // tanh(x)
|
||||
}
|
||||
auto x_big = x > 44.4f;
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = select(x_big, 1.0f, y1); // avoid overflow
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of tanh_f template
|
||||
static inline Vec4f tanh(Vec4f const x) {
|
||||
return tanh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f tanh(Vec8f const x) {
|
||||
return tanh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f tanh(Vec16f const x) {
|
||||
return tanh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Inverse hyperbolic functions
|
||||
******************************************************************************/
|
||||
|
||||
// Template for asinh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE asinh_d(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const double p0 = -5.56682227230859640450E0;
|
||||
const double p1 = -9.09030533308377316566E0;
|
||||
const double p2 = -4.37390226194356683570E0;
|
||||
const double p3 = -5.91750212056387121207E-1;
|
||||
const double p4 = -4.33231683752342103572E-3;
|
||||
|
||||
const double q0 = 3.34009336338516356383E1;
|
||||
const double q1 = 6.95722521337257608734E1;
|
||||
const double q2 = 4.86042483805291788324E1;
|
||||
const double q3 = 1.28757002067426453537E1;
|
||||
const double q4 = 1.0;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x2 = x0 * x0;
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 0.533; // use Pade approximation if abs(x) <= 0.5
|
||||
// Both methods give the highest error close to 0.5.
|
||||
// This limit is adjusted for minimum error
|
||||
auto x_huge = x > 1.E20; // simple approximation, avoid overflow
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
y1 = polynomial_4(x2, p0, p1, p2, p3, p4) / polynomial_4(x2, q0, q1, q2, q3, q4);
|
||||
y1 = mul_add(y1, x2*x, x); // y1 = x + (x2*x)*y1;
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log(x + sqrt(x2 + 1.0));
|
||||
if (horizontal_or(x_huge)) {
|
||||
// At least one element needs huge method to avoid overflow
|
||||
y2 = select(x_huge, log(x) + VM_LN2, y2);
|
||||
}
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of asinh_d template
|
||||
static inline Vec2d asinh(Vec2d const x) {
|
||||
return asinh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d asinh(Vec4d const x) {
|
||||
return asinh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d asinh(Vec8d const x) {
|
||||
return asinh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for asinh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE asinh_f(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const float r0 = -1.6666288134E-1f;
|
||||
const float r1 = 7.4847586088E-2f;
|
||||
const float r2 = -4.2699340972E-2f;
|
||||
const float r3 = 2.0122003309E-2f;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2;
|
||||
|
||||
x2 = x0 * x0;
|
||||
x = abs(x0);
|
||||
auto x_small = x <= 0.51f; // use polynomial approximation if abs(x) <= 0.5
|
||||
auto x_huge = x > 1.E10f; // simple approximation, avoid overflow
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
y1 = polynomial_3(x2, r0, r1, r2, r3);
|
||||
y1 = mul_add(y1, x2*x, x); // y1 = x + (x2*x)*y1;
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log(x + sqrt(x2 + 1.0f));
|
||||
if (horizontal_or(x_huge)) {
|
||||
// At least one element needs huge method to avoid overflow
|
||||
y2 = select(x_huge, log(x) + (float)VM_LN2, y2);
|
||||
}
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of asinh_f template
|
||||
static inline Vec4f asinh(Vec4f const x) {
|
||||
return asinh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f asinh(Vec8f const x) {
|
||||
return asinh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f asinh(Vec16f const x) {
|
||||
return asinh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for acosh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE acosh_d(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const double p0 = 1.10855947270161294369E5;
|
||||
const double p1 = 1.08102874834699867335E5;
|
||||
const double p2 = 3.43989375926195455866E4;
|
||||
const double p3 = 3.94726656571334401102E3;
|
||||
const double p4 = 1.18801130533544501356E2;
|
||||
|
||||
const double q0 = 7.83869920495893927727E4;
|
||||
const double q1 = 8.29725251988426222434E4;
|
||||
const double q2 = 2.97683430363289370382E4;
|
||||
const double q3 = 4.15352677227719831579E3;
|
||||
const double q4 = 1.86145380837903397292E2;
|
||||
const double q5 = 1.0;
|
||||
|
||||
// data vectors
|
||||
VTYPE x1, y1, y2;
|
||||
|
||||
x1 = x0 - 1.0;
|
||||
auto undef = x0 < 1.0; // result is NAN
|
||||
auto x_small = x1 < 0.49; // use Pade approximation if abs(x-1) < 0.5
|
||||
auto x_huge = x1 > 1.E20; // simple approximation, avoid overflow
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
y1 = sqrt(x1) * (polynomial_4(x1, p0, p1, p2, p3, p4) / polynomial_5(x1, q0, q1, q2, q3, q4, q5));
|
||||
// x < 1 generates NAN
|
||||
y1 = select(undef, nan_vec<VTYPE>(NAN_HYP), y1);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log(x0 + sqrt(mul_sub(x0,x0,1.0)));
|
||||
if (horizontal_or(x_huge)) {
|
||||
// At least one element needs huge method to avoid overflow
|
||||
y2 = select(x_huge, log(x0) + VM_LN2, y2);
|
||||
}
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of acosh_d template
|
||||
static inline Vec2d acosh(Vec2d const x) {
|
||||
return acosh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d acosh(Vec4d const x) {
|
||||
return acosh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d acosh(Vec8d const x) {
|
||||
return acosh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for acosh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE acosh_f(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const float r0 = 1.4142135263E0f;
|
||||
const float r1 = -1.1784741703E-1f;
|
||||
const float r2 = 2.6454905019E-2f;
|
||||
const float r3 = -7.5272886713E-3f;
|
||||
const float r4 = 1.7596881071E-3f;
|
||||
|
||||
// data vectors
|
||||
VTYPE x1, y1, y2;
|
||||
|
||||
x1 = x0 - 1.0f;
|
||||
auto undef = x0 < 1.0f; // result is NAN
|
||||
auto x_small = x1 < 0.49f; // use Pade approximation if abs(x-1) < 0.5
|
||||
auto x_huge = x1 > 1.E10f; // simple approximation, avoid overflow
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
y1 = sqrt(x1) * polynomial_4(x1, r0, r1, r2, r3, r4);
|
||||
// x < 1 generates NAN
|
||||
y1 = select(undef, nan_vec<VTYPE>(NAN_HYP), y1);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log(x0 + sqrt(mul_sub(x0,x0,1.0)));
|
||||
if (horizontal_or(x_huge)) {
|
||||
// At least one element needs huge method to avoid overflow
|
||||
y2 = select(x_huge, log(x0) + (float)VM_LN2, y2);
|
||||
}
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of acosh_f template
|
||||
static inline Vec4f acosh(Vec4f const x) {
|
||||
return acosh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f acosh(Vec8f const x) {
|
||||
return acosh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f acosh(Vec16f const x) {
|
||||
return acosh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for atanh function, double precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE atanh_d(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const double p0 = -3.09092539379866942570E1;
|
||||
const double p1 = 6.54566728676544377376E1;
|
||||
const double p2 = -4.61252884198732692637E1;
|
||||
const double p3 = 1.20426861384072379242E1;
|
||||
const double p4 = -8.54074331929669305196E-1;
|
||||
|
||||
const double q0 = -9.27277618139601130017E1;
|
||||
const double q1 = 2.52006675691344555838E2;
|
||||
const double q2 = -2.49839401325893582852E2;
|
||||
const double q3 = 1.08938092147140262656E2;
|
||||
const double q4 = -1.95638849376911654834E1;
|
||||
const double q5 = 1.0;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2, y3;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x < 0.5; // use Pade approximation if abs(x) < 0.5
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x * x;
|
||||
y1 = polynomial_4(x2, p0, p1, p2, p3, p4) / polynomial_5(x2, q0, q1, q2, q3, q4, q5);
|
||||
y1 = mul_add(y1, x2*x, x);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log((1.0+x)/(1.0-x)) * 0.5;
|
||||
// check if out of range
|
||||
y3 = select(x == 1.0, infinite_vec<VTYPE>(), nan_vec<VTYPE>(NAN_HYP));
|
||||
y2 = select(x >= 1.0, y3, y2);
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of atanh_d template
|
||||
static inline Vec2d atanh(Vec2d const x) {
|
||||
return atanh_d(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec4d atanh(Vec4d const x) {
|
||||
return atanh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec8d atanh(Vec8d const x) {
|
||||
return atanh_d(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
|
||||
// Template for atanh function, single precision
|
||||
// This function does not produce denormals
|
||||
// Template parameters:
|
||||
// VTYPE: double vector type
|
||||
template<typename VTYPE>
|
||||
static inline VTYPE atanh_f(VTYPE const x0) {
|
||||
|
||||
// Coefficients
|
||||
const float r0 = 3.33337300303E-1f;
|
||||
const float r1 = 1.99782164500E-1f;
|
||||
const float r2 = 1.46691431730E-1f;
|
||||
const float r3 = 8.24370301058E-2f;
|
||||
const float r4 = 1.81740078349E-1f;
|
||||
|
||||
// data vectors
|
||||
VTYPE x, x2, y1, y2, y3;
|
||||
|
||||
x = abs(x0);
|
||||
auto x_small = x < 0.5f; // use polynomial approximation if abs(x) < 0.5
|
||||
|
||||
if (horizontal_or(x_small)) {
|
||||
// At least one element needs small method
|
||||
x2 = x * x;
|
||||
y1 = polynomial_4(x2, r0, r1, r2, r3, r4);
|
||||
y1 = mul_add(y1, x2*x, x);
|
||||
}
|
||||
if (!horizontal_and(x_small)) {
|
||||
// At least one element needs big method
|
||||
y2 = log((1.0f+x)/(1.0f-x)) * 0.5f;
|
||||
// check if out of range
|
||||
y3 = select(x == 1.0f, infinite_vec<VTYPE>(), nan_vec<VTYPE>(NAN_HYP));
|
||||
y2 = select(x >= 1.0f, y3, y2);
|
||||
}
|
||||
y1 = select(x_small, y1, y2); // choose method
|
||||
y1 = sign_combine(y1, x0); // get original sign
|
||||
return y1;
|
||||
}
|
||||
|
||||
// instances of atanh_f template
|
||||
static inline Vec4f atanh(Vec4f const x) {
|
||||
return atanh_f(x);
|
||||
}
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 256
|
||||
static inline Vec8f atanh(Vec8f const x) {
|
||||
return atanh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 256
|
||||
|
||||
#if MAX_VECTOR_SIZE >= 512
|
||||
static inline Vec16f atanh(Vec16f const x) {
|
||||
return atanh_f(x);
|
||||
}
|
||||
#endif // MAX_VECTOR_SIZE >= 512
|
||||
|
||||
#ifdef VCL_NAMESPACE
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,7 @@ option(NANOVDB_BUILD_PYTHON_MODULE "Build the nanovdb Python module" OFF)
|
||||
option(NANOVDB_USE_INTRINSICS "Build with hardware intrinsics support" OFF)
|
||||
option(NANOVDB_USE_CUDA "Build with CUDA support" OFF)
|
||||
option(NANOVDB_CUDA_KEEP_PTX "Keep CUDA PTX" OFF)
|
||||
option(NANOVDB_USE_OLD_ACCESSOR "Use the old (buggy) ReadAccessor instead of the new fixed one" OFF)
|
||||
|
||||
option(NANOVDB_USE_OPENVDB "Build with OpenVDB support" OFF)
|
||||
option(NANOVDB_USE_BLOSC "Build with BLOSC support" ${USE_BLOSC})
|
||||
@@ -304,6 +305,10 @@ if(NANOVDB_USE_INTRINSICS)
|
||||
target_compile_definitions(nanovdb INTERFACE -DNANOVDB_USE_INTRINSICS)
|
||||
endif()
|
||||
|
||||
if(NANOVDB_USE_OLD_ACCESSOR)
|
||||
target_compile_definitions(nanovdb INTERFACE -DNANOVDB_USE_OLD_ACCESSOR)
|
||||
endif()
|
||||
|
||||
if(NANOVDB_USE_OPENVDB)
|
||||
if(NOT OPENVDB_BUILD_CORE)
|
||||
target_link_libraries(nanovdb INTERFACE OpenVDB::openvdb)
|
||||
|
||||
+269
-29
@@ -5,14 +5,15 @@
|
||||
// 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__
|
||||
|
||||
#define CNANOVDB_DATA_ALIGNMENT 32
|
||||
#define CNANOVDB_ALIGNMENT_PADDING(x, n) (-(x) & ((n)-1))
|
||||
|
||||
// We compute alignment, but if it evaluates to 0
|
||||
// it will be invalid C99, allowing pedantic compilers to
|
||||
// crash and claim they remain standards conforming.
|
||||
//#define CNANOVDB_DATA_ALIGNMENT 32
|
||||
//#define CNANOVDB_ALIGNMENT_PADDING(x, n) (-(x) & ((n)-1))
|
||||
|
||||
#define USE_SINGLE_ROOT_KEY
|
||||
|
||||
@@ -53,6 +54,21 @@ enum cnanovdb_GridType
|
||||
cnanovdb_GridType_End = 10
|
||||
};
|
||||
|
||||
// #define versions of the grid type to allow pre-processor
|
||||
// tests.
|
||||
#define CNANOVDB_GRIDTYPE_UNKNOWN 0
|
||||
#define CNANOVDB_GRIDTYPE_FLOAT 1
|
||||
#define CNANOVDB_GRIDTYPE_DOUBLE 2
|
||||
#define CNANOVDB_GRIDTYPE_INT16 3
|
||||
#define CNANOVDB_GRIDTYPE_INT32 4
|
||||
#define CNANOVDB_GRIDTYPE_INT64 5
|
||||
#define CNANOVDB_GRIDTYPE_VEC3F 6
|
||||
#define CNANOVDB_GRIDTYPE_VEC3D 7
|
||||
#define CNANOVDB_GRIDTYPE_MASK 8
|
||||
#define CNANOVDB_GRIDTYPE_FP16 9
|
||||
#define CNANOVDB_GRIDTYPE_END 10
|
||||
|
||||
|
||||
#define ROOT_LEVEL 3
|
||||
|
||||
#define DEFINEMASK_int(LOG2DIM, SIZE) \
|
||||
@@ -88,7 +104,7 @@ typedef struct
|
||||
double mInvMatD[9]; // r,c = 3*r + c
|
||||
double mVecD[3];
|
||||
double mTaperD;
|
||||
} cnanovdb_map;
|
||||
} cnanovdb_map; // Size 264
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -204,12 +220,13 @@ typedef struct
|
||||
{
|
||||
int64_t mByteOffset; // byte offset to the blind data, relative to the GridData.
|
||||
uint64_t mElementCount; // number of elements, e.g. point count
|
||||
uint32_t mFlags; // flags
|
||||
uint32_t mValueSize; // byte size of each value, etc.
|
||||
uint32_t mSemantic; // semantic meaning of the data.
|
||||
uint32_t mDataClass; // 4 bytes
|
||||
uint32_t mDataType; // 4 bytes
|
||||
char mName[256];
|
||||
uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(int64_t)+sizeof(uint64_t)+2*sizeof(uint32_t)+2*sizeof(uint32_t)+256*sizeof(char), CNANOVDB_DATA_ALIGNMENT)];
|
||||
// 2*8+4*4+256 : aligned, so don't include.
|
||||
// uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(int64_t)+sizeof(uint64_t)+2*sizeof(uint32_t)+2*sizeof(uint32_t)+256*sizeof(char), CNANOVDB_DATA_ALIGNMENT)];
|
||||
} cnanovdb_gridblindmetadata;
|
||||
|
||||
typedef struct
|
||||
@@ -227,9 +244,12 @@ typedef struct
|
||||
double mVoxelSize[3]; // 24B. size of a voxel in world units
|
||||
uint32_t mGridClass; // 4B.
|
||||
uint32_t mGridType; // 4B.
|
||||
uint64_t mBlindMetadataOffset; // 8B. offset of GridBlindMetaData structures.
|
||||
int32_t mBlindMetadataCount; // 4B. count of GridBlindMetaData structures.
|
||||
uint32_t _reserved[CNANOVDB_ALIGNMENT_PADDING(8 + 8 + 4 + 4 + 4 + 4 + 8 + 256 + 24 + 24 + sizeof(cnanovdb_map) + 24 + 4 + 4 + 8 + 4, CNANOVDB_DATA_ALIGNMENT) / 4];
|
||||
int64_t mBlindMetadataOffset; // 8B. offset of GridBlindMetaData structures.
|
||||
uint32_t mBlindMetadataCount; // 4B. count of GridBlindMetaData structures.
|
||||
|
||||
// Total 652: misaligned by 20.
|
||||
// uint32_t _reserved[CNANOVDB_ALIGNMENT_PADDING(8 + 8 + 4 + 4 + 4 + 4 + 8 + 256 + 24 + 24 + sizeof(cnanovdb_map) + 24 + 4 + 4 + 8 + 4, CNANOVDB_DATA_ALIGNMENT) / 4];
|
||||
uint32_t _reserved[5];
|
||||
} cnanovdb_griddata;
|
||||
|
||||
static void
|
||||
@@ -268,7 +288,8 @@ typedef struct
|
||||
uint32_t mNodeCount[ROOT_LEVEL];
|
||||
uint32_t mTileCount[ROOT_LEVEL];
|
||||
uint64_t mVoxelCount;
|
||||
uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(4*sizeof(uint64_t)+(3+3)*sizeof(uint32_t)+sizeof(uint64_t), CNANOVDB_DATA_ALIGNMENT)];
|
||||
// Total 64, so aligned, so do not include:
|
||||
// uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(4*sizeof(uint64_t)+(3+3)*sizeof(uint32_t)+sizeof(uint64_t), CNANOVDB_DATA_ALIGNMENT)];
|
||||
} cnanovdb_treedata;
|
||||
|
||||
static const CNANOVDB_GLOBAL cnanovdb_treedata *
|
||||
@@ -301,6 +322,17 @@ cnanovdb_readaccessor_insert(cnanovdb_readaccessor *RESTRICT acc, int childlevel
|
||||
acc->mKey.mVec[2] = ijk->mVec[2];
|
||||
}
|
||||
|
||||
|
||||
// The padding of nodes varies on the signature, as the value type may
|
||||
// result in aligned or misaligned...
|
||||
#define LEAFDATA_PAD_F
|
||||
#define LEAFDATA_PAD_F3 uint32_t _reserved[4];
|
||||
#define LEAFDATA_PAD_I
|
||||
|
||||
#define NODEDATA_PAD_F uint8_t _reserved[16];
|
||||
#define NODEDATA_PAD_F3
|
||||
#define NODEDATA_PAD_I uint8_t _reserved[16];
|
||||
|
||||
#define CREATE_LEAF_NODE_int(LEVEL, LOG2DIM, CHILDTOTAL, TOTAL, MASK, VALUETYPE, STATSTYPE, SUFFIX) \
|
||||
typedef struct \
|
||||
{ \
|
||||
@@ -312,7 +344,8 @@ typedef struct \
|
||||
VALUETYPE mMaximum; \
|
||||
STATSTYPE mAverage; \
|
||||
STATSTYPE mStdDevi; \
|
||||
uint32_t _reserved[ CNANOVDB_ALIGNMENT_PADDING(sizeof(cnanovdb_mask##LOG2DIM)+2*sizeof(VALUETYPE)+2*sizeof(STATSTYPE)+sizeof(cnanovdb_coord)+sizeof(uint8_t[3])+sizeof(uint8_t), CNANOVDB_DATA_ALIGNMENT)/4]; \
|
||||
LEAFDATA_PAD_##SUFFIX \
|
||||
/* uint32_t _reserved[ CNANOVDB_ALIGNMENT_PADDING(sizeof(cnanovdb_mask##LOG2DIM)+2*sizeof(VALUETYPE)+2*sizeof(STATSTYPE)+sizeof(cnanovdb_coord)+sizeof(uint8_t[3])+sizeof(uint8_t), CNANOVDB_DATA_ALIGNMENT)/4]; */ \
|
||||
VALUETYPE mVoxels[1u << (3*LOG2DIM)]; \
|
||||
} cnanovdb_node##LEVEL##SUFFIX; \
|
||||
\
|
||||
@@ -340,6 +373,23 @@ cnanovdb_node##LEVEL##SUFFIX##_getValueAndCache(const CNANOVDB_GLOBAL cnanovdb_n
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_setValue(CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value) \
|
||||
{ \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
node->mVoxels[n] = value; \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_setValueAndCache(CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value,cnanovdb_readaccessor *RESTRICT /* DO NOT REMOVE: Required for C99 compliance */ acc) \
|
||||
{ \
|
||||
(void)(acc); \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
node->mVoxels[n] = value; \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_isActive(const CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
@@ -379,7 +429,7 @@ typedef struct \
|
||||
cnanovdb_mask##LOG2DIM mValueMask, mChildMask; \
|
||||
VALUETYPE mMinimum, mMaximum; \
|
||||
STATSTYPE mAverage, mStdDevi; \
|
||||
uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(cnanovdb_mask##LOG2DIM)+sizeof(VALUETYPE)*2+sizeof(STATSTYPE)*2+sizeof(cnanovdb_coord)*2+sizeof(int32_t)+sizeof(uint32_t), CNANOVDB_DATA_ALIGNMENT)]; \
|
||||
NODEDATA_PAD_##SUFFIX \
|
||||
cnanovdb_tileentry##SUFFIX mTable[1u << (3*LOG2DIM)]; \
|
||||
} cnanovdb_node##LEVEL##SUFFIX; \
|
||||
\
|
||||
@@ -398,6 +448,13 @@ cnanovdb_node##LEVEL##SUFFIX##_getChild(const CNANOVDB_GLOBAL cnanovdb_node##LEV
|
||||
return childnode; \
|
||||
} \
|
||||
\
|
||||
static CNANOVDB_GLOBAL cnanovdb_node##CHILDLEVEL##SUFFIX * \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_getChildNC(CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, uint32_t n) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_node##CHILDLEVEL##SUFFIX *childnode = (CNANOVDB_GLOBAL cnanovdb_node##CHILDLEVEL##SUFFIX *)( ((CNANOVDB_GLOBAL uint8_t *)node) + node->mTable[n].child); \
|
||||
return childnode; \
|
||||
} \
|
||||
\
|
||||
static VALUETYPE \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_getValue(const CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
@@ -424,6 +481,31 @@ cnanovdb_node##LEVEL##SUFFIX##_getValueAndCache(const CNANOVDB_GLOBAL cnanovdb_n
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_setValue(CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value) \
|
||||
{ \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
if (cnanovdb_mask##LOG2DIM##_isOn(&node->mChildMask, n)) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_node##CHILDLEVEL##SUFFIX *child = cnanovdb_node##LEVEL##SUFFIX##_getChildNC(node, n); \
|
||||
return cnanovdb_node##CHILDLEVEL##SUFFIX##_setValue(child, ijk, value); \
|
||||
} \
|
||||
return false; \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_setValueAndCache(CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value, cnanovdb_readaccessor *RESTRICT acc) \
|
||||
{ \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
if (cnanovdb_mask##LOG2DIM##_isOn(&node->mChildMask, n)) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_node##CHILDLEVEL##SUFFIX *child = cnanovdb_node##LEVEL##SUFFIX##_getChildNC(node, n); \
|
||||
cnanovdb_readaccessor_insert(acc, CHILDLEVEL, child, ijk); \
|
||||
return cnanovdb_node##CHILDLEVEL##SUFFIX##_setValueAndCache(child, ijk, value, acc); \
|
||||
} \
|
||||
return false; \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_node##LEVEL##SUFFIX##_isActive(const CNANOVDB_GLOBAL cnanovdb_node##LEVEL##SUFFIX *RESTRICT node, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
uint32_t n = cnanovdb_node##LEVEL##SUFFIX##_CoordToOffset(ijk); \
|
||||
@@ -462,10 +544,33 @@ CREATE_INTERNAL_NODE_int(CHILDLEVEL, LEVEL, LOG2DIM, (TOTAL-LOG2DIM), TOTAL, ((1
|
||||
|
||||
|
||||
#ifdef USE_SINGLE_ROOT_KEY
|
||||
|
||||
// Whether this is aligned varies with the signature...
|
||||
#define ROOTDATATILE_PAD_F uint8_t _reserved[8];
|
||||
#define ROOTDATATILE_PAD_I uint8_t _reserved[8];
|
||||
#define ROOTDATATILE_PAD_F3
|
||||
|
||||
#define DEFINE_KEY(KEY) \
|
||||
uint64_t KEY;
|
||||
#define KEYSIZE sizeof(uint64_t)
|
||||
|
||||
#ifdef CNANOVDB_FULL_ROOT_SEARCH
|
||||
#define KEYSEARCH(SUFFIX) \
|
||||
uint64_t key; \
|
||||
const CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *result = 0; \
|
||||
key = cnanovdb_coord_to_key(ijk); \
|
||||
\
|
||||
(void)high; \
|
||||
/* Re-read tablesize for compiler bug on intel windows driver */ \
|
||||
for (int idx = low; idx < rootdata->mTableSize; idx++) \
|
||||
{ \
|
||||
const CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *tile = tiles + idx; \
|
||||
if (tile->key == key) \
|
||||
result = tile; \
|
||||
} \
|
||||
return result; \
|
||||
/**/
|
||||
#else
|
||||
#define KEYSEARCH(SUFFIX) \
|
||||
uint64_t key; \
|
||||
key = cnanovdb_coord_to_key(ijk); \
|
||||
@@ -477,7 +582,14 @@ CREATE_INTERNAL_NODE_int(CHILDLEVEL, LEVEL, LOG2DIM, (TOTAL-LOG2DIM), TOTAL, ((1
|
||||
return tile; \
|
||||
} \
|
||||
/**/
|
||||
#endif
|
||||
#else
|
||||
|
||||
// Whether this is aligned varies with the signature...
|
||||
#define ROOTDATATILE_PAD_F
|
||||
#define ROOTDATATILE_PAD_I
|
||||
#define ROOTDATATILE_PAD_F3 uint8_t _reserved[24];
|
||||
|
||||
#define DEFINE_KEY(KEY) \
|
||||
cnanovdb_coord KEY;
|
||||
#define KEYSIZE sizeof(cnanovdb_coord)
|
||||
@@ -504,6 +616,9 @@ CREATE_INTERNAL_NODE_int(CHILDLEVEL, LEVEL, LOG2DIM, (TOTAL-LOG2DIM), TOTAL, ((1
|
||||
/**/
|
||||
#endif
|
||||
|
||||
#define ROOTDATA_PAD_F uint32_t reserved[4];
|
||||
#define ROOTDATA_PAD_F3 uint32_t reserved[6];
|
||||
#define ROOTDATA_PAD_I uint32_t reserved[4];
|
||||
|
||||
#define CREATE_ROOTDATA(VALUETYPE, STATSTYPE, SUFFIX) \
|
||||
typedef struct \
|
||||
@@ -512,7 +627,11 @@ typedef struct \
|
||||
int64_t child; \
|
||||
uint32_t state; \
|
||||
VALUETYPE value; \
|
||||
uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(KEYSIZE)+sizeof(VALUETYPE)+sizeof(int64_t)+sizeof(uint32_t), CNANOVDB_DATA_ALIGNMENT)]; \
|
||||
/* Key is either 8 or 3*4, coord will round to 4*4 due to alignment */ \
|
||||
/* of child, so 8+8 or 4*4+8. state+value is either 4+4 or 4+3*4 */ \
|
||||
/* 8+8+4+4:pad, 8+8+4*4:align, 4*4+8+2*4:align, 4*4+8+4*4:pad */ \
|
||||
ROOTDATATILE_PAD_##SUFFIX \
|
||||
/* uint8_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(KEYSIZE)+sizeof(VALUETYPE)+sizeof(int64_t)+sizeof(uint32_t), CNANOVDB_DATA_ALIGNMENT)]; */ \
|
||||
} cnanovdb_rootdata_tile##SUFFIX; \
|
||||
\
|
||||
typedef struct \
|
||||
@@ -522,7 +641,10 @@ typedef struct \
|
||||
VALUETYPE mBackground; \
|
||||
VALUETYPE mMinimum, mMaximum; \
|
||||
STATSTYPE mAverage, mStdDevi; \
|
||||
uint32_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(cnanovdb_coord)*2+sizeof(uint32_t)+sizeof(VALUETYPE)*3+sizeof(STATSTYPE)*2, CNANOVDB_DATA_ALIGNMENT)/4]; \
|
||||
/* 2*3*4 + 4 + 3*4 + 2*4: 12*4, unaligned */ \
|
||||
/* 2*3*4 + 4 + 3*3*4 + 2*4: 18*4, unaligned */ \
|
||||
ROOTDATA_PAD_##SUFFIX \
|
||||
/* uint32_t _reserved[CNANOVDB_ALIGNMENT_PADDING(sizeof(cnanovdb_coord)*2+sizeof(uint32_t)+sizeof(VALUETYPE)*3+sizeof(STATSTYPE)*2, CNANOVDB_DATA_ALIGNMENT)/4]; */ \
|
||||
} cnanovdb_rootdata##SUFFIX; \
|
||||
\
|
||||
static const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX * \
|
||||
@@ -540,6 +662,13 @@ cnanovdb_rootdata##SUFFIX##_getTile(const CNANOVDB_GLOBAL cnanovdb_rootdata##SUF
|
||||
\
|
||||
static const CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX * \
|
||||
cnanovdb_rootdata##SUFFIX##_getChild(const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, const CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *RESTRICT tile) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *basenode = (CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) (((CNANOVDB_GLOBAL uint8_t *) rootdata) + tile->child); \
|
||||
return basenode; \
|
||||
} \
|
||||
\
|
||||
static CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX * \
|
||||
cnanovdb_rootdata##SUFFIX##_getChildNC(CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *RESTRICT tile) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *basenode = (CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) (((CNANOVDB_GLOBAL uint8_t *) rootdata) + tile->child); \
|
||||
return basenode; \
|
||||
@@ -555,6 +684,13 @@ cnanovdb_rootdata##SUFFIX##_findTile(const CNANOVDB_GLOBAL cnanovdb_rootdata##SU
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX * \
|
||||
cnanovdb_rootdata##SUFFIX##_findTileNC(CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
return (CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *) \
|
||||
cnanovdb_rootdata##SUFFIX##_findTile(rootdata, ijk); \
|
||||
} \
|
||||
\
|
||||
static VALUETYPE \
|
||||
cnanovdb_rootdata##SUFFIX##_getValue(const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
@@ -577,6 +713,30 @@ cnanovdb_rootdata##SUFFIX##_getValueAndCache(const CNANOVDB_GLOBAL cnanovdb_root
|
||||
const CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *child = cnanovdb_rootdata##SUFFIX##_getChild(rootdata, tile); \
|
||||
cnanovdb_readaccessor_insert(acc, 2, child, ijk); \
|
||||
return cnanovdb_node2##SUFFIX##_getValueAndCache( child, ijk, acc ); \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_rootdata##SUFFIX##_setValue(CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *tile = cnanovdb_rootdata##SUFFIX##_findTileNC(rootdata, ijk); \
|
||||
if (!tile) \
|
||||
return false; \
|
||||
if (tile->child == 0) \
|
||||
return false; \
|
||||
return cnanovdb_node2##SUFFIX##_setValue( cnanovdb_rootdata##SUFFIX##_getChildNC(rootdata, tile), ijk, value ); \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
cnanovdb_rootdata##SUFFIX##_setValueAndCache(CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *RESTRICT rootdata, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value, cnanovdb_readaccessor *RESTRICT acc) \
|
||||
{ \
|
||||
CNANOVDB_GLOBAL cnanovdb_rootdata_tile##SUFFIX *tile = cnanovdb_rootdata##SUFFIX##_findTileNC(rootdata, ijk); \
|
||||
if (!tile) \
|
||||
return false; \
|
||||
if (tile->child == 0) \
|
||||
return false; \
|
||||
CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *child = cnanovdb_rootdata##SUFFIX##_getChildNC(rootdata, tile); \
|
||||
cnanovdb_readaccessor_insert(acc, 2, child, ijk); \
|
||||
return cnanovdb_node2##SUFFIX##_setValueAndCache( child, ijk, value, acc );\
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
@@ -605,16 +765,19 @@ cnanovdb_rootdata##SUFFIX##_isActiveAndCache(const CNANOVDB_GLOBAL cnanovdb_root
|
||||
/**/
|
||||
|
||||
|
||||
inline void
|
||||
static inline void
|
||||
cnanovdb_readaccessor_init(cnanovdb_readaccessor *RESTRICT acc,
|
||||
const CNANOVDB_GLOBAL void /*cnanovdb_rootdata* */ *RESTRICT rootdata)
|
||||
{
|
||||
acc->mKey.mVec[0] = 0;
|
||||
acc->mKey.mVec[1] = 0;
|
||||
acc->mKey.mVec[2] = 0;
|
||||
acc->mNode[0] = acc->mNode[1] = acc->mNode[2] = 0;
|
||||
acc->mNode[3] = rootdata;
|
||||
}
|
||||
|
||||
#define DEFINE_ISCACHED(LEVEL, MASK) \
|
||||
inline bool \
|
||||
static inline bool \
|
||||
cnanovdb_readaccessor_isCached##LEVEL(cnanovdb_readaccessor *RESTRICT acc, int32_t dirty) \
|
||||
{ \
|
||||
if (!acc->mNode[LEVEL]) \
|
||||
@@ -632,7 +795,7 @@ DEFINE_ISCACHED(0, ((1u << 3) - 1u) )
|
||||
DEFINE_ISCACHED(1, ((1u << 7) - 1u) )
|
||||
DEFINE_ISCACHED(2, ((1u << 12) - 1u) )
|
||||
|
||||
inline int32_t
|
||||
static inline int32_t
|
||||
cnanovdb_readaccessor_computeDirty(const cnanovdb_readaccessor *RESTRICT acc, const cnanovdb_coord *RESTRICT ijk)
|
||||
{
|
||||
return (ijk->mVec[0] ^ acc->mKey.mVec[0]) |
|
||||
@@ -641,35 +804,102 @@ cnanovdb_readaccessor_computeDirty(const cnanovdb_readaccessor *RESTRICT acc, co
|
||||
}
|
||||
|
||||
#define CREATE_ACCESSOR(VALUETYPE, SUFFIX) \
|
||||
inline VALUETYPE \
|
||||
static inline VALUETYPE \
|
||||
cnanovdb_readaccessor_getValue##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
int32_t dirty = cnanovdb_readaccessor_computeDirty(acc, ijk); \
|
||||
\
|
||||
if (cnanovdb_readaccessor_isCached0(acc, dirty)) \
|
||||
return cnanovdb_node0##SUFFIX##_getValue( ((CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0]), ijk); \
|
||||
return cnanovdb_node0##SUFFIX##_getValue( ((const CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0]), ijk); \
|
||||
if (cnanovdb_readaccessor_isCached1(acc, dirty)) \
|
||||
return cnanovdb_node1##SUFFIX##_getValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, acc); \
|
||||
return cnanovdb_node1##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, acc); \
|
||||
if (cnanovdb_readaccessor_isCached2(acc, dirty)) \
|
||||
return cnanovdb_node2##SUFFIX##_getValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, acc); \
|
||||
return cnanovdb_node2##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, acc); \
|
||||
\
|
||||
return cnanovdb_rootdata##SUFFIX##_getValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, acc); \
|
||||
return cnanovdb_rootdata##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, acc); \
|
||||
} \
|
||||
\
|
||||
inline bool \
|
||||
static inline VALUETYPE \
|
||||
cnanovdb_readaccessor_getIJKValue##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, int i, int j, int k) \
|
||||
{ \
|
||||
cnanovdb_coord coord; \
|
||||
coord.mVec[0] = i; coord.mVec[1] = j; coord.mVec[2] = k; \
|
||||
return cnanovdb_readaccessor_getValue##SUFFIX(acc, &coord); \
|
||||
} \
|
||||
\
|
||||
static inline CNANOVDB_GLOBAL VALUETYPE * \
|
||||
cnanovdb_readaccessor_getLeaf##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
int32_t dirty = cnanovdb_readaccessor_computeDirty(acc, ijk); \
|
||||
\
|
||||
if (cnanovdb_readaccessor_isCached0(acc, dirty)) \
|
||||
return ((CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0])->mVoxels; \
|
||||
else if (cnanovdb_readaccessor_isCached1(acc, dirty)) \
|
||||
cnanovdb_node1##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, acc); \
|
||||
else if (cnanovdb_readaccessor_isCached2(acc, dirty)) \
|
||||
cnanovdb_node2##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, acc); \
|
||||
else \
|
||||
cnanovdb_rootdata##SUFFIX##_getValueAndCache( ((const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, acc); \
|
||||
if (acc->mNode[0]) \
|
||||
return ((CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0])->mVoxels; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static inline CNANOVDB_GLOBAL VALUETYPE * \
|
||||
cnanovdb_readaccessor_getIJKLeaf##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, int i, int j, int k) \
|
||||
{ \
|
||||
cnanovdb_coord coord; \
|
||||
coord.mVec[0] = i; coord.mVec[1] = j; coord.mVec[2] = k; \
|
||||
return cnanovdb_readaccessor_getLeaf##SUFFIX(acc, &coord); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
cnanovdb_readaccessor_isActive##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, const cnanovdb_coord *RESTRICT ijk) \
|
||||
{ \
|
||||
int32_t dirty = cnanovdb_readaccessor_computeDirty(acc, ijk); \
|
||||
\
|
||||
if (cnanovdb_readaccessor_isCached0(acc, dirty)) \
|
||||
return cnanovdb_node0##SUFFIX##_isActive( ((CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0]), ijk); \
|
||||
return cnanovdb_node0##SUFFIX##_isActive( ((const CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0]), ijk); \
|
||||
if (cnanovdb_readaccessor_isCached1(acc, dirty)) \
|
||||
return cnanovdb_node1##SUFFIX##_isActiveAndCache( ((CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, acc); \
|
||||
return cnanovdb_node1##SUFFIX##_isActiveAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, acc); \
|
||||
if (cnanovdb_readaccessor_isCached2(acc, dirty)) \
|
||||
return cnanovdb_node2##SUFFIX##_isActiveAndCache( ((CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, acc); \
|
||||
return cnanovdb_node2##SUFFIX##_isActiveAndCache( ((const CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, acc); \
|
||||
\
|
||||
return cnanovdb_rootdata##SUFFIX##_isActiveAndCache( ((CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, acc); \
|
||||
return cnanovdb_rootdata##SUFFIX##_isActiveAndCache( ((const CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, acc); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
cnanovdb_readaccessor_isIJKActive##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, int i, int j, int k) \
|
||||
{ \
|
||||
cnanovdb_coord coord; \
|
||||
coord.mVec[0] = i; coord.mVec[1] = j; coord.mVec[2] = k; \
|
||||
return cnanovdb_readaccessor_isActive##SUFFIX(acc, &coord); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
cnanovdb_readaccessor_setValue##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, const cnanovdb_coord *RESTRICT ijk, VALUETYPE value) \
|
||||
{ \
|
||||
int32_t dirty = cnanovdb_readaccessor_computeDirty(acc, ijk); \
|
||||
\
|
||||
if (cnanovdb_readaccessor_isCached0(acc, dirty)) \
|
||||
return cnanovdb_node0##SUFFIX##_setValue( ((CNANOVDB_GLOBAL cnanovdb_node0##SUFFIX *) acc->mNode[0]), ijk, value); \
|
||||
if (cnanovdb_readaccessor_isCached1(acc, dirty)) \
|
||||
return cnanovdb_node1##SUFFIX##_setValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_node1##SUFFIX *) acc->mNode[1]), ijk, value, acc); \
|
||||
if (cnanovdb_readaccessor_isCached2(acc, dirty)) \
|
||||
return cnanovdb_node2##SUFFIX##_setValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_node2##SUFFIX *) acc->mNode[2]), ijk, value, acc); \
|
||||
\
|
||||
return cnanovdb_rootdata##SUFFIX##_setValueAndCache( ((CNANOVDB_GLOBAL cnanovdb_rootdata##SUFFIX *)acc->mNode[3]), ijk, value, acc); \
|
||||
} \
|
||||
\
|
||||
static inline bool \
|
||||
cnanovdb_readaccessor_setIJKValue##SUFFIX(cnanovdb_readaccessor *RESTRICT acc, int i, int j, int k, VALUETYPE value) \
|
||||
{ \
|
||||
cnanovdb_coord coord; \
|
||||
coord.mVec[0] = i; coord.mVec[1] = j; coord.mVec[2] = k; \
|
||||
return cnanovdb_readaccessor_setValue##SUFFIX(acc, &coord, value); \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/**/
|
||||
|
||||
|
||||
@@ -684,6 +914,7 @@ CREATE_ACCESSOR(VALUETYPE, SUFFIX) \
|
||||
|
||||
CREATE_GRIDTYPE(float, float, F)
|
||||
CREATE_GRIDTYPE(cnanovdb_Vec3F, float, F3)
|
||||
CREATE_GRIDTYPE(int, int, I)
|
||||
|
||||
static int
|
||||
cnanovdb_griddata_valid(const CNANOVDB_GLOBAL cnanovdb_griddata *RESTRICT grid)
|
||||
@@ -715,4 +946,13 @@ cnanovdb_griddata_validF3(const CNANOVDB_GLOBAL cnanovdb_griddata *RESTRICT grid
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
cnanovdb_griddata_validI(const CNANOVDB_GLOBAL cnanovdb_griddata *RESTRICT grid)
|
||||
{
|
||||
if (!cnanovdb_griddata_valid(grid))
|
||||
return 0;
|
||||
if (grid->mGridType != cnanovdb_GridType_Int32)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -163,7 +163,6 @@ public:
|
||||
/// @brief Return a const pointer to the @a n'th grid encoded in this GridHandle on the device, e.g. GPU
|
||||
/// @tparam ValueT Value type of the grid point to be returned
|
||||
/// @param n Index of the grid pointer to be returned
|
||||
/// @param verbose if non-zero error messages will be printed in case something failed
|
||||
/// @warning Note that the return pointer can be NULL if the GridHandle was not initialized, @a n is invalid,
|
||||
/// or if the template parameter does not match the specified grid.
|
||||
template<typename ValueT, typename U = BufferT>
|
||||
@@ -293,7 +292,7 @@ public:
|
||||
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 fileName string name of the input file containing a raw grid buffer
|
||||
/// @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);
|
||||
@@ -302,7 +301,7 @@ public:
|
||||
}
|
||||
|
||||
/// @brief Read a specific grid from a file containing a raw grid buffer
|
||||
/// @param filename string name of the input file containing a raw grid buffer
|
||||
/// @param fileName string name of the input file containing a raw grid buffer
|
||||
/// @param n zero-based index of the grid to be read
|
||||
/// @param pool optional pool from which to allocate the new grid buffer
|
||||
/// @throw Will throw a std::ios_base::failure if the file does not exist and a
|
||||
@@ -314,7 +313,7 @@ public:
|
||||
}
|
||||
|
||||
/// @brief Read a specific grid from a file containing a raw grid buffer
|
||||
/// @param filename string name of the input file containing a raw grid buffer
|
||||
/// @param fileName string name of the input file 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::ios_base::failure if the file does not exist and a
|
||||
@@ -400,6 +399,7 @@ GridHandle<BufferT>::deviceGrid(uint32_t n) const
|
||||
template<typename BufferT>
|
||||
void GridHandle<BufferT>::read(std::istream& is, const BufferT& pool)
|
||||
{
|
||||
const std::streampos start = is.tellg();// remember where the raw buffer begins
|
||||
GridData data;
|
||||
is.read((char*)&data, sizeof(GridData));
|
||||
if (data.isValid()) {
|
||||
@@ -410,7 +410,7 @@ void GridHandle<BufferT>::read(std::istream& is, const BufferT& pool)
|
||||
sum += data.mGridSize;
|
||||
}
|
||||
auto buffer = BufferT::create(size + sum, &pool);
|
||||
is.seekg(-int64_t(sum + sizeof(GridData)), std::ios::cur);// rewind to start
|
||||
is.seekg(start);// rewind to the start of the raw buffer
|
||||
is.read((char*)(buffer.data()), buffer.size());
|
||||
*this = GridHandle(std::move(buffer));
|
||||
} else {
|
||||
@@ -470,7 +470,7 @@ void GridHandle<BufferT>::read(std::istream& is, const std::string &gridName, co
|
||||
/// @brief Split all grids in a single GridHandle into a vector of multiple GridHandles each with a single grid
|
||||
/// @tparam BufferT Type of the input and output grid buffers
|
||||
/// @param handle GridHandle with grids that will be slip into individual GridHandles
|
||||
/// @param pool optional pool used for allocation of output GridHandle
|
||||
/// @param other optional pool used for allocation of output GridHandle
|
||||
/// @return Vector of GridHandles each containing a single grid
|
||||
template<typename BufferT, template <class, class...> class VectorT = std::vector>
|
||||
inline VectorT<GridHandle<BufferT>>
|
||||
|
||||
+90
-21
@@ -145,7 +145,7 @@
|
||||
|
||||
#define NANOVDB_MAJOR_VERSION_NUMBER 32 // reflects changes to the ABI and hence also the file format
|
||||
#define NANOVDB_MINOR_VERSION_NUMBER 9 // reflects changes to the API but not ABI
|
||||
#define NANOVDB_PATCH_VERSION_NUMBER 1 // reflects changes that does not affect the ABI or API
|
||||
#define NANOVDB_PATCH_VERSION_NUMBER 2 // reflects changes that do not affect the ABI or API
|
||||
|
||||
#define TBB_SUPPRESS_DEPRECATED_MESSAGES 1
|
||||
|
||||
@@ -158,6 +158,11 @@
|
||||
// Use this to switch between std::ofstream or FILE implementations
|
||||
//#define NANOVDB_USE_IOSTREAMS
|
||||
|
||||
// Define NANOVDB_USE_OLD_ACCESSOR before including this header to temporarily
|
||||
// restore legacy ReadAccessor behavior where value lookups fall back to root
|
||||
// after a leaf-cache miss.
|
||||
|
||||
// Comment out to use (slower) branched version of LeafData<FpN,...>::getValue
|
||||
#define NANOVDB_FPN_BRANCHLESS
|
||||
|
||||
#if !defined(NANOVDB_ALIGN)
|
||||
@@ -290,7 +295,8 @@ enum class GridClass : uint32_t { Unknown = 0,
|
||||
VoxelVolume = 7, // volume of geometric cubes, e.g. colors cubes in Minecraft
|
||||
IndexGrid = 8, // grid whose values are offsets, e.g. into an external array
|
||||
TensorGrid = 9, // Index grid for indexing learnable tensor features
|
||||
End = 10,// total number of types in this enum (excluding StrLen since it's not a type)
|
||||
VoxelBVH = 10, // grid where each voxel points to list of primitive ids
|
||||
End = 11,// total number of types in this enum (excluding StrLen since it's not a type)
|
||||
StrLen = End + 7};// this entry is used to determine the minimum size of c-string
|
||||
|
||||
|
||||
@@ -310,6 +316,7 @@ __hostdev__ inline char* toStr(char *dst, GridClass gridClass)
|
||||
case GridClass::VoxelVolume: return util::strcpy(dst, "VOX");
|
||||
case GridClass::IndexGrid: return util::strcpy(dst, "INDEX");
|
||||
case GridClass::TensorGrid: return util::strcpy(dst, "TENSOR");
|
||||
case GridClass::VoxelBVH: return util::strcpy(dst, "VOXBVH");
|
||||
default: return util::strcpy(dst, "END");
|
||||
}
|
||||
}
|
||||
@@ -421,7 +428,17 @@ enum class GridBlindDataSemantic : uint32_t { Unknown = 0,
|
||||
LevelSet = 10, // narrow band level set, e.g. SDF
|
||||
FogVolume = 11, // fog volume, e.g. density
|
||||
Staggered = 12, // staggered MAC grid, e.g. velocity
|
||||
End = 13 };
|
||||
PointOpacity = 13, // opacity associated with point
|
||||
PointQuat = 14, // quaternion rotation, wxyz convention
|
||||
PointScale = 15, // vec3 scale
|
||||
PointSH0 = 16, // spherical harmonics, DC component
|
||||
PointSHN = 17, // spherical haromnics, AC components
|
||||
LineId = 18, // integer ID of line
|
||||
TriangleId = 19, // integer ID of triangle
|
||||
GaussianId = 20, // integer ID of Gaussian
|
||||
Range = 21, // begin/end pair of indices
|
||||
VoxelBVH = 22, // voxelbvh 64-bit voxel value
|
||||
End = 23 };
|
||||
|
||||
/// @brief Maps from GridBlindDataSemantic to GridClass
|
||||
/// @note Useful when converting an IndexGrid with blind data of type T into a Grid<T>
|
||||
@@ -450,6 +467,8 @@ __hostdev__ inline GridClass toGridClass(GridBlindDataSemantic semantics,
|
||||
return GridClass::FogVolume;
|
||||
case GridBlindDataSemantic::Staggered:
|
||||
return GridClass::Staggered;
|
||||
case GridBlindDataSemantic::VoxelBVH:
|
||||
return GridClass::VoxelBVH;
|
||||
default:
|
||||
return defaultClass;
|
||||
}
|
||||
@@ -472,6 +491,8 @@ __hostdev__ inline GridBlindDataSemantic toSemantic(GridClass gridClass,
|
||||
return GridBlindDataSemantic::FogVolume;
|
||||
case GridClass::Staggered:
|
||||
return GridBlindDataSemantic::Staggered;
|
||||
case GridClass::VoxelBVH:
|
||||
return GridBlindDataSemantic::VoxelBVH;
|
||||
default:
|
||||
return defaultSemantic;
|
||||
}
|
||||
@@ -1242,19 +1263,21 @@ public:
|
||||
/// @brief Set the specified bit off.
|
||||
__hostdev__ void setOff(uint32_t n) { mWords[n >> 6] &= ~(uint64_t(1) << (n & 63)); }
|
||||
|
||||
#if defined(__CUDACC__) // the following functions only run on the GPU!
|
||||
__device__ inline void setOnAtomic(uint32_t n)
|
||||
__hostdev__ inline void setOnAtomic(uint32_t n)
|
||||
{
|
||||
atomicOr(reinterpret_cast<unsigned long long int*>(this) + (n >> 6), 1ull << (n & 63));
|
||||
util::atomicOr(mWords + (n >> 6), uint64_t(1) << (n & 63));
|
||||
}
|
||||
__device__ inline void setOffAtomic(uint32_t n)
|
||||
__hostdev__ inline void setOffAtomic(uint32_t n)
|
||||
{
|
||||
atomicAnd(reinterpret_cast<unsigned long long int*>(this) + (n >> 6), ~(1ull << (n & 63)));
|
||||
util::atomicAnd(mWords + (n >> 6), ~(uint64_t(1) << (n & 63)));
|
||||
}
|
||||
__device__ inline void setAtomic(uint32_t n, bool on)
|
||||
__hostdev__ inline void setAtomic(uint32_t n, bool on)
|
||||
{
|
||||
on ? this->setOnAtomic(n) : this->setOffAtomic(n);
|
||||
}
|
||||
#if defined(__CUDACC__)
|
||||
// TODO: setWordAtomic is currently disabled. Before re-enabling, audit for
|
||||
// possible migration to __hostdev__ using util::atomicOr/util::atomicAnd.
|
||||
/*
|
||||
template<typename WordT>
|
||||
__device__ inline void setWordAtomic(WordT w, uint32_t n)
|
||||
@@ -1501,7 +1524,7 @@ struct Map
|
||||
/// e.g. inverse scale and inverse rotation WITHOUT translation.
|
||||
/// @note Typically this operation is used for scale and rotation from world -> index mapping
|
||||
/// @tparam Vec3T Template type of the 3D vector to be mapped
|
||||
/// @param ijk 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @param xyz 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @return linear inverse 3x3 mapping of the input vector i.e. xyz x mat^-1
|
||||
template<typename Vec3T>
|
||||
__hostdev__ Vec3T applyInverseJacobian(const Vec3T& xyz) const { return math::matMult(mInvMatD, xyz); }
|
||||
@@ -1510,7 +1533,7 @@ struct Map
|
||||
/// e.g. inverse scale and inverse rotation WITHOUT translation.
|
||||
/// @note Typically this operation is used for scale and rotation from world -> index mapping
|
||||
/// @tparam Vec3T Template type of the 3D vector to be mapped
|
||||
/// @param ijk 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @param xyz 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @return linear inverse 3x3 mapping of the input vector i.e. xyz x mat^-1
|
||||
template<typename Vec3T>
|
||||
__hostdev__ Vec3T applyInverseJacobianF(const Vec3T& xyz) const { return math::matMult(mInvMatF, xyz); }
|
||||
@@ -1519,7 +1542,7 @@ struct Map
|
||||
/// e.g. inverse scale and inverse rotation WITHOUT translation.
|
||||
/// @note Typically this operation is used for scale and rotation from world -> index mapping
|
||||
/// @tparam Vec3T Template type of the 3D vector to be mapped
|
||||
/// @param ijk 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @param xyz 3D vector to be mapped - typically floating point index coordinates
|
||||
/// @return linear inverse 3x3 mapping of the input vector i.e. xyz x mat^-1
|
||||
template<typename Vec3T>
|
||||
__hostdev__ Vec3T applyIJT(const Vec3T& xyz) const { return math::matMultT(mInvMatD, xyz); }
|
||||
@@ -2260,6 +2283,7 @@ public:
|
||||
__hostdev__ bool isPointIndex() const { return DataType::mGridClass == GridClass::PointIndex; }
|
||||
__hostdev__ bool isGridIndex() const { return DataType::mGridClass == GridClass::IndexGrid; }
|
||||
__hostdev__ bool isPointData() const { return DataType::mGridClass == GridClass::PointData; }
|
||||
__hostdev__ bool isVoxelBVH() const { return DataType::mGridClass == GridClass::VoxelBVH; }
|
||||
__hostdev__ bool isMask() const { return DataType::mGridClass == GridClass::Topology; }
|
||||
__hostdev__ bool isUnknown() const { return DataType::mGridClass == GridClass::Unknown; }
|
||||
__hostdev__ bool hasMinMax() const { return DataType::mFlags.isMaskOn(GridFlags::HasMinMax); }
|
||||
@@ -4717,7 +4741,7 @@ using OnIndexGrid = Grid<OnIndexTree>;
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
/// @brief Use this function, which depends a pointer to GridData, to call
|
||||
/// @brief Use this function, which depends on a pointer to GridData, to call
|
||||
/// other functions that depend on a NanoGrid of a known ValueType.
|
||||
/// @details This function allows for generic programming by converting GridData
|
||||
/// to a NanoGrid of the type encoded in GridData::mGridType.
|
||||
@@ -4829,7 +4853,7 @@ public:
|
||||
}
|
||||
|
||||
/// @brief Reset this access to its initial state, i.e. with an empty cache
|
||||
/// @node Noop since this template specialization has no cache
|
||||
/// @note Noop since this template specialization has no cache
|
||||
__hostdev__ void clear() {}
|
||||
|
||||
__hostdev__ const RootT& root() const { return *mRoot; }
|
||||
@@ -5161,7 +5185,11 @@ public:
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= LEVEL0) {
|
||||
if (this->isCached1(dirty)) return mNode1->template getAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= LEVEL1) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= LEVEL1) {
|
||||
if (this->isCached2(dirty)) return mNode2->template getAndCache<OpT>(ijk, *this, args...);
|
||||
}
|
||||
return mRoot->template getAndCache<OpT>(ijk, *this, args...);
|
||||
@@ -5177,7 +5205,11 @@ public:
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= LEVEL0) {
|
||||
if (this->isCached1(dirty)) return const_cast<Node1T*>(mNode1)->template setAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= LEVEL1) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= LEVEL1) {
|
||||
if (this->isCached2(dirty)) return const_cast<Node2T*>(mNode2)->template setAndCache<OpT>(ijk, *this, args...);
|
||||
}
|
||||
return const_cast<RootT*>(mRoot)->template setAndCache<OpT>(ijk, *this, args...);
|
||||
@@ -5335,7 +5367,7 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
__hostdev__ ValueType getValue(const CoordType& ijk) const {return this->template get<GetValue<BuildT>>(ijk);}
|
||||
__hostdev__ ValueType getValue(const CoordType& ijk) const {return this->template get<GetValue<BuildT>>(ijk);}
|
||||
__hostdev__ ValueType getValue(int i, int j, int k) const { return this->template get<GetValue<BuildT>>(CoordType(i, j, k)); }
|
||||
__hostdev__ ValueType operator()(const CoordType& ijk) const { return this->template get<GetValue<BuildT>>(ijk); }
|
||||
__hostdev__ ValueType operator()(int i, int j, int k) const { return this->template get<GetValue<BuildT>>(CoordType(i, j, k)); }
|
||||
@@ -5354,9 +5386,17 @@ public:
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <=0) {
|
||||
if (this->isCached<LeafT>(dirty)) return ((const LeafT*)mNode[0])->template getAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= 1) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= 1) {
|
||||
if (this->isCached<NodeT1>(dirty)) return ((const NodeT1*)mNode[1])->template getAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= 2) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= 2) {
|
||||
if (this->isCached<NodeT2>(dirty)) return ((const NodeT2*)mNode[2])->template getAndCache<OpT>(ijk, *this, args...);
|
||||
}
|
||||
return mRoot->template getAndCache<OpT>(ijk, *this, args...);
|
||||
@@ -5372,9 +5412,17 @@ public:
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= 0) {
|
||||
if (this->isCached<LeafT>(dirty)) return ((LeafT*)mNode[0])->template setAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= 1) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= 1) {
|
||||
if (this->isCached<NodeT1>(dirty)) return ((NodeT1*)mNode[1])->template setAndCache<OpT>(ijk, *this, args...);
|
||||
} else if constexpr(OpT::LEVEL <= 2) {
|
||||
}
|
||||
#ifdef NANOVDB_USE_OLD_ACCESSOR
|
||||
else
|
||||
#endif
|
||||
if constexpr(OpT::LEVEL <= 2) {
|
||||
if (this->isCached<NodeT2>(dirty)) return ((NodeT2*)mNode[2])->template setAndCache<OpT>(ijk, *this, args...);
|
||||
}
|
||||
return ((RootT*)mRoot)->template setAndCache<OpT>(ijk, *this, args...);
|
||||
@@ -5511,6 +5559,7 @@ public:
|
||||
__hostdev__ bool isPointIndex() const { return mGridData.mGridClass == GridClass::PointIndex; }
|
||||
__hostdev__ bool isGridIndex() const { return mGridData.mGridClass == GridClass::IndexGrid; }
|
||||
__hostdev__ bool isPointData() const { return mGridData.mGridClass == GridClass::PointData; }
|
||||
__hostdev__ bool isVoxelBVH() const { return mGridData.mGridClass == GridClass::VoxelBVH; }
|
||||
__hostdev__ bool isMask() const { return mGridData.mGridClass == GridClass::Topology; }
|
||||
__hostdev__ bool isUnknown() const { return mGridData.mGridClass == GridClass::Unknown; }
|
||||
__hostdev__ bool hasMinMax() const { return mGridData.mFlags.isMaskOn(GridFlags::HasMinMax); }
|
||||
@@ -5760,6 +5809,26 @@ public:
|
||||
|
||||
}; // ChannelAccessor
|
||||
|
||||
/// @brief Generic Accessor type that maps to either a ReadAccessor or ChannelAccessor
|
||||
/// @tparam BuildT Build type, e.g. float or ValueOnIndex
|
||||
/// @tparam ValueT Value type, e.g. float or Vec3f
|
||||
template <typename BuildT, typename ValueT>
|
||||
using AccType = typename util::conditional<BuildTraits<BuildT>::is_index,
|
||||
ChannelAccessor<ValueT, BuildT>, DefaultReadAccessor<BuildT>>::type;
|
||||
|
||||
/// @brief Generic template functions that return an Accessor to either an index grid or a regular grid
|
||||
template <typename GridT, typename ValueT>
|
||||
inline __hostdev__ auto getAccessor(const GridT &grid, ValueT *sideCar = nullptr)
|
||||
{
|
||||
using BuildT = typename GridT::BuildType;
|
||||
if constexpr(BuildTraits<BuildT>::is_index) {
|
||||
return sideCar ? ChannelAccessor<ValueT, BuildT>(grid, sideCar) : ChannelAccessor<ValueT, BuildT>(grid);
|
||||
} else {
|
||||
static_assert(util::is_same<ValueT, typename GridT::ValueType>::value, "wrong ValueT for regular GridT");
|
||||
return DefaultReadAccessor<BuildT>(grid);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This MiniGridHandle class is only included as a stand-alone example. Note that aligned_alloc is a C++17 feature!
|
||||
// Normally we recommend using GridHandle defined in util/GridHandle.h but this minimal implementation could be an
|
||||
|
||||
+31
-17
@@ -1088,7 +1088,7 @@ PNANOVDB_FORCE_INLINE void pnanovdb_write_vec3(pnanovdb_buf_t buf, pnanovdb_addr
|
||||
|
||||
#define PNANOVDB_MAJOR_VERSION_NUMBER 32// reflects changes to the ABI
|
||||
#define PNANOVDB_MINOR_VERSION_NUMBER 9// reflects changes to the API but not ABI
|
||||
#define PNANOVDB_PATCH_VERSION_NUMBER 1// reflects bug-fixes with no ABI or API changes
|
||||
#define PNANOVDB_PATCH_VERSION_NUMBER 2// reflects bug-fixes with no ABI or API changes
|
||||
|
||||
#define PNANOVDB_GRID_TYPE_UNKNOWN 0
|
||||
#define PNANOVDB_GRID_TYPE_FLOAT 1
|
||||
@@ -1130,8 +1130,9 @@ PNANOVDB_FORCE_INLINE void pnanovdb_write_vec3(pnanovdb_buf_t buf, pnanovdb_addr
|
||||
#define PNANOVDB_GRID_CLASS_TOPOLOGY 6 // grid with active states only (no values)
|
||||
#define PNANOVDB_GRID_CLASS_VOXEL_VOLUME 7 // volume of geometric cubes, e.g. minecraft
|
||||
#define PNANOVDB_GRID_CLASS_INDEX_GRID 8 // grid whose values are offsets, e.g. into an external array
|
||||
#define PNANOVDB_GRID_CLASS_TENSOR_GRID 9 // grid which can have extra metadata and features
|
||||
#define PNANOVDB_GRID_CLASS_END 10
|
||||
#define PNANOVDB_GRID_CLASS_TENSOR_GRID 9 // grid which can have extra metadata and features
|
||||
#define PNANOVDB_GRID_CLASS_VOXEL_BVH 10 // grid where each voxel points to list of primitive ids
|
||||
#define PNANOVDB_GRID_CLASS_END 11
|
||||
|
||||
#define PNANOVDB_GRID_FLAGS_HAS_LONG_GRID_NAME (1 << 0)
|
||||
#define PNANOVDB_GRID_FLAGS_HAS_BBOX (1 << 1)
|
||||
@@ -1412,23 +1413,36 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_version_get_patch(pnanovdb_uint
|
||||
}
|
||||
|
||||
#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_INDEX_ARRAY 1 // indices typically used for mapping into other arrays
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_ATTRIBUTE_ARRAY 2 // attributes typically associated with points
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_GRID_NAME 3 // grid names of length longer than 256 characters
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_CHANNEL_ARRAY 4 // channel of values typically used by index grids
|
||||
#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
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_POSITION 1 // 3D coordinates in an unknown space
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_COLOR 2 // color associated with point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_NORMAL 3 // normal associated with point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_RADIUS 4 // radius of point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_VELOCITY 5 // velocity associated with point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_ID 6 // integer ID of point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_WORLD_COORDS 7 // 3D coordinates in world space, e.g. (0.056, 0.8, 1,8)
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_GRID_COORDS 8 // 3D coordinates in grid space, e.g. (1.2, 4.0, 5.7), aka index-space
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_VOXEL_COORDS 9 // 3D coordinates in voxel space, e.g. (0.2, 0.0, 0.7)
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_LEVEL_SET 10 // narrow band level set, e.g. SDF
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_FOG_VOLUME 11 // fog volume, e.g. density
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_STAGGERED 12 // staggered MAC grid, e.g. velocity
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_OPACITY 13 // opacity associated with point
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_QUAT 14 // quaternion rotation, wxyz convention
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_SCALE 15 // vec3 scale
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_SH0 16 // spherical harmonics, DC component
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_SHN 17 // spherical haromnics, AC components
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_LINE_ID 18 // integer ID of line
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_TRIANGLE_ID 19 // integer ID of triangle
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_GAUSSIAN_ID 20 // integer ID of Gaussian
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_RANGE 21 // begin/end pair of indices
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_VOXEL_BVH 22 // voxelbvh 64-bit voxel value
|
||||
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_END 23
|
||||
|
||||
struct pnanovdb_gridblindmetadata_t
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ A lightweight GPU friendly version of VDB initially targeting rendering applicat
|
||||
* [Frequently asked questions](../../doc/nanovdb/FAQ.md)
|
||||
* [Source tree](../../doc/nanovdb/SourceTree.md)
|
||||
* [Examples](../../doc/nanovdb/HelloWorld.md)
|
||||
* [Python examples](python/examples/)
|
||||
|
||||
### Copyright Contributors to the OpenVDB Project
|
||||
### SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -7,10 +7,23 @@
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <nanovdb/util/cuda/Util.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace nanovdb {
|
||||
|
||||
namespace cuda {
|
||||
|
||||
/// @brief Default stream-ordered device memory resource. Allocations are made
|
||||
/// with cudaMallocAsync and freed with cudaFreeAsync via the
|
||||
/// util::cuda wrappers.
|
||||
/// @note Exposes both the legacy static methods (allocateAsync /
|
||||
/// deallocateAsync, retained for source compatibility) and the
|
||||
/// instance methods (allocate_async / deallocate_async) that model
|
||||
/// the AsyncResource concept. The instance methods forward to the
|
||||
/// static ones, so a default-constructed instance is stateless and
|
||||
/// adds no overhead.
|
||||
class DeviceResource
|
||||
{
|
||||
public:
|
||||
@@ -26,8 +39,63 @@ public:
|
||||
static void deallocateAsync(void *p, size_t, size_t, cudaStream_t stream) {
|
||||
cudaCheck(util::cuda::freeAsync(p, stream));
|
||||
}
|
||||
|
||||
/// @brief Instance allocation entry point modelling the AsyncResource concept.
|
||||
/// @param bytes number of bytes to allocate
|
||||
/// @param alignment requested alignment (cudaMallocAsync always 256B-aligns)
|
||||
/// @param stream cuda stream the allocation is ordered on
|
||||
void* allocate_async(size_t bytes, size_t alignment, cudaStream_t stream) {
|
||||
return allocateAsync(bytes, alignment, stream);
|
||||
}
|
||||
|
||||
/// @brief Instance deallocation entry point modelling the AsyncResource concept.
|
||||
/// @param p pointer previously returned by allocate_async
|
||||
/// @param bytes size passed to the matching allocate_async
|
||||
/// @param alignment alignment passed to the matching allocate_async
|
||||
/// @param stream cuda stream the deallocation is ordered on
|
||||
void deallocate_async(void* p, size_t bytes, size_t alignment, cudaStream_t stream) {
|
||||
deallocateAsync(p, bytes, alignment, stream);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Returns a program-lifetime, address-stable reference to a default
|
||||
/// instance of resource @c R.
|
||||
/// @details The instance is a function-local static, so it outlives every
|
||||
/// caller and is safe to bind through a default function/constructor
|
||||
/// argument. @c R must be default-constructible.
|
||||
template <class R>
|
||||
inline R& default_resource()
|
||||
{
|
||||
static R sResource;
|
||||
return sResource;
|
||||
}
|
||||
|
||||
/// @brief Detection trait: @c is_async_resource<R>::value is true iff @c R
|
||||
/// models the stream-ordered AsyncResource concept, i.e. exposes
|
||||
/// allocate_async(size_t, size_t, cudaStream_t) and
|
||||
/// deallocate_async(void*, size_t, size_t, cudaStream_t).
|
||||
/// @details Use it to dispatch between a stream-ordered resource and a
|
||||
/// synchronous one (which exposes allocate/deallocate without a
|
||||
/// stream argument):
|
||||
/// @code
|
||||
/// template<typename R>
|
||||
/// void* allocate(R& resource, size_t bytes, size_t alignment, cudaStream_t stream)
|
||||
/// {
|
||||
/// if constexpr (nanovdb::cuda::is_async_resource<R>::value)
|
||||
/// return resource.allocate_async(bytes, alignment, stream); // stream-ordered
|
||||
/// else
|
||||
/// return resource.allocate(bytes, alignment); // synchronous
|
||||
/// }
|
||||
/// @endcode
|
||||
template <class R, class = void>
|
||||
struct is_async_resource : std::false_type {};
|
||||
|
||||
template <class R>
|
||||
struct is_async_resource<R, std::void_t<
|
||||
decltype(std::declval<R&>().allocate_async(size_t{0}, size_t{0}, cudaStream_t{0})),
|
||||
decltype(std::declval<R&>().deallocate_async(std::declval<void*>(), size_t{0}, size_t{0}, cudaStream_t{0}))>>
|
||||
: std::true_type {};
|
||||
|
||||
}
|
||||
|
||||
} // namespace nanovdb::cuda
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
#ifndef NANOVDB_CUDA_PINNEDRESOURCE_H_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_CUDA_PINNEDRESOURCE_H_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <nanovdb/util/cuda/Util.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace nanovdb {
|
||||
|
||||
namespace cuda {
|
||||
|
||||
/// @brief Default host-pinned memory resource. Allocations are page-locked
|
||||
/// host memory (cudaMallocHost) that is both host-accessible and
|
||||
/// device-accessible, so it can serve as the host side of an
|
||||
/// asynchronous host<->device copy.
|
||||
/// @note This resource is *synchronous*: cudaMallocHost / cudaFreeHost
|
||||
/// synchronize the context, so it models the synchronous Resource
|
||||
/// concept (allocate / deallocate, no stream) rather than the
|
||||
/// stream-ordered AsyncResource concept. It is a seam so callers can
|
||||
/// substitute a pooled / genuinely stream-ordered pinned-host resource
|
||||
/// and avoid that synchronization.
|
||||
class PinnedResource
|
||||
{
|
||||
public:
|
||||
// cudaMallocHost over-aligns (to at least the page size), so the requested
|
||||
// alignment is always satisfied; 256 is the nominal default advertised here.
|
||||
static constexpr size_t DEFAULT_ALIGNMENT = 256;
|
||||
|
||||
/// @brief Synchronous allocation of page-locked host memory.
|
||||
/// @param bytes number of bytes to allocate
|
||||
/// @param alignment requested alignment (ignored; cudaMallocHost is page-aligned)
|
||||
void* allocate(size_t bytes, size_t alignment) {
|
||||
(void)alignment;
|
||||
void* p = nullptr;
|
||||
cudaCheck(cudaMallocHost(&p, bytes));
|
||||
return p;
|
||||
}
|
||||
|
||||
/// @brief Synchronous deallocation.
|
||||
/// @param p pointer previously returned by allocate
|
||||
/// @param bytes size passed to the matching allocate (unused)
|
||||
/// @param alignment alignment passed to the matching allocate (unused)
|
||||
void deallocate(void* p, size_t bytes, size_t alignment) {
|
||||
(void)bytes;
|
||||
(void)alignment;
|
||||
cudaCheck(cudaFreeHost(p));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} // namespace nanovdb::cuda
|
||||
|
||||
#endif // end of NANOVDB_CUDA_PINNEDRESOURCE_H_HAS_BEEN_INCLUDED
|
||||
@@ -5,13 +5,16 @@
|
||||
///
|
||||
/// @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.
|
||||
/// private member data.
|
||||
//
|
||||
#ifndef NANOVDB_CUDA_TEMPPOOL_H_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_CUDA_TEMPPOOL_H_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <nanovdb/cuda/DeviceResource.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cuda_runtime_api.h>
|
||||
|
||||
namespace nanovdb {
|
||||
|
||||
namespace cuda {
|
||||
@@ -20,13 +23,21 @@ template <class Resource>
|
||||
class TempPool {
|
||||
public:
|
||||
|
||||
/// @brief Default c-tor of an empty memory pool.
|
||||
TempPool() : mData(nullptr), mSize(0), mRequestedSize(0) {}
|
||||
/// @brief Default c-tor of an empty memory pool that uses the default
|
||||
/// instance of @c Resource for all allocations.
|
||||
TempPool() : mResource(&default_resource<Resource>()), mData(nullptr), mSize(0), mRequestedSize(0), mStream(nullptr) {}
|
||||
|
||||
/// @brief Destructor
|
||||
/// @brief C-tor of an empty memory pool that routes all allocations through
|
||||
/// the supplied @c Resource instance.
|
||||
/// @param resource resource instance to allocate from; must outlive this pool.
|
||||
explicit TempPool(Resource& resource) : mResource(&resource), mData(nullptr), mSize(0), mRequestedSize(0), mStream(nullptr) {}
|
||||
|
||||
/// @brief Destructor. Frees the managed memory on the stream of the most
|
||||
/// recent reallocate(), so the stream-ordered free is ordered after
|
||||
/// the work that used the memory (rather than on the null stream).
|
||||
~TempPool() {
|
||||
mRequestedSize = 0;
|
||||
Resource::deallocateAsync(mData, mSize, Resource::DEFAULT_ALIGNMENT, nullptr);
|
||||
mResource->deallocate_async(mData, mSize, Resource::DEFAULT_ALIGNMENT, mStream);
|
||||
mData = nullptr;
|
||||
mSize = 0;
|
||||
}
|
||||
@@ -41,20 +52,27 @@ public:
|
||||
/// @note This requested size should always be less than or smaller than the actual size().
|
||||
size_t& requestedSize() {return mRequestedSize;}
|
||||
|
||||
/// @brief Returns the stream that the managed memory was last (re)allocated on,
|
||||
/// i.e. the stream this pool will free on at destruction.
|
||||
cudaStream_t stream() const {return mStream;}
|
||||
|
||||
/// @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);
|
||||
mResource->deallocate_async(mData, mSize, Resource::DEFAULT_ALIGNMENT, stream);
|
||||
mData = mResource->allocate_async(mRequestedSize, Resource::DEFAULT_ALIGNMENT, stream);
|
||||
mSize = mRequestedSize;
|
||||
}
|
||||
mStream = stream;// retained so the destructor frees on the most-recently-used stream
|
||||
}
|
||||
private:
|
||||
void *mData;
|
||||
size_t mSize;
|
||||
size_t mRequestedSize;
|
||||
Resource *mResource;
|
||||
void *mData;
|
||||
size_t mSize;
|
||||
size_t mRequestedSize;
|
||||
cudaStream_t mStream;
|
||||
};// TempPool<Resource> class
|
||||
|
||||
using TempDevicePool = TempPool<DeviceResource>;
|
||||
|
||||
@@ -109,10 +109,12 @@ 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_raytrace_iso_surface")
|
||||
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)
|
||||
nanovdb_example(NAME "ex_mesh_to_grid_cuda" OPENVDB)
|
||||
|
||||
if(CUDAToolkit_FOUND)
|
||||
nanovdb_example(NAME "ex_make_mgpu_nanovdb") # requires cuRAND
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// the following files are from OpenVDB
|
||||
#include <openvdb/util/CpuTimer.h>
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
|
||||
// the following files are from NanoVDB
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
|
||||
#include <thrust/universal_vector.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
template<typename BuildT>
|
||||
void mainMeshToGrid(
|
||||
const nanovdb::Vec3f *devicePoints,
|
||||
const int pointCount,
|
||||
const nanovdb::Vec3i *deviceTriangles,
|
||||
const int triangleCount,
|
||||
const nanovdb::Map map,
|
||||
const openvdb::FloatGrid::Ptr refGrid);
|
||||
|
||||
void readOBJ(const std::string& filename,
|
||||
std::vector<openvdb::Vec3s>& points,
|
||||
std::vector<openvdb::Vec3I>& triangles,
|
||||
std::vector<openvdb::Vec4I>& quads)
|
||||
{
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
OPENVDB_THROW(openvdb::IoError, "Failed to open OBJ file: " + filename);
|
||||
}
|
||||
|
||||
std::string line;
|
||||
int lineNumber = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
lineNumber++;
|
||||
std::istringstream iss(line);
|
||||
std::string type;
|
||||
iss >> type;
|
||||
|
||||
if (type == "v") {
|
||||
float x, y, z;
|
||||
iss >> x >> y >> z;
|
||||
points.push_back(openvdb::Vec3s(x, y, z));
|
||||
} else if (type == "f") {
|
||||
std::vector<int> faceIndices;
|
||||
std::string vertexData;
|
||||
|
||||
while (iss >> vertexData) {
|
||||
// Isolate the vertex index (everything before the first slash)
|
||||
size_t slashPos = vertexData.find('/');
|
||||
std::string indexStr = vertexData.substr(0, slashPos);
|
||||
|
||||
if (indexStr.empty()) continue;
|
||||
|
||||
int raw_idx = std::stoi(indexStr);
|
||||
int actual_idx = 0;
|
||||
|
||||
// Handle negative indices: relative to the number of points parsed so far
|
||||
if (raw_idx < 0) {
|
||||
actual_idx = points.size() + raw_idx;
|
||||
} else {
|
||||
// Standard positive indices: OBJ is 1-based, convert to 0-based for C++
|
||||
actual_idx = raw_idx - 1;
|
||||
}
|
||||
|
||||
// Strict bounds checking to prevent segfaults
|
||||
if (actual_idx < 0 || actual_idx >= points.size()) {
|
||||
OPENVDB_THROW(openvdb::ValueError,
|
||||
"OBJ parse error on line " + std::to_string(lineNumber) +
|
||||
": Face index out of bounds (Raw: " + std::to_string(raw_idx) +
|
||||
", Computed: " + std::to_string(actual_idx) + ", Total Points: " +
|
||||
std::to_string(points.size()) + ")");
|
||||
}
|
||||
|
||||
faceIndices.push_back(actual_idx);
|
||||
}
|
||||
|
||||
// Add to the appropriate OpenVDB list
|
||||
if (faceIndices.size() == 3) {
|
||||
triangles.push_back(openvdb::Vec3I(faceIndices[0], faceIndices[1], faceIndices[2]));
|
||||
} else if (faceIndices.size() == 4) {
|
||||
quads.push_back(openvdb::Vec4I(faceIndices[0], faceIndices[1], faceIndices[2], faceIndices[3]));
|
||||
} else if (faceIndices.size() > 4) {
|
||||
std::cerr << "Warning on line " << lineNumber << ": Skipping face with "
|
||||
<< faceIndices.size() << " vertices. Triangulate your mesh!" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief This example depends on OpenVDB, NanoVDB, and CUDA
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
using BuildT = nanovdb::ValueOnIndex;
|
||||
|
||||
openvdb::util::CpuTimer cpuTimer;
|
||||
|
||||
try {
|
||||
|
||||
if (argc<2) OPENVDB_THROW(openvdb::ValueError, "usage: "+std::string(argv[0])+" input.obj [output.vdb]\n");
|
||||
std::string inputFile = argv[1];
|
||||
std::string outputFile = "output.vdb";
|
||||
if (argc > 2)
|
||||
outputFile = argv[2];
|
||||
float voxelSize = 0.001f;
|
||||
if (argc > 3)
|
||||
voxelSize = std::stof(argv[3]);
|
||||
|
||||
std::vector<openvdb::Vec3s> openvdb_points;
|
||||
std::vector<openvdb::Vec3I> openvdb_triangles;
|
||||
std::vector<openvdb::Vec4I> quads;
|
||||
|
||||
// Read the OBJ file
|
||||
std::cout << "Reading " << inputFile << "..." << std::endl;
|
||||
readOBJ(inputFile, openvdb_points, openvdb_triangles, quads);
|
||||
std::cout << "Loaded " << openvdb_points.size() << " vertices, "
|
||||
<< openvdb_triangles.size() << " triangles, and "
|
||||
<< quads.size() << " quads." << std::endl;
|
||||
|
||||
// Initialize OpenVDB
|
||||
openvdb::initialize();
|
||||
|
||||
// Setup Grid Transform (Voxel Size)
|
||||
openvdb::math::Transform::Ptr transform =
|
||||
openvdb::math::Transform::createLinearTransform(voxelSize);
|
||||
|
||||
// Convert Mesh to Unsigned Distance Field (UDF)
|
||||
// halfband specifies the half-width of the narrow band in voxel units
|
||||
float halfband = 3.0f;
|
||||
cpuTimer.start("Converting mesh to OpenVDB unsigned distance field");
|
||||
openvdb::FloatGrid::Ptr grid = openvdb::tools::meshToUnsignedDistanceField<openvdb::FloatGrid>(
|
||||
*transform, openvdb_points, openvdb_triangles, quads, halfband);
|
||||
cpuTimer.stop();
|
||||
|
||||
// Write the Grid to a VDB File
|
||||
grid->setName("UnsignedDistanceField");
|
||||
grid->print(std::cout, 2);
|
||||
std::cout << "Writing to " << outputFile << "..." << std::endl;
|
||||
openvdb::GridPtrVec grids;
|
||||
grids.push_back(grid);
|
||||
openvdb::io::File file(outputFile);
|
||||
file.write(grids);
|
||||
file.close();
|
||||
|
||||
// Cast the raw pointers from the std::vector data
|
||||
const auto* nano_pts_data = reinterpret_cast<const nanovdb::Vec3f*>(openvdb_points.data());
|
||||
const auto* nano_tris_data = reinterpret_cast<const nanovdb::Vec3i*>(openvdb_triangles.data());
|
||||
|
||||
// Initialize the thrust vectors using the casted pointer ranges
|
||||
thrust::universal_vector<nanovdb::Vec3f> nanovdb_points(nano_pts_data, nano_pts_data + openvdb_points.size());
|
||||
thrust::universal_vector<nanovdb::Vec3i> nanovdb_triangles(nano_tris_data, nano_tris_data + openvdb_triangles.size());
|
||||
|
||||
// Convert OpenVDB transform to nanovdb::Map
|
||||
const auto openvdb_mat4 = transform->baseMap()->getAffineMap()->getMat4();
|
||||
nanovdb::Map map;
|
||||
map.set(openvdb_mat4, openvdb_mat4.inverse());
|
||||
|
||||
mainMeshToGrid<BuildT>(
|
||||
nanovdb_points.data().get(),
|
||||
nanovdb_points.size(),
|
||||
nanovdb_triangles.data().get(),
|
||||
nanovdb_triangles.size(),
|
||||
map,
|
||||
grid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
|
||||
#include <nanovdb/tools/cuda/MeshToGrid.cuh>
|
||||
|
||||
#include <openvdb/openvdb.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
|
||||
template<typename BuildT>
|
||||
void mainMeshToGrid(
|
||||
const nanovdb::Vec3f *devicePoints,
|
||||
const int pointCount,
|
||||
const nanovdb::Vec3i *deviceTriangles,
|
||||
const int triangleCount,
|
||||
const nanovdb::Map map,
|
||||
const openvdb::FloatGrid::Ptr refGrid)
|
||||
{
|
||||
// Test topology-only path (getHandle)
|
||||
{
|
||||
nanovdb::tools::cuda::MeshToGrid<BuildT> conv(devicePoints, pointCount, deviceTriangles, triangleCount, map);
|
||||
conv.setVerbose(1);
|
||||
auto handle = conv.getHandle();
|
||||
std::cout << "[getHandle] completed, buffer size: " << handle.bufferSize() << " bytes\n";
|
||||
}
|
||||
|
||||
// Initialize mesh-to-grid converter
|
||||
nanovdb::tools::cuda::MeshToGrid<BuildT> converter( devicePoints, pointCount, deviceTriangles, triangleCount, map );
|
||||
converter.setVerbose(1);
|
||||
auto [handle, sidecar] = converter.getHandleAndUDF();
|
||||
|
||||
// --- Comparison against OpenVDB reference ---
|
||||
// Download NanoVDB grid to host for CPU-side analysis.
|
||||
std::vector<char> hostBuf(handle.bufferSize());
|
||||
cudaCheck(cudaMemcpy(hostBuf.data(), handle.deviceData(), handle.bufferSize(), cudaMemcpyDeviceToHost));
|
||||
const auto *nanoGrid = reinterpret_cast<const nanovdb::NanoGrid<BuildT>*>(hostBuf.data());
|
||||
|
||||
// Download sidecar.
|
||||
const uint64_t sidecarFloats = sidecar.size() / sizeof(float);
|
||||
std::vector<float> hostSidecar(sidecarFloats);
|
||||
cudaCheck(cudaMemcpy(hostSidecar.data(), sidecar.deviceData(),
|
||||
sidecar.size(), cudaMemcpyDeviceToHost));
|
||||
|
||||
// Encode a voxel coord as a sortable int64_t key.
|
||||
auto encodeCoord = [](int x, int y, int z) -> int64_t {
|
||||
return (int64_t(x) + (1<<20))
|
||||
| ((int64_t(y) + (1<<20)) << 21)
|
||||
| ((int64_t(z) + (1<<20)) << 42);
|
||||
};
|
||||
|
||||
auto ovdbAcc = refGrid->getConstAccessor();
|
||||
const float voxelSize = (float)refGrid->voxelSize()[0];
|
||||
|
||||
uint64_t falsePositives = 0, falseNegatives = 0;
|
||||
float minMissedUDF = std::numeric_limits<float>::max();
|
||||
uint64_t badUDF = 0;
|
||||
float maxErrVoxels = 0.f;
|
||||
|
||||
std::vector<int64_t> ourCoords;
|
||||
ourCoords.reserve(12000000);
|
||||
|
||||
const uint32_t nanoLeafCount = nanoGrid->tree().nodeCount(0);
|
||||
const auto *leaves = nanoGrid->tree().getFirstLeaf();
|
||||
for (uint32_t li = 0; li < nanoLeafCount; ++li) {
|
||||
const auto &leaf = leaves[li];
|
||||
const auto origin = leaf.origin();
|
||||
for (uint32_t vi = 0; vi < 512; ++vi) {
|
||||
if (!leaf.isActive(vi)) continue;
|
||||
const auto local = nanovdb::NanoLeaf<BuildT>::OffsetToLocalCoord(vi);
|
||||
const int x = origin[0]+local[0], y = origin[1]+local[1], z = origin[2]+local[2];
|
||||
ourCoords.push_back(encodeCoord(x, y, z));
|
||||
|
||||
const openvdb::Coord coord(x, y, z);
|
||||
if (!ovdbAcc.isValueOn(coord)) {
|
||||
++falsePositives;
|
||||
} else {
|
||||
const float err = std::abs(hostSidecar[leaf.getValue(vi)] - ovdbAcc.getValue(coord)) / voxelSize;
|
||||
maxErrVoxels = std::max(maxErrVoxels, err);
|
||||
if (err > 0.1f) ++badUDF;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(ourCoords.begin(), ourCoords.end());
|
||||
|
||||
for (auto it = refGrid->tree().cbeginValueOn(); it; ++it) {
|
||||
const auto c = it.getCoord();
|
||||
if (!std::binary_search(ourCoords.begin(), ourCoords.end(), encodeCoord(c[0], c[1], c[2]))) {
|
||||
++falseNegatives;
|
||||
minMissedUDF = std::min(minMissedUDF, *it);
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t ourActiveCount = ourCoords.size();
|
||||
const uint64_t nTP = ourActiveCount - falsePositives;
|
||||
std::cout << "\n--- Comparison against OpenVDB reference ---\n";
|
||||
std::cout << "Active voxels: ours=" << ourActiveCount
|
||||
<< " OpenVDB=" << refGrid->tree().activeVoxelCount() << "\n";
|
||||
std::cout << "False negatives: " << falseNegatives;
|
||||
if (falseNegatives > 0)
|
||||
std::cout << " (smallest missed UDF: " << minMissedUDF << ")";
|
||||
std::cout << "\n";
|
||||
std::cout << "False positives: " << falsePositives
|
||||
<< " (" << (100.0 * falsePositives / ourActiveCount) << "%)\n";
|
||||
std::cout << "UDF vs OpenVDB (true positives=" << nTP << "): "
|
||||
<< "max error=" << maxErrVoxels << " voxels, "
|
||||
<< "exceeding 0.1 voxels: " << badUDF << "\n";
|
||||
|
||||
}
|
||||
|
||||
template
|
||||
void mainMeshToGrid<nanovdb::ValueOnIndex>(
|
||||
const nanovdb::Vec3f *devicePoints,
|
||||
const int pointCount,
|
||||
const nanovdb::Vec3i *deviceTriangles,
|
||||
const int triangleCount,
|
||||
const nanovdb::Map map,
|
||||
const openvdb::FloatGrid::Ptr refGrid);
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! [openvdb_to_nanovdb_accessor]
|
||||
#include <openvdb/tools/LevelSetSphere.h> // replace with your own dependencies for generating the OpenVDB grid
|
||||
#include <nanovdb/tools/CreateNanoGrid.h> // converter from OpenVDB to NanoVDB (includes NanoVDB.h and GridManager.h)
|
||||
#include <nanovdb/io/IO.h>
|
||||
@@ -38,4 +39,5 @@ int main()
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//! [openvdb_to_nanovdb_accessor]
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include "ComputePrimitives.h"
|
||||
|
||||
struct RenderOp;
|
||||
template<typename GridT>
|
||||
__global__ void renderIsoSurfacePersistentKernel(RenderOp renderOp, float* image, const GridT* grid, int numPixels, int* nextPixel);
|
||||
|
||||
struct RenderOp
|
||||
{
|
||||
using Vec3T = nanovdb::math::Vec3<float>;
|
||||
using RayT = nanovdb::math::Ray<float>;
|
||||
int mWidth, mHeight;
|
||||
float mDx, mIso, mWBBoxDimZ;
|
||||
Vec3T mWBBoxCenter;
|
||||
|
||||
template<typename BufferT>
|
||||
RenderOp(nanovdb::GridHandle<BufferT>& handle, int width, int height)
|
||||
{
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
const auto *metaData = handle.gridMetaData();
|
||||
mDx = float(metaData->voxelSize()[0]);
|
||||
mIso = mDx;
|
||||
mWBBoxDimZ = (float)metaData->worldBBox().dim()[2] * 2;
|
||||
mWBBoxCenter = Vec3T(metaData->worldBBox().min() + metaData->worldBBox().dim() * 0.5f);
|
||||
}
|
||||
|
||||
template<typename GridT>
|
||||
inline float renderImage(bool useCuda, float* image, const GridT* grid)
|
||||
{
|
||||
using ClockT = std::chrono::high_resolution_clock;
|
||||
auto t0 = ClockT::now();
|
||||
|
||||
computeForEach(
|
||||
useCuda, mWidth * mHeight, 512, __FILE__, __LINE__, [this, image, grid] __hostdev__(int start, int end) {
|
||||
(*this)(start, end, image, grid);
|
||||
});
|
||||
computeSync(useCuda, __FILE__, __LINE__);
|
||||
|
||||
auto t1 = ClockT::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() / 1000.f;
|
||||
return duration;
|
||||
}
|
||||
|
||||
template <typename GridT>
|
||||
inline __hostdev__ void operator()(int start, int end, float* image, const GridT* grid) const
|
||||
{
|
||||
static_assert(nanovdb::util::is_same<typename GridT::BuildType, float, nanovdb::ValueOnIndex, nanovdb::ValueIndex>::value, "only works for float and OnIndex grids");
|
||||
auto acc = nanovdb::getAccessor<GridT, float>(*grid);
|
||||
for (int i = start; i < end; ++i) {
|
||||
this->renderPixel(i, image, grid, acc);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename GridT, typename AccT>
|
||||
inline __hostdev__ void renderPixel(int i, float* image, const GridT* grid, AccT& acc) const
|
||||
{
|
||||
float t0, v;
|
||||
nanovdb::Coord ijk;
|
||||
RayT iRay = this->getIndexRay(i, grid);
|
||||
if (nanovdb::math::isoCrossing(iRay, acc, ijk, v, t0, mIso)) {// intersect...
|
||||
this->composite(image, i, (t0 * mDx) / (mWBBoxDimZ * 2), 1.0f);
|
||||
} else {
|
||||
this->composite(image, i, 0.0f, 0.0f);// write background value.
|
||||
}
|
||||
}
|
||||
|
||||
template<typename GridT>
|
||||
inline float renderImagePersistent(float* image, const GridT* grid, int* nextPixel) const
|
||||
{
|
||||
int device = 0;
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaGetDevice(&device), __FILE__, __LINE__);
|
||||
|
||||
cudaDeviceProp properties;
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaGetDeviceProperties(&properties, device), __FILE__, __LINE__);
|
||||
|
||||
constexpr int blockSize = 256;
|
||||
// Launch a small, fixed pool of blocks that persists on the GPU and
|
||||
// pulls pixel work from a global counter instead of launching one
|
||||
// logical thread per pixel up front.
|
||||
int blockCount = properties.multiProcessorCount * 4;
|
||||
if (blockCount < 1) blockCount = 1;
|
||||
|
||||
// Reset the work queue before each timed render. The kernel advances
|
||||
// this counter by one warp of pixels at a time.
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaMemset(nextPixel, 0, sizeof(int)), __FILE__, __LINE__);
|
||||
|
||||
using ClockT = std::chrono::high_resolution_clock;
|
||||
auto t0 = ClockT::now();
|
||||
|
||||
const int numPixels = mWidth * mHeight;
|
||||
renderIsoSurfacePersistentKernel<GridT><<<blockCount, blockSize>>>(*this, image, grid, numPixels, nextPixel);
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaGetLastError(), __FILE__, __LINE__);
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaDeviceSynchronize(), __FILE__, __LINE__);
|
||||
|
||||
auto t1 = ClockT::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() / 1000.f;
|
||||
return duration;
|
||||
}
|
||||
|
||||
template <typename GridT>
|
||||
inline __hostdev__ RayT getIndexRay(int i, const GridT *grid) const
|
||||
{
|
||||
// perspective camera along Z-axis...
|
||||
const uint32_t x = i % mWidth, y = i / mWidth;
|
||||
const float fov = 45.f;
|
||||
const float u = (float(x) + 0.5f) / mWidth;
|
||||
const float v = (float(y) + 0.5f) / mHeight;
|
||||
const float aspect = mWidth / float(mHeight);
|
||||
const float Px = (2.f * u - 1.f) * tanf(fov / 2 * 3.14159265358979323846f / 180.f) * aspect;
|
||||
const float Py = (2.f * v - 1.f) * tanf(fov / 2 * 3.14159265358979323846f / 180.f);
|
||||
const Vec3T origin = mWBBoxCenter + Vec3T(0, 0, mWBBoxDimZ);
|
||||
Vec3T dir(Px, Py, -1.f);
|
||||
dir.normalize();
|
||||
RayT wRay(origin, dir);
|
||||
return wRay.worldToIndexF(*grid);// transform the ray to the grid's index-space.
|
||||
}
|
||||
|
||||
inline __hostdev__ void composite(float* outImage, int offset, float value, float alpha) const
|
||||
{
|
||||
const uint32_t x = offset % mWidth, y = offset / mWidth;
|
||||
|
||||
// checkerboard background...
|
||||
const int mask = 1 << 7;
|
||||
const float bg = ((x & mask) ^ (y & mask)) ? 1.0f : 0.5f;
|
||||
outImage[offset] = alpha * value + (1.0f - alpha) * bg;
|
||||
}
|
||||
|
||||
inline void saveImage(const std::string& filename, const float* image) const
|
||||
{
|
||||
const auto isLittleEndian = []() -> bool {
|
||||
static int x = 1;
|
||||
static bool result = reinterpret_cast<uint8_t*>(&x)[0] == 1;
|
||||
return result;
|
||||
};
|
||||
|
||||
float scale = 1.0f;
|
||||
if (isLittleEndian()) scale = -scale;
|
||||
|
||||
std::fstream fs(filename, std::ios::out | std::ios::binary);
|
||||
if (!fs.is_open()) throw std::runtime_error("Unable to open file: " + filename);
|
||||
|
||||
fs << "Pf\n"
|
||||
<< mWidth << "\n"
|
||||
<< mHeight << "\n"
|
||||
<< scale << "\n";
|
||||
|
||||
for (int i = 0; i < mWidth * mHeight; ++i) {
|
||||
float r = image[i];
|
||||
fs.write((char*)&r, sizeof(float));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename GridT>
|
||||
__global__ void renderIsoSurfacePersistentKernel(RenderOp renderOp, float* image, const GridT* grid, int numPixels, int* nextPixel)
|
||||
{
|
||||
static_assert(nanovdb::util::is_same<typename GridT::BuildType, float, nanovdb::ValueOnIndex, nanovdb::ValueIndex>::value, "only works for float and OnIndex grids");
|
||||
auto acc = nanovdb::getAccessor<GridT, float>(*grid);
|
||||
const unsigned int lane = threadIdx.x & 31u;
|
||||
|
||||
// Keep the fixed set of launched threads busy until all pixels have been assigned.
|
||||
while (true) {
|
||||
int base = 0;
|
||||
// Each warp asks the shared counter for the next batch of 32 pixels.
|
||||
// Only lane 0 updates the counter; __shfl_sync copies lane 0's result
|
||||
// to the other lanes in the warp.
|
||||
if (lane == 0) base = atomicAdd(nextPixel, 32);
|
||||
base = __shfl_sync(0xFFFFFFFFu, base, 0);
|
||||
|
||||
// Each lane renders one pixel from the batch: lane 0 renders base,
|
||||
// lane 1 renders base + 1, and so on.
|
||||
const int i = base + int(lane);
|
||||
if (i >= numPixels) break;
|
||||
|
||||
renderOp.renderPixel(i, image, grid, acc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <nanovdb/io/IO.h>
|
||||
#include <nanovdb/tools/CreatePrimitives.h>
|
||||
#include <nanovdb/cuda/DeviceBuffer.h>
|
||||
|
||||
#if defined(NANOVDB_USE_CUDA)
|
||||
using BufferT = nanovdb::cuda::DeviceBuffer;
|
||||
#else
|
||||
using BufferT = nanovdb::HostBuffer;
|
||||
#endif
|
||||
|
||||
extern void runNanoVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int width, int height, BufferT& imageBuffer, bool usePersistentThreads);
|
||||
|
||||
int main(int ac, char** av)
|
||||
{
|
||||
try {
|
||||
bool usePersistentThreads = false;
|
||||
const char* gridName = nullptr;
|
||||
for (int i = 1; i < ac; ++i) {
|
||||
if (std::strcmp(av[i], "--persistent") == 0) {
|
||||
usePersistentThreads = true;
|
||||
} else if (!gridName) {
|
||||
gridName = av[i];
|
||||
} else {
|
||||
throw std::runtime_error("Usage: ex_raytrace_iso_surface [--persistent] [grid.nvdb]");
|
||||
}
|
||||
}
|
||||
nanovdb::GridHandle<BufferT> handle;
|
||||
if (gridName) {
|
||||
handle = nanovdb::io::readGrid<BufferT>(gridName);
|
||||
std::cout << "Loaded NanoVDB grid[" << handle.gridMetaData()->shortGridName() << "]...\n";
|
||||
} else {
|
||||
handle = nanovdb::tools::createLevelSetSphere<float, BufferT>(100.0f, nanovdb::Vec3d(-20, 0, 0), 1.0, 3.0, nanovdb::Vec3d(0), "sphere");
|
||||
}
|
||||
|
||||
const int numIterations = 50;
|
||||
const int width = 4096;
|
||||
const int height = 4096;
|
||||
BufferT imageBuffer(width * height * sizeof(float));
|
||||
|
||||
runNanoVDB(handle, numIterations, width, height, imageBuffer, usePersistentThreads);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
|
||||
#if defined(NANOVDB_USE_CUDA)
|
||||
#include <nanovdb/cuda/DeviceBuffer.h>
|
||||
using BufferT = nanovdb::cuda::DeviceBuffer;
|
||||
#else
|
||||
using BufferT = nanovdb::HostBuffer;
|
||||
#endif
|
||||
#include <nanovdb/GridHandle.h>
|
||||
#include <nanovdb/io/IO.h>
|
||||
#include <nanovdb/math/Ray.h>
|
||||
#include <nanovdb/math/HDDA.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void runNanoVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int width, int height, BufferT& imageBuffer, bool usePersistentThreads)
|
||||
{
|
||||
float *h_outImage = reinterpret_cast<float*>(imageBuffer.data());
|
||||
RenderOp renderOp(handle, width, height);
|
||||
|
||||
auto kernel = [&](auto *h_grid){
|
||||
float sum = 0;
|
||||
for (int i = 0; i < numIterations; ++i, sum += renderOp.renderImage(false/*useCuda*/, h_outImage, h_grid));
|
||||
std::cout << "Average of " << numIterations << " renderings (NanoVDB-Host) = " << (sum/numIterations) << " ms" << std::endl;
|
||||
renderOp.saveImage("raytrace_iso_surface-nanovdb-host.pfm", (float*)imageBuffer.data());
|
||||
|
||||
#if defined(NANOVDB_USE_CUDA)
|
||||
handle.deviceUpload();
|
||||
using BuildT = typename nanovdb::util::remove_pointer_t<decltype(h_grid)>::BuildType;
|
||||
auto* d_grid = handle.deviceGrid<BuildT>();
|
||||
if (!d_grid) throw std::runtime_error("GridHandle does not contain a valid device grid");
|
||||
imageBuffer.deviceUpload();
|
||||
float* d_outImage = reinterpret_cast<float*>(imageBuffer.deviceData());
|
||||
sum = 0;
|
||||
if (usePersistentThreads) {
|
||||
int* d_nextPixel = nullptr;
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaMalloc(&d_nextPixel, sizeof(int)), __FILE__, __LINE__);
|
||||
for (int i = 0; i < numIterations; ++i, sum += renderOp.renderImagePersistent(d_outImage, d_grid, d_nextPixel));
|
||||
NANOVDB_CUDA_CHECK_ERROR(cudaFree(d_nextPixel), __FILE__, __LINE__);
|
||||
std::cout << "Average of " << numIterations << " renderings (NanoVDB-Cuda-Persistent) = " << (sum/numIterations) << " ms " << std::endl;
|
||||
imageBuffer.deviceDownload();
|
||||
renderOp.saveImage("raytrace_iso_surface-nanovdb-cuda-persistent.pfm", (float*)imageBuffer.data());
|
||||
} else {
|
||||
for (int i = 0; i < numIterations; ++i, sum += renderOp.renderImage(true/*useCuda*/, d_outImage, d_grid));
|
||||
std::cout << "Average of " << numIterations << " renderings (NanoVDB-Cuda) = " << (sum/numIterations) << " ms " << std::endl;
|
||||
imageBuffer.deviceDownload();
|
||||
renderOp.saveImage("raytrace_iso_surface-nanovdb-cuda.pfm", (float*)imageBuffer.data());
|
||||
}
|
||||
#endif
|
||||
};// kernel
|
||||
|
||||
if (auto *h_grid = handle.grid<float>()) {
|
||||
kernel(h_grid);
|
||||
} else if (auto *h_grid = handle.grid<nanovdb::ValueIndex>()) {
|
||||
kernel(h_grid);
|
||||
} else if (auto *h_grid = handle.grid<nanovdb::ValueOnIndex>()) {
|
||||
kernel(h_grid);
|
||||
} else {
|
||||
throw std::runtime_error("GridHandle does not contain a valid device grid");
|
||||
}
|
||||
}// runNanoVDB
|
||||
@@ -20,15 +20,14 @@ using BufferT = nanovdb::HostBuffer;
|
||||
|
||||
void runNanoVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int width, int height, BufferT& imageBuffer)
|
||||
{
|
||||
using GridT = nanovdb::FloatGrid;
|
||||
using GridT = nanovdb::FloatGrid;
|
||||
using CoordT = nanovdb::Coord;
|
||||
using RealT = float;
|
||||
using Vec3T = nanovdb::math::Vec3<RealT>;
|
||||
using RayT = nanovdb::math::Ray<RealT>;
|
||||
using RealT = float;
|
||||
using Vec3T = nanovdb::math::Vec3<RealT>;
|
||||
using RayT = nanovdb::math::Ray<RealT>;
|
||||
|
||||
auto *h_grid = handle.grid<float>();
|
||||
if (!h_grid)
|
||||
throw std::runtime_error("GridHandle does not contain a valid host grid");
|
||||
if (!h_grid) throw std::runtime_error("GridHandle does not contain a valid host grid");
|
||||
|
||||
float* h_outImage = reinterpret_cast<float*>(imageBuffer.data());
|
||||
|
||||
@@ -58,7 +57,7 @@ void runNanoVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int wid
|
||||
float t0;
|
||||
CoordT ijk;
|
||||
float v;
|
||||
if (nanovdb::math::ZeroCrossing(iRay, acc, ijk, v, t0)) {
|
||||
if (nanovdb::math::zeroCrossing(iRay, acc, ijk, v, t0)) {
|
||||
// write distance to surface. (we assume it is a uniform voxel)
|
||||
float wT0 = t0 * float(grid->voxelSize()[0]);
|
||||
compositeOp(image, i, width, height, wT0 / (wBBoxDimZ * 2), 1.0f);
|
||||
|
||||
+4
-1
@@ -1,5 +1,7 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! [read_nanovdb_sphere_accessor]
|
||||
#include <nanovdb/io/IO.h> // this is required to read (and write) NanoVDB files on the host
|
||||
|
||||
/// @brief Read a NanoVDB grid from a file and print out multiple values.
|
||||
@@ -24,4 +26,5 @@ int main()
|
||||
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//! [read_nanovdb_sphere_accessor]
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! [read_nanovdb_sphere_accessor_cuda]
|
||||
#include <nanovdb/io/IO.h> // this is required to read (and write) NanoVDB files on the host
|
||||
#include <nanovdb/cuda/DeviceBuffer.h> // required for CUDA memory management
|
||||
#include <nanovdb/GridHandle.h>
|
||||
@@ -38,4 +39,5 @@ int main(int, char**)
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//! [read_nanovdb_sphere_accessor_cuda]
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! [read_nanovdb_sphere_accessor_cuda_kernel]
|
||||
#include <nanovdb/NanoVDB.h> // this defined the core tree data structure of NanoVDB accessable on both the host and device
|
||||
#include <stdio.h> // for printf
|
||||
|
||||
@@ -33,4 +34,5 @@ extern "C" void launch_kernels(const nanovdb::NanoGrid<float>* deviceGrid,
|
||||
|
||||
// Launch the host "kernel" (synchronously)
|
||||
cpu_kernel(cpuGrid);
|
||||
}
|
||||
}
|
||||
//! [read_nanovdb_sphere_accessor_cuda_kernel]
|
||||
@@ -19,24 +19,6 @@
|
||||
#include <tuple>
|
||||
|
||||
|
||||
// forward compatibility for C++14 Standard Library
|
||||
namespace cxx14 {
|
||||
template<std::size_t...>
|
||||
struct index_sequence
|
||||
{
|
||||
};
|
||||
|
||||
template<std::size_t N, std::size_t... Is>
|
||||
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...>
|
||||
{
|
||||
};
|
||||
|
||||
template<std::size_t... Is>
|
||||
struct make_index_sequence<0, Is...> : index_sequence<Is...>
|
||||
{
|
||||
};
|
||||
} // namespace cxx14
|
||||
|
||||
#if defined(__CUDACC__)
|
||||
|
||||
static inline bool checkCUDA(cudaError_t result, const char* file, const int line)
|
||||
@@ -75,7 +57,7 @@ public:
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
void call(int start, int end, cxx14::index_sequence<Is...>) const
|
||||
void call(int start, int end, std::index_sequence<Is...>) const
|
||||
{
|
||||
mFunc(start, end, std::get<Is>(mArgs)...);
|
||||
}
|
||||
@@ -86,7 +68,7 @@ public:
|
||||
int end = i * mBlockSize + mBlockSize;
|
||||
if (end > mCount)
|
||||
end = mCount;
|
||||
call(start, end, cxx14::make_index_sequence<sizeof...(Args)>());
|
||||
call(start, end, std::make_index_sequence<sizeof...(Args)>());
|
||||
}
|
||||
|
||||
#if defined(NANOVDB_USE_TBB)
|
||||
@@ -96,7 +78,7 @@ public:
|
||||
int end = r.end();
|
||||
if (end > mCount)
|
||||
end = mCount;
|
||||
call(start, end, cxx14::make_index_sequence<sizeof...(Args)>());
|
||||
call(start, end, std::make_index_sequence<sizeof...(Args)>());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ private:
|
||||
Vec3T mDelta, mNext; // delta time and next time
|
||||
}; // class HDDA
|
||||
|
||||
/////////////////////////////////////////// ZeroCrossing ////////////////////////////////////////////
|
||||
/////////////////////////////////////////// zeroCrossing ////////////////////////////////////////////
|
||||
|
||||
/// @brief returns true if the ray intersects a zero-crossing at the voxel level of the grid in the accessor
|
||||
/// The empty-space ray-marching is performed at all levels of the tree using an
|
||||
@@ -187,8 +187,9 @@ private:
|
||||
/// voxel after the intersection point, v contains the grid values at ijk, and t is set to the time of
|
||||
/// the intersection along the ray.
|
||||
template<typename RayT, typename AccT>
|
||||
inline __hostdev__ bool ZeroCrossing(RayT& ray, AccT& acc, Coord& ijk, typename AccT::ValueType& v, float& t)
|
||||
inline __hostdev__ bool zeroCrossing(RayT& ray, AccT& acc, Coord& ijk, typename AccT::ValueType& v, float& t)
|
||||
{
|
||||
static_assert(util::is_floating_point<typename AccT::ValueType>::value, "zeroCrossing assumed a grid with floating point values");
|
||||
if (!ray.clip(acc.root().bbox()) || ray.t1() > 1e20)
|
||||
return false; // clip ray to bbox
|
||||
static const float Delta = 1.0001f;
|
||||
@@ -210,8 +211,42 @@ inline __hostdev__ bool ZeroCrossing(RayT& ray, AccT& acc, Coord& ijk, typename
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}// zeroCrossing
|
||||
|
||||
template<typename RayT, typename AccT>
|
||||
[[deprecated("Use zeroCrossing(ray, acc, ijk, v, t)")]]
|
||||
inline __hostdev__ bool ZeroCrossing(RayT& ray, AccT& acc, Coord& ijk, typename AccT::ValueType& v, float& t)
|
||||
{
|
||||
return zeroCrossing(ray, acc, ijk, v,t);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////// isoCrossing ////////////////////////////////////////////
|
||||
|
||||
template<typename RayT, typename AccT>
|
||||
inline __hostdev__ bool isoCrossing(RayT& ray, AccT& acc, Coord& ijk, typename AccT::ValueType& v, float& t, const typename AccT::ValueType& iso = 0.0f)
|
||||
{
|
||||
static_assert(util::is_floating_point<typename AccT::ValueType>::value, "isoCrossing assumed a grid with floating point values");
|
||||
if (!ray.clip(acc.root().bbox()) || ray.t1() > 1e20) return false; // clip ray to bbox
|
||||
static const float Delta = 1.0001f;
|
||||
ijk = RoundDown<Coord>(ray.start()); // first hit of bbox
|
||||
HDDA<RayT, Coord> hdda(ray, acc.getDim(ijk, ray));
|
||||
const auto v0 = acc.getValue(ijk) - iso;
|
||||
while (hdda.step()) {
|
||||
ijk = RoundDown<Coord>(ray(hdda.time() + Delta));
|
||||
hdda.update(ray, acc.getDim(ijk, ray));
|
||||
if (hdda.dim() > 1 || !acc.isActive(ijk)) continue; // either a tile value or an inactive voxel
|
||||
while (hdda.step() && acc.isActive(hdda.voxel())) { // in the narrow band
|
||||
v = acc.getValue(hdda.voxel()) - iso;
|
||||
if (v * v0 < 0) { // zero crossing
|
||||
ijk = hdda.voxel();
|
||||
t = hdda.time();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}// isoCrossing
|
||||
|
||||
/////////////////////////////////////////// DDA ////////////////////////////////////////////
|
||||
|
||||
/// @brief A Digital Differential Analyzer. Unlike HDDA (defined above) this DDA
|
||||
@@ -343,10 +378,10 @@ private:
|
||||
Vec3T mDelta, mNext; // delta time and next time
|
||||
}; // class DDA
|
||||
|
||||
/////////////////////////////////////////// ZeroCrossingNode ////////////////////////////////////////////
|
||||
/////////////////////////////////////////// zeroCrossingNode ////////////////////////////////////////////
|
||||
|
||||
template<typename RayT, typename NodeT>
|
||||
inline __hostdev__ bool ZeroCrossingNode(RayT& ray, const NodeT& node, float v0, nanovdb::math::Coord& ijk, float& v, float& t)
|
||||
inline __hostdev__ bool zeroCrossingNode(RayT& ray, const NodeT& node, float v0, nanovdb::math::Coord& ijk, float& v, float& t)
|
||||
{
|
||||
math::BBox<Coord> bbox(node.origin(), node.origin() + Coord(node.dim() - 1));
|
||||
|
||||
@@ -376,6 +411,13 @@ inline __hostdev__ bool ZeroCrossingNode(RayT& ray, const NodeT& node, float v0,
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}// zeroCrossingNode
|
||||
|
||||
template<typename RayT, typename NodeT>
|
||||
[[deprecated("Use zeroCrossingNode(ray, node, v0, ijk, v, t)")]]
|
||||
inline __hostdev__ bool ZeroCrossingNode(RayT& ray, const NodeT& node, float v0, nanovdb::math::Coord& ijk, float& v, float& t)
|
||||
{
|
||||
return zeroCrossingNode(ray, node, v0, ijk, v, t);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////// TreeMarcher ////////////////////////////////////////////
|
||||
@@ -399,7 +441,7 @@ inline __hostdev__ bool firstActive(RayT& ray, AccT& acc, Coord &ijk, float& t)
|
||||
ijk = RoundDown<Coord>( ray(t) );// update ijk
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}// firstActive
|
||||
|
||||
/////////////////////////////////////////// TreeMarcher ////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*!
|
||||
\file Proximity.h
|
||||
|
||||
\author Efty Sifakis
|
||||
|
||||
\brief Closest-point queries on geometric primitives,
|
||||
suitable for use in both host and device code.
|
||||
*/
|
||||
|
||||
#ifndef NANOVDB_MATH_PROXIMITY_H_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_MATH_PROXIMITY_H_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <nanovdb/util/Util.h> // for __hostdev__
|
||||
#include <nanovdb/math/Math.h> // for nanovdb::math::Sqrt
|
||||
|
||||
namespace nanovdb {
|
||||
|
||||
namespace math {
|
||||
|
||||
/// @brief Returns the closest point on the line segment [v0,v1] to @a p.
|
||||
///
|
||||
/// The result lies on the closed segment (t clamped to [0,1]).
|
||||
///
|
||||
/// @tparam Vec3T Any Vec3 type supporting arithmetic and dot().
|
||||
template<typename Vec3T>
|
||||
__hostdev__ inline Vec3T
|
||||
closestPointOnSegmentToPoint(const Vec3T &v0, const Vec3T &v1, const Vec3T &p)
|
||||
{
|
||||
const Vec3T seg = v1 - v0;
|
||||
const Vec3T w = p - v0;
|
||||
|
||||
const auto c1 = seg.dot(w);
|
||||
if (c1 <= 0) return v0;
|
||||
|
||||
const auto c2 = seg.dot(seg);
|
||||
if (c2 <= c1) return v1;
|
||||
|
||||
return v0 + (c1 / c2) * seg;
|
||||
}
|
||||
|
||||
/// @brief Returns the closest point on triangle [v0,v1,v2] to @a p,
|
||||
/// and (via @a t0, @a t1) the barycentric coordinates of that
|
||||
/// point: closest = v0 + t0*(v1-v0) + t1*(v2-v0).
|
||||
///
|
||||
/// Uses Voronoi-region decomposition (Ericson, "Real-Time Collision
|
||||
/// Detection", Sec. 5.1.5). Degenerate triangles (zero area, collinear or
|
||||
/// coincident vertices) are handled implicitly: the face-interior test
|
||||
/// naturally fails and the code falls through to the nearest edge or
|
||||
/// vertex result.
|
||||
///
|
||||
/// @tparam Vec3T Any Vec3 type supporting arithmetic and dot().
|
||||
template<typename Vec3T>
|
||||
__hostdev__ inline Vec3T
|
||||
closestPointOnTriangleToPoint(
|
||||
const Vec3T &v0,
|
||||
const Vec3T &v1,
|
||||
const Vec3T &v2,
|
||||
const Vec3T &p,
|
||||
typename Vec3T::ValueType &t0,
|
||||
typename Vec3T::ValueType &t1)
|
||||
{
|
||||
using RealT = typename Vec3T::ValueType;
|
||||
|
||||
const Vec3T ab = v1 - v0;
|
||||
const Vec3T ac = v2 - v0;
|
||||
const Vec3T ap = p - v0;
|
||||
|
||||
// --- Region A (vertex v0) ---
|
||||
const RealT d1 = ab.dot(ap);
|
||||
const RealT d2 = ac.dot(ap);
|
||||
if (d1 <= RealT(0) && d2 <= RealT(0)) {
|
||||
t0 = RealT(0);
|
||||
t1 = RealT(0);
|
||||
return v0;
|
||||
}
|
||||
|
||||
// --- Region B (vertex v1) ---
|
||||
const Vec3T bp = p - v1;
|
||||
const RealT d3 = ab.dot(bp);
|
||||
const RealT d4 = ac.dot(bp);
|
||||
if (d3 >= RealT(0) && d4 <= d3) {
|
||||
t0 = RealT(1);
|
||||
t1 = RealT(0);
|
||||
return v1;
|
||||
}
|
||||
|
||||
// --- Region AB (edge v0-v1) ---
|
||||
const RealT vc = d1 * d4 - d3 * d2;
|
||||
if (vc <= RealT(0) && d1 >= RealT(0) && d3 <= RealT(0)) {
|
||||
t0 = d1 / (d1 - d3);
|
||||
t1 = RealT(0);
|
||||
return v0 + t0 * ab;
|
||||
}
|
||||
|
||||
// --- Region C (vertex v2) ---
|
||||
const Vec3T cp = p - v2;
|
||||
const RealT d5 = ab.dot(cp);
|
||||
const RealT d6 = ac.dot(cp);
|
||||
if (d6 >= RealT(0) && d5 <= d6) {
|
||||
t0 = RealT(0);
|
||||
t1 = RealT(1);
|
||||
return v2;
|
||||
}
|
||||
|
||||
// --- Region AC (edge v0-v2) ---
|
||||
const RealT vb = d5 * d2 - d1 * d6;
|
||||
if (vb <= RealT(0) && d2 >= RealT(0) && d6 <= RealT(0)) {
|
||||
t1 = d2 / (d2 - d6);
|
||||
t0 = RealT(0);
|
||||
return v0 + t1 * ac;
|
||||
}
|
||||
|
||||
// --- Region BC (edge v1-v2) ---
|
||||
const RealT va = d3 * d6 - d5 * d4;
|
||||
if (va <= RealT(0) && (d4 - d3) >= RealT(0) && (d5 - d6) >= RealT(0)) {
|
||||
t1 = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
t0 = RealT(1) - t1;
|
||||
return v1 + t1 * (v2 - v1);
|
||||
}
|
||||
|
||||
// --- Interior of triangle ---
|
||||
const RealT denom = RealT(1) / (va + vb + vc);
|
||||
t0 = vb * denom;
|
||||
t1 = vc * denom;
|
||||
return v0 + t0 * ab + t1 * ac;
|
||||
}
|
||||
|
||||
/// @brief Returns the squared distance from @a p to the closest point
|
||||
/// on triangle [v0,v1,v2].
|
||||
template<typename Vec3T>
|
||||
__hostdev__ inline typename Vec3T::ValueType
|
||||
pointToTriangleDistSqr(
|
||||
const Vec3T &v0,
|
||||
const Vec3T &v1,
|
||||
const Vec3T &v2,
|
||||
const Vec3T &p)
|
||||
{
|
||||
typename Vec3T::ValueType t0, t1;
|
||||
const Vec3T closest = closestPointOnTriangleToPoint(v0, v1, v2, p, t0, t1);
|
||||
return (p - closest).lengthSqr();
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
|
||||
} // namespace nanovdb
|
||||
|
||||
#endif // NANOVDB_MATH_PROXIMITY_H_HAS_BEEN_INCLUDED
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// X-macro list of NanoVDB BuildT types currently exposed to Python.
|
||||
//
|
||||
// NanoVDB coding style says "avoid macro functions; use inline and templates
|
||||
// instead" (docs/codingstyle.txt). This file is a deliberate exception: it
|
||||
// must emit top-level template instantiations and binding registrations into
|
||||
// several translation units from one canonical list, which a C++ template
|
||||
// cannot do. The pattern is contained to this file plus the matching
|
||||
// per-consumer macro definitions; no runtime behavior is hidden behind a
|
||||
// macro function.
|
||||
//
|
||||
// Consumers define one or more of the kind-specific macros below before
|
||||
// including this file. Each macro is invoked once per matching type; any
|
||||
// macro the consumer does not define is treated as a no-op and reset after
|
||||
// the file is processed. This is the single point that must be edited when
|
||||
// adding (or removing) a Python-visible BuildT.
|
||||
//
|
||||
// Macros:
|
||||
// NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum)
|
||||
// Scalar value types with arithmetic semantics — exposed with full
|
||||
// NodeInfo + setVoxel accessors. Suffix forms Python class names
|
||||
// (e.g. "Float" -> "FloatGrid"). GridTypeEnum names the
|
||||
// nanovdb::GridType:: enumerator a grid of this BuildT carries
|
||||
// (used by the polymorphic handle.grid(n) dispatch).
|
||||
//
|
||||
// NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum)
|
||||
// Vector value types — exposed with a setVoxel accessor but no
|
||||
// NodeInfo. AccessorName is passed explicitly because the legacy
|
||||
// Python class names for the original two (Vec3f, RGBA8) are
|
||||
// inconsistent — kept as-is for backwards compatibility.
|
||||
//
|
||||
// NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum)
|
||||
// The nanovdb::Point build type — exposed with a bare accessor.
|
||||
// Point grids carry GridType::PointIndex (NOT a GridType::Point;
|
||||
// that enumerator doesn't exist), so GridTypeEnum is given
|
||||
// explicitly. Polymorphic dispatch routes PointIndex grids to
|
||||
// NanoGrid<Point>.
|
||||
//
|
||||
// NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum)
|
||||
// BuildTs whose nanovdb::SetVoxel<T> is unavailable (bool, the
|
||||
// quantized Fp4/Fp8/Fp16/FpN types, the index types ValueIndex /
|
||||
// ValueOnIndex, and ValueMask). nanovdb::BuildTraits<T>::is_special
|
||||
// is true for all of these. Exposed with a bare accessor only — no
|
||||
// setVoxel, no NodeInfo. The Python accessor's getValue() returns
|
||||
// the type given by nanovdb::BuildToValueMap<T>::Type — float for
|
||||
// Fp4 / Fp8 / Fp16 / FpN, uint64 for ValueIndex / ValueOnIndex,
|
||||
// bool for ValueMask and for the BuildT=bool (Boolean) grid.
|
||||
//
|
||||
// NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix)
|
||||
// Subset that has C++ sampler specializations (used by PyMath
|
||||
// samplers and PyCreateNanoGrid create*Grid factories).
|
||||
|
||||
#ifndef NANOVDB_PY_FOR_EACH_SCALAR_BUILDT
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum)
|
||||
#define NANOVDB_PY_LOCAL_DEFINED_SCALAR
|
||||
#endif
|
||||
#ifndef NANOVDB_PY_FOR_EACH_VECTOR_BUILDT
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum)
|
||||
#define NANOVDB_PY_LOCAL_DEFINED_VECTOR
|
||||
#endif
|
||||
#ifndef NANOVDB_PY_FOR_EACH_POINT_BUILDT
|
||||
#define NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum)
|
||||
#define NANOVDB_PY_LOCAL_DEFINED_POINT
|
||||
#endif
|
||||
#ifndef NANOVDB_PY_FOR_EACH_READONLY_BUILDT
|
||||
#define NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum)
|
||||
#define NANOVDB_PY_LOCAL_DEFINED_READONLY
|
||||
#endif
|
||||
#ifndef NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT
|
||||
#define NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix)
|
||||
#define NANOVDB_PY_LOCAL_DEFINED_SAMPLEABLE
|
||||
#endif
|
||||
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(float, Float, Float)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(double, Double, Double)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(int16_t, Int16, Int16)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(int32_t, Int32, Int32)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(int64_t, Int64, Int64)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(uint8_t, UInt8, UInt8)
|
||||
NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(uint32_t, UInt32, UInt32)
|
||||
// nanovdb::Half is intentionally not bound. The source declares it as
|
||||
// `class Half{};` (an empty placeholder for IEEE 754 half-precision, see
|
||||
// NanoVDB.h around line 180) and the C++ ProbeValue<Half> chain is
|
||||
// inconsistent — leaf storage carries Half but the ProbeValue specialization
|
||||
// expects float, causing a type mismatch during instantiation. When the
|
||||
// upstream Half implementation lands this can be added in a follow-up.
|
||||
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec3f, Vec3f,
|
||||
"Vec3fReadVectorAccessor", Vec3f)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec3d, Vec3d,
|
||||
"Vec3dReadAccessor", Vec3d)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec4f, Vec4f,
|
||||
"Vec4fReadAccessor", Vec4f)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec4d, Vec4d,
|
||||
"Vec4dReadAccessor", Vec4d)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec3u8, Vec3u8,
|
||||
"Vec3u8ReadAccessor", Vec3u8)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::Vec3u16, Vec3u16,
|
||||
"Vec3u16ReadAccessor", Vec3u16)
|
||||
NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(::nanovdb::math::Rgba8, RGBA8,
|
||||
"RGBA8ReadAccessor", RGBA8)
|
||||
|
||||
NANOVDB_PY_FOR_EACH_POINT_BUILDT(::nanovdb::Point, Point, PointIndex)
|
||||
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(bool, Boolean, Boolean)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::Fp4, Fp4, Fp4)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::Fp8, Fp8, Fp8)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::Fp16, Fp16, Fp16)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::FpN, FpN, FpN)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::ValueIndex, Index, Index)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::ValueOnIndex, OnIndex, OnIndex)
|
||||
NANOVDB_PY_FOR_EACH_READONLY_BUILDT(::nanovdb::ValueMask, Mask, Mask)
|
||||
|
||||
NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(float, Float)
|
||||
NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(double, Double)
|
||||
NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(int32_t, Int32)
|
||||
NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(::nanovdb::Vec3f, Vec3f)
|
||||
|
||||
#ifdef NANOVDB_PY_LOCAL_DEFINED_SCALAR
|
||||
#undef NANOVDB_PY_LOCAL_DEFINED_SCALAR
|
||||
#endif
|
||||
#ifdef NANOVDB_PY_LOCAL_DEFINED_VECTOR
|
||||
#undef NANOVDB_PY_LOCAL_DEFINED_VECTOR
|
||||
#endif
|
||||
#ifdef NANOVDB_PY_LOCAL_DEFINED_POINT
|
||||
#undef NANOVDB_PY_LOCAL_DEFINED_POINT
|
||||
#endif
|
||||
#ifdef NANOVDB_PY_LOCAL_DEFINED_READONLY
|
||||
#undef NANOVDB_PY_LOCAL_DEFINED_READONLY
|
||||
#endif
|
||||
#ifdef NANOVDB_PY_LOCAL_DEFINED_SAMPLEABLE
|
||||
#undef NANOVDB_PY_LOCAL_DEFINED_SAMPLEABLE
|
||||
#endif
|
||||
|
||||
#undef NANOVDB_PY_FOR_EACH_SCALAR_BUILDT
|
||||
#undef NANOVDB_PY_FOR_EACH_VECTOR_BUILDT
|
||||
#undef NANOVDB_PY_FOR_EACH_POINT_BUILDT
|
||||
#undef NANOVDB_PY_FOR_EACH_READONLY_BUILDT
|
||||
#undef NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT
|
||||
@@ -4,8 +4,19 @@ option(NANOVDB_BUILD_PYTHON_UNITTESTS [=[
|
||||
"Include the NanoVDB Python unit test. Requires a python interpreter]=]
|
||||
${NANOVDB_BUILD_UNITTESTS})
|
||||
|
||||
# Stubs are only consumed from an installed wheel (they ship next to the .so
|
||||
# in nanovdb/), so default ON only when SKBUILD is driving the build. In an
|
||||
# in-source dev / OpenVDB-CI build the .pyi is not used and stub generation
|
||||
# can be skipped — which also dodges the macOS CI's sanitizer-runtime
|
||||
# DYLD_INSERT_LIBRARIES wrapper, under which Python dlopen'ing the .so
|
||||
# triggers TSan "Interceptors are not working" and aborts stubgen.
|
||||
option(NANOVDB_BUILD_PYTHON_STUBS
|
||||
"Generate .pyi type stubs for the nanovdb Python module (requires nanobind 2.x or newer)."
|
||||
${SKBUILD})
|
||||
|
||||
nanobind_add_module(nanovdb_python NB_STATIC
|
||||
NanoVDBModule.cc
|
||||
PyBuildGrid.cc
|
||||
PyCreateNanoGrid.cc
|
||||
PyGridChecksum.cc
|
||||
PyGridHandle.cc
|
||||
@@ -18,6 +29,8 @@ nanobind_add_module(nanovdb_python NB_STATIC
|
||||
PyPrimitives.cc
|
||||
PySampleFromVoxels.cc
|
||||
PyTools.cc
|
||||
PyTree.cc
|
||||
PyVoxelBlockManager.cc
|
||||
cuda/PyDeviceBuffer.cc
|
||||
cuda/PyDeviceGridHandle.cu
|
||||
cuda/PyPointsToGrid.cu
|
||||
@@ -33,10 +46,47 @@ if(SKBUILD)
|
||||
set_target_properties(nanovdb_python PROPERTIES INSTALL_RPATH "$ORIGIN/../../openvdb/lib")
|
||||
install(TARGETS nanovdb_python DESTINATION ${NANOVDB_INSTALL_LIBDIR})
|
||||
install(FILES __init__.py DESTINATION nanovdb)
|
||||
# Ship the nanovdb C/C++ headers inside the Python wheel so downstream
|
||||
# extension authors can compile against the same NanoVDB the wheel was
|
||||
# built with. nanovdb.get_include() resolves to this directory at runtime.
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
DESTINATION nanovdb/include/nanovdb
|
||||
FILES_MATCHING
|
||||
PATTERN "*.h"
|
||||
PATTERN "*.cuh"
|
||||
PATTERN "python" EXCLUDE
|
||||
PATTERN "examples" EXCLUDE
|
||||
PATTERN "unittest" EXCLUDE
|
||||
PATTERN "cmd" EXCLUDE
|
||||
PATTERN "docs" EXCLUDE)
|
||||
else()
|
||||
install(TARGETS nanovdb_python DESTINATION ${VDB_PYTHON_INSTALL_DIRECTORY})
|
||||
endif()
|
||||
|
||||
# .pyi type stubs for IDE / type-checker support. Driven by the CMake helper
|
||||
# that ships with nanobind 2.x; silently skipped on older nanobind where the
|
||||
# helper does not exist.
|
||||
if(NANOVDB_BUILD_PYTHON_STUBS AND COMMAND nanobind_add_stub)
|
||||
set(NANOVDB_STUB_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
nanobind_add_stub(nanovdb_python_stub
|
||||
MODULE nanovdb
|
||||
OUTPUT "${NANOVDB_STUB_OUTPUT_DIR}/nanovdb.pyi"
|
||||
PYTHON_PATH $<TARGET_FILE_DIR:nanovdb_python>
|
||||
DEPENDS nanovdb_python
|
||||
MARKER_FILE "${NANOVDB_STUB_OUTPUT_DIR}/py.typed"
|
||||
INCLUDE_PRIVATE)
|
||||
if(SKBUILD)
|
||||
install(FILES
|
||||
"${NANOVDB_STUB_OUTPUT_DIR}/nanovdb.pyi"
|
||||
"${NANOVDB_STUB_OUTPUT_DIR}/py.typed"
|
||||
DESTINATION nanovdb)
|
||||
endif()
|
||||
elseif(NANOVDB_BUILD_PYTHON_STUBS)
|
||||
message(STATUS
|
||||
"NANOVDB_BUILD_PYTHON_STUBS is ON but nanobind_add_stub is unavailable "
|
||||
"(nanobind >= 2.0 required). Skipping stub generation.")
|
||||
endif()
|
||||
|
||||
# pytest
|
||||
if(NANOVDB_BUILD_PYTHON_UNITTESTS)
|
||||
|
||||
@@ -49,12 +99,19 @@ if(NANOVDB_BUILD_PYTHON_UNITTESTS)
|
||||
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/TestNanoVDB.py -v
|
||||
WORKING_DIRECTORY "${NANOVDB_PYTHON_WORKING_DIR}")
|
||||
|
||||
# Smoke-run every example script (they self-skip when optional
|
||||
# dependencies such as NumPy or OpenVDB are unavailable).
|
||||
add_test(NAME pytest_nanovdb_examples
|
||||
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/TestExamples.py -v
|
||||
WORKING_DIRECTORY "${NANOVDB_PYTHON_WORKING_DIR}")
|
||||
set_tests_properties(pytest_nanovdb_examples PROPERTIES TIMEOUT 300)
|
||||
|
||||
if(WIN32)
|
||||
set(PYTHONPATH "$ENV{PYTHONPATH};${NANOVDB_PYTHON_WORKING_DIR}")
|
||||
string(REPLACE "\\;" ";" PYTHONPATH "${PYTHONPATH}")
|
||||
string(REPLACE ";" "\\;" PYTHONPATH "${PYTHONPATH}")
|
||||
set_tests_properties(pytest_nanovdb PROPERTIES ENVIRONMENT "PYTHONPATH=${PYTHONPATH}")
|
||||
set_tests_properties(pytest_nanovdb pytest_nanovdb_examples PROPERTIES ENVIRONMENT "PYTHONPATH=${PYTHONPATH}")
|
||||
else()
|
||||
set_tests_properties(pytest_nanovdb PROPERTIES ENVIRONMENT "PYTHONPATH=$ENV{PYTHONPATH}:${NANOVDB_PYTHON_WORKING_DIR}")
|
||||
set_tests_properties(pytest_nanovdb pytest_nanovdb_examples PROPERTIES ENVIRONMENT "PYTHONPATH=$ENV{PYTHONPATH}:${NANOVDB_PYTHON_WORKING_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
+1001
-194
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "PyBuildGrid.h"
|
||||
|
||||
#include <nanobind/stl/array.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include <nanovdb/HostBuffer.h>
|
||||
#include <nanovdb/tools/CreateNanoGrid.h>
|
||||
#include <nanovdb/tools/GridBuilder.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
using namespace nanovdb;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// Bind nanovdb::tools::build::Grid<BuildT> together with its
|
||||
// ValueAccessor<BuildT> and Tree<BuildT>::WriteAccessor proxies, plus a
|
||||
// .to_nanovdb() shortcut that bakes the build grid into a host NanoGrid
|
||||
// via tools::createNanoGrid.
|
||||
//
|
||||
// One instantiation per writable BuildT in BuildTypes.def (scalars +
|
||||
// vectors). All three classes live under the nanovdb.tools.build submodule;
|
||||
// the Python class names mirror the existing typed-grid naming so
|
||||
// nanovdb.tools.build.FloatGrid is the mutable counterpart of the read-only
|
||||
// nanovdb.FloatGrid.
|
||||
template<typename BuildT>
|
||||
static void defineBuildGrid(nb::module_& m,
|
||||
const char* gridName,
|
||||
const char* valueAccName,
|
||||
const char* writeAccName)
|
||||
{
|
||||
using GridT = tools::build::Grid<BuildT>;
|
||||
using TreeT = tools::build::Tree<BuildT>;
|
||||
using AccT = tools::build::ValueAccessor<BuildT>;
|
||||
using WriteAccT = typename TreeT::WriteAccessor;
|
||||
using ValueT = typename GridT::ValueType;
|
||||
|
||||
// ----- build::Grid<BuildT> -----
|
||||
nb::class_<GridT>(m, gridName,
|
||||
"Mutable host-side grid builder for this BuildT. Write voxels via "
|
||||
"setValue / getAccessor / getWriteAccessor, then call to_nanovdb() "
|
||||
"to bake a frozen NanoGrid GridHandle.")
|
||||
.def(nb::init<const ValueT&, const std::string&, GridClass>(),
|
||||
"background"_a,
|
||||
"name"_a = std::string(""),
|
||||
"gridClass"_a = GridClass::Unknown,
|
||||
"Construct an empty mutable build grid. Voxels read as "
|
||||
"background until written.")
|
||||
.def("getValue",
|
||||
[](const GridT& self, const Coord& ijk) -> ValueT {
|
||||
return self.getValue(ijk);
|
||||
},
|
||||
"ijk"_a,
|
||||
"Return the value at ijk (background if no leaf covers it).")
|
||||
.def("setValue",
|
||||
[](GridT& self, const Coord& ijk, const ValueT& value) {
|
||||
self.setValue(ijk, value);
|
||||
},
|
||||
"ijk"_a, "value"_a,
|
||||
"Set the voxel value at ijk and mark the voxel active.")
|
||||
// build::Grid has no top-level isActive(ijk); the read path is via
|
||||
// ValueAccessor. Spin up a fresh accessor for the single query so
|
||||
// Python callers don't have to.
|
||||
.def("isActive",
|
||||
[](GridT& self, const Coord& ijk) {
|
||||
AccT acc = self.getAccessor();
|
||||
return acc.isActive(ijk);
|
||||
},
|
||||
"ijk"_a,
|
||||
"Return True iff ijk is in an active voxel. Equivalent to "
|
||||
"self.getAccessor().isActive(ijk), but allocates a fresh "
|
||||
"accessor for each call — for repeated queries use "
|
||||
"self.getAccessor() and reuse it.")
|
||||
.def("setValueOn",
|
||||
[](GridT& self, const Coord& ijk) {
|
||||
AccT acc = self.getAccessor();
|
||||
acc.setValueOn(ijk);
|
||||
},
|
||||
"ijk"_a,
|
||||
"Mark ijk active without changing the stored value. Equivalent "
|
||||
"to self.getAccessor().setValueOn(ijk).")
|
||||
.def("nodeCount",
|
||||
[](const GridT& self) -> std::array<size_t, 3> {
|
||||
return self.nodeCount();
|
||||
},
|
||||
"Return a 3-tuple (leaf_count, lower_count, upper_count).")
|
||||
.def("gridType", &GridT::gridType,
|
||||
"Return the GridType enumerator this BuildT carries.")
|
||||
.def("gridClass", &GridT::gridClass,
|
||||
"Return the GridClass assigned at construction time.")
|
||||
.def("getName", &GridT::getName,
|
||||
"Return the grid name (as passed at construction).")
|
||||
.def("setName", &GridT::setName, "name"_a,
|
||||
"Replace the grid name.")
|
||||
.def("setTransform", &GridT::setTransform,
|
||||
"scale"_a = 1.0,
|
||||
"translation"_a = Vec3d(0.0),
|
||||
"Set an affine index-to-world map from a uniform scale and "
|
||||
"translation. Replaces any prior transform.")
|
||||
.def_prop_ro("background",
|
||||
[](const GridT& self) -> ValueT {
|
||||
return self.mRoot.background();
|
||||
},
|
||||
"The background value supplied at construction.")
|
||||
.def("getAccessor", &GridT::getAccessor,
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a ValueAccessor wired to this grid's root. Thread-safe "
|
||||
"for reads, NOT thread-safe for writes. The accessor borrows "
|
||||
"from this grid — the grid must outlive it.")
|
||||
// WriteAccessor's defaulted move constructor would leave its
|
||||
// internal ValueAccessor's `mRoot&` reference dangling (it points
|
||||
// into the moved-from WriteAccessor's own mRoot field). Bypass
|
||||
// the move path entirely by heap-allocating and handing nanobind
|
||||
// ownership — the C++ object stays at a stable address for its
|
||||
// whole lifetime.
|
||||
.def("getWriteAccessor",
|
||||
[](GridT& self) -> WriteAccT* {
|
||||
return new WriteAccT(self.mRoot, self.mMutex);
|
||||
},
|
||||
nb::rv_policy::take_ownership,
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a WriteAccessor for thread-safe writes; the accessor "
|
||||
"buffers changes into a private root and merges them into the "
|
||||
"parent grid on destruction (or on an explicit merge() call). "
|
||||
"Held by nanobind on the heap so the accessor's internal "
|
||||
"references stay valid.")
|
||||
// Baking a large grid is the most expensive operation on this
|
||||
// class; release the GIL so other Python threads can run during
|
||||
// the conversion (the lambda only touches C++ state).
|
||||
.def("to_nanovdb",
|
||||
[](const GridT& self,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
int verbose) {
|
||||
return tools::createNanoGrid<GridT, BuildT, HostBuffer>(
|
||||
self, sMode, cMode, verbose);
|
||||
},
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"verbose"_a = 0,
|
||||
"Bake this mutable grid into a host NanoGrid<BuildT> and "
|
||||
"return its GridHandle. The build grid is unchanged. "
|
||||
"Releases the GIL during conversion.");
|
||||
|
||||
// ----- build::ValueAccessor<BuildT> -----
|
||||
//
|
||||
// Move-only (copy is deleted) — returned by getAccessor(). Caches the
|
||||
// last leaf / lower / upper node it touched, so repeated access to
|
||||
// neighboring coordinates is fast.
|
||||
nb::class_<AccT>(m, valueAccName,
|
||||
"Move-only read/write accessor returned by build.Grid.getAccessor(). "
|
||||
"Caches the most recently visited tree path for fast neighbor access.")
|
||||
.def("getValue",
|
||||
[](const AccT& self, const Coord& ijk) -> ValueT {
|
||||
return self.getValue(ijk);
|
||||
},
|
||||
"ijk"_a, "Return the value at ijk (uses the cache).")
|
||||
.def("setValue",
|
||||
[](AccT& self, const Coord& ijk, const ValueT& value) {
|
||||
self.setValue(ijk, value);
|
||||
},
|
||||
"ijk"_a, "value"_a,
|
||||
"Set the value at ijk and mark it active (uses the cache).")
|
||||
.def("setValueOn",
|
||||
[](AccT& self, const Coord& ijk) { self.setValueOn(ijk); },
|
||||
"ijk"_a,
|
||||
"Mark ijk active without changing the stored value.")
|
||||
.def("isActive",
|
||||
[](const AccT& self, const Coord& ijk) {
|
||||
return self.isActive(ijk);
|
||||
},
|
||||
"ijk"_a,
|
||||
"Return True iff ijk is in an active voxel.")
|
||||
.def("isValueOn",
|
||||
[](const AccT& self, const Coord& ijk) {
|
||||
return self.isValueOn(ijk);
|
||||
},
|
||||
"ijk"_a,
|
||||
"Alias for isActive(ijk).");
|
||||
|
||||
// ----- build::Tree<BuildT>::WriteAccessor -----
|
||||
//
|
||||
// Move-only. Holds its own root node + a reference to the parent root's
|
||||
// mutex; on destruction (or explicit merge()) it locks the mutex and
|
||||
// splices its buffered nodes into the parent. Designed for multi-thread
|
||||
// writes — one WriteAccessor per thread, no shared mutable state.
|
||||
nb::class_<WriteAccT>(m, writeAccName,
|
||||
"Thread-safe write accessor returned by build.Grid.getWriteAccessor(). "
|
||||
"Buffers writes into a private root and merges them into the parent "
|
||||
"grid on destruction or explicit merge().")
|
||||
.def("setValue",
|
||||
[](WriteAccT& self, const Coord& ijk, const ValueT& value) {
|
||||
self.setValue(ijk, value);
|
||||
},
|
||||
"ijk"_a, "value"_a,
|
||||
"Buffer a set into this accessor's private root.")
|
||||
.def("setValueOn",
|
||||
[](WriteAccT& self, const Coord& ijk) { self.setValueOn(ijk); },
|
||||
"ijk"_a,
|
||||
"Buffer an active-state set into this accessor's private root.")
|
||||
.def("merge", &WriteAccT::merge,
|
||||
"Lock the parent mutex and splice this accessor's buffered "
|
||||
"nodes into the parent grid. Called automatically when the "
|
||||
"accessor is destroyed; calling it explicitly is only "
|
||||
"necessary if you want the changes visible to the parent "
|
||||
"before the accessor goes out of scope.");
|
||||
}
|
||||
|
||||
void defineBuildGridModule(nb::module_& toolsModule)
|
||||
{
|
||||
nb::module_ buildModule = toolsModule.def_submodule("build");
|
||||
buildModule.doc() =
|
||||
"Mutable, voxel-by-voxel CPU grid builder mirroring "
|
||||
"nanovdb::tools::build::*. Construct a typed Grid (e.g. "
|
||||
"FloatGrid(0.0, 'mygrid')), populate it with setValue / "
|
||||
"ValueAccessor / WriteAccessor, then call .to_nanovdb() to bake "
|
||||
"a host NanoGrid handle.";
|
||||
|
||||
// X-macro instantiation over every writable BuildT: scalars (full
|
||||
// arithmetic) and vectors. Read-only special BuildTs (Boolean, Fp*,
|
||||
// Index, Mask) and Point are deliberately excluded — they have no
|
||||
// SetValue<T> specialization and can't be built voxel-by-voxel.
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
defineBuildGrid<T>(buildModule, #Suffix "Grid", \
|
||||
#Suffix "ValueAccessor", #Suffix "WriteAccessor");
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
defineBuildGrid<T>(buildModule, #Suffix "Grid", \
|
||||
#Suffix "ValueAccessor", #Suffix "WriteAccessor");
|
||||
#include "BuildTypes.def"
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#ifndef NANOVDB_PYBUILDGRID_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_PYBUILDGRID_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
/// @brief Register the nanovdb.tools.build submodule and its per-BuildT
|
||||
/// Grid / ValueAccessor / WriteAccessor classes (one set per writable
|
||||
/// scalar and vector BuildT in BuildTypes.def). Constructs the submodule
|
||||
/// as `toolsModule.def_submodule("build")`.
|
||||
void defineBuildGridModule(nb::module_& toolsModule);
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,10 @@
|
||||
#include <nanovdb/tools/CreateNanoGrid.h>
|
||||
#include <nanovdb/tools/GridBuilder.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
using namespace nanovdb;
|
||||
@@ -35,20 +39,553 @@ GridHandle<HostBuffer> createNanoGridFromFunc(const BuildT&
|
||||
|
||||
template<typename BuildT> void defineCreateNanoGrid(nb::module_& m, const char* name)
|
||||
{
|
||||
m.def(name, &createNanoGridFromFunc<BuildT>, nb::call_guard<nb::gil_scoped_release>(), "background"_a, "name"_a, "gridClass"_a, "func"_a, "bbox"_a);
|
||||
m.def(name, &createNanoGridFromFunc<BuildT>, nb::call_guard<nb::gil_scoped_release>(), "background"_a, "name"_a, "gridClass"_a, "func"_a, "bbox"_a,
|
||||
"Construct a NanoGrid by evaluating func(Coord) over every voxel in bbox. "
|
||||
"Returns a GridHandle owning the freshly-built grid.");
|
||||
}
|
||||
|
||||
template<typename BufferT> void defineOpenToNanoVDB(nb::module_& m)
|
||||
{
|
||||
#ifdef NANOVDB_USE_OPENVDB
|
||||
m.def("openToNanoVDB", &tools::openToNanoVDB<BufferT>, "base"_a, "sMode"_a = tools::StatsMode::Default, "cMode"_a = CheckMode::Default, "verbose"_a = 0);
|
||||
m.def("openToNanoVDB", &tools::openToNanoVDB<BufferT>,
|
||||
"base"_a,
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"verbose"_a = 0,
|
||||
"Convert an OpenVDB base grid to a NanoVDB GridHandle.");
|
||||
#endif
|
||||
}
|
||||
|
||||
template void defineCreateNanoGrid<float>(nb::module_&, const char*);
|
||||
template void defineCreateNanoGrid<double>(nb::module_&, const char*);
|
||||
template void defineCreateNanoGrid<int32_t>(nb::module_&, const char*);
|
||||
template void defineCreateNanoGrid<Vec3f>(nb::module_&, const char*);
|
||||
// ============================================================================
|
||||
// Conversion bindings: AbsDiff/RelDiff oracle classes, and the polymorphic
|
||||
// createNanoGrid free functions for quantized + index destination BuildTs.
|
||||
// Each accepts source = NanoGrid<SrcBuildT> OR build::Grid<SrcBuildT>.
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
|
||||
// ----- Quantized (Fp4/Fp8/Fp16) -----
|
||||
//
|
||||
// C++ signature: createNanoGrid<SrcGridT, DstBuildT, BufferT>(srcGrid,
|
||||
// sMode, cMode, ditherOn, verbose, buffer).
|
||||
//
|
||||
// Try SrcBuildT against both NanoGrid<SrcBuildT> and build::Grid<SrcBuildT>
|
||||
// and return an empty nb::object on no match so the caller can fall through
|
||||
// to the next SrcBuildT.
|
||||
//
|
||||
// GIL is held for the isinstance / cast dispatch (which touches the Python
|
||||
// object's type and reference graph) but released around the underlying
|
||||
// tools::createNanoGrid traversal — the source data lives in stable C++
|
||||
// storage whose lifetime is anchored by the Python wrapper passed in via
|
||||
// py_src, so it's safe to read without holding the GIL.
|
||||
template<typename SrcBuildT, typename DstBuildT>
|
||||
nb::object tryQuantizeFpX(nb::handle py_src,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
bool ditherOn,
|
||||
int verbose)
|
||||
{
|
||||
using NanoSrcT = NanoGrid<SrcBuildT>;
|
||||
using BuildSrcT = tools::build::Grid<SrcBuildT>;
|
||||
if (nb::isinstance<NanoSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const NanoSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<NanoSrcT, DstBuildT, HostBuffer>(
|
||||
src, sMode, cMode, ditherOn, verbose);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
if (nb::isinstance<BuildSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const BuildSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<BuildSrcT, DstBuildT, HostBuffer>(
|
||||
src, sMode, cMode, ditherOn, verbose);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
return nb::object();
|
||||
}
|
||||
|
||||
template<typename DstBuildT>
|
||||
nb::object createNanoGridFpX(nb::handle py_src,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
bool ditherOn,
|
||||
int verbose,
|
||||
const char* pyFnName)
|
||||
{
|
||||
// The C++ Fp{4,8,16,N} preProcess static_asserts SrcValueT == float;
|
||||
// double sources hit a compile-time error, so we accept float only.
|
||||
if (auto r = tryQuantizeFpX<float, DstBuildT>(
|
||||
py_src, sMode, cMode, ditherOn, verbose); r.is_valid()) return r;
|
||||
std::string msg(pyFnName);
|
||||
msg += ": source must be a FloatGrid or nanovdb.tools.build.FloatGrid "
|
||||
"(Fp4/Fp8/Fp16/FpN require a float source value type).";
|
||||
throw nb::type_error(msg.c_str());
|
||||
}
|
||||
|
||||
// ----- FpN (variable bit-width) -----
|
||||
//
|
||||
// C++ signature: createNanoGrid<SrcGridT, FpN, OracleT, BufferT>(srcGrid,
|
||||
// sMode, cMode, ditherOn, verbose, oracle, buffer). OracleT is AbsDiff or
|
||||
// RelDiff; the binding exposes both as separate Python overloads.
|
||||
template<typename SrcBuildT, typename OracleT>
|
||||
nb::object tryQuantizeFpN(nb::handle py_src,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
bool ditherOn,
|
||||
int verbose,
|
||||
const OracleT& oracle)
|
||||
{
|
||||
using NanoSrcT = NanoGrid<SrcBuildT>;
|
||||
using BuildSrcT = tools::build::Grid<SrcBuildT>;
|
||||
// Same GIL pattern as tryQuantizeFpX: hold the GIL through the
|
||||
// isinstance / cast dispatch, release it for the conversion.
|
||||
if (nb::isinstance<NanoSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const NanoSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<NanoSrcT, FpN, OracleT, HostBuffer>(
|
||||
src, sMode, cMode, ditherOn, verbose, oracle);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
if (nb::isinstance<BuildSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const BuildSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<BuildSrcT, FpN, OracleT, HostBuffer>(
|
||||
src, sMode, cMode, ditherOn, verbose, oracle);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
return nb::object();
|
||||
}
|
||||
|
||||
template<typename OracleT>
|
||||
nb::object createNanoGridFpNImpl(nb::handle py_src,
|
||||
const OracleT& oracle,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
bool ditherOn,
|
||||
int verbose)
|
||||
{
|
||||
if (auto r = tryQuantizeFpN<float, OracleT>(
|
||||
py_src, sMode, cMode, ditherOn, verbose, oracle); r.is_valid()) return r;
|
||||
throw nb::type_error(
|
||||
"createNanoGridFpN: source must be a FloatGrid or "
|
||||
"nanovdb.tools.build.FloatGrid (FpN requires a float source value type).");
|
||||
}
|
||||
|
||||
// ----- Index / OnIndex -----
|
||||
//
|
||||
// C++ signature: createNanoGrid<SrcGridT, DstBuildT, BufferT>(srcGrid,
|
||||
// channels, includeStats, includeTiles, verbose, buffer). DstBuildT is
|
||||
// ValueIndex or ValueOnIndex; the binding exposes both as separate
|
||||
// named functions. Source set is wider than the quantized variants —
|
||||
// any arithmetic or vector source can be re-cast as an index grid.
|
||||
template<typename SrcBuildT, typename DstBuildT>
|
||||
nb::object tryIndexify(nb::handle py_src,
|
||||
uint32_t channels,
|
||||
bool includeStats,
|
||||
bool includeTiles,
|
||||
int verbose)
|
||||
{
|
||||
using NanoSrcT = NanoGrid<SrcBuildT>;
|
||||
using BuildSrcT = tools::build::Grid<SrcBuildT>;
|
||||
// Same GIL pattern as tryQuantizeFpX.
|
||||
if (nb::isinstance<NanoSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const NanoSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<NanoSrcT, DstBuildT, HostBuffer>(
|
||||
src, channels, includeStats, includeTiles, verbose);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
if (nb::isinstance<BuildSrcT>(py_src)) {
|
||||
const auto& src = nb::cast<const BuildSrcT&>(py_src);
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
handle = tools::createNanoGrid<BuildSrcT, DstBuildT, HostBuffer>(
|
||||
src, channels, includeStats, includeTiles, verbose);
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
return nb::object();
|
||||
}
|
||||
|
||||
template<typename DstBuildT>
|
||||
nb::object createIndexImpl(nb::handle py_src,
|
||||
uint32_t channels,
|
||||
bool includeStats,
|
||||
bool includeTiles,
|
||||
int verbose,
|
||||
const char* pyFnName)
|
||||
{
|
||||
if (auto r = tryIndexify<float, DstBuildT>(py_src, channels, includeStats, includeTiles, verbose); r.is_valid()) return r;
|
||||
if (auto r = tryIndexify<double, DstBuildT>(py_src, channels, includeStats, includeTiles, verbose); r.is_valid()) return r;
|
||||
if (auto r = tryIndexify<int32_t, DstBuildT>(py_src, channels, includeStats, includeTiles, verbose); r.is_valid()) return r;
|
||||
if (auto r = tryIndexify<Vec3f, DstBuildT>(py_src, channels, includeStats, includeTiles, verbose); r.is_valid()) return r;
|
||||
std::string msg(pyFnName);
|
||||
msg += ": source must be a FloatGrid, DoubleGrid, Int32Grid, "
|
||||
"Vec3fGrid, or the matching nanovdb.tools.build.* mutable grid.";
|
||||
throw nb::type_error(msg.c_str());
|
||||
}
|
||||
|
||||
// ----- tools.CreateNanoGrid: converter class with blind-data authoring -----
|
||||
//
|
||||
// Mirrors nanovdb::tools::CreateNanoGrid<SrcGridT>. The C++ class is
|
||||
// templated on the source grid type, so this binding stores the source
|
||||
// Python object plus the recorded settings and addBlindData() calls, then
|
||||
// dispatches over the supported SrcBuildTs when getHandle() is called —
|
||||
// constructing the C++ converter, replaying the recorded state, and baking
|
||||
// the handle. The destination BuildT is the source BuildT (the C++
|
||||
// default); quantized and index destinations remain on the
|
||||
// createNanoGridFp* / createNanoGridIndex free functions above. Authored
|
||||
// channels come back zeroed — fill them through the writable NumPy view
|
||||
// returned by grid.getBlindData(n).
|
||||
|
||||
// Byte size of one element of the given blind-data GridType, or 0 when the
|
||||
// size cannot be derived (the caller must then pass it explicitly). Matches
|
||||
// the per-type size table enforced by GridBlindMetaData::isValid().
|
||||
uint32_t blindDataTypeSize(GridType dataType)
|
||||
{
|
||||
switch (dataType) {
|
||||
case GridType::Float: return 4u;
|
||||
case GridType::Double: return 8u;
|
||||
case GridType::Int16: return 2u;
|
||||
case GridType::Int32: return 4u;
|
||||
case GridType::Int64: return 8u;
|
||||
case GridType::UInt8: return 1u;
|
||||
case GridType::UInt32: return 4u;
|
||||
case GridType::Half: return 2u;
|
||||
case GridType::RGBA8: return 4u;
|
||||
case GridType::Fp8: return 1u;
|
||||
case GridType::Fp16: return 2u;
|
||||
case GridType::Vec3f: return 12u;
|
||||
case GridType::Vec3d: return 24u;
|
||||
case GridType::Vec4f: return 16u;
|
||||
case GridType::Vec4d: return 32u;
|
||||
case GridType::Vec3u8: return 3u;
|
||||
case GridType::Vec3u16: return 6u;
|
||||
default: return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
class PyCreateNanoGrid
|
||||
{
|
||||
struct BlindDataSpec
|
||||
{
|
||||
std::string name;
|
||||
GridBlindDataSemantic semantic;
|
||||
GridBlindDataClass dataClass;
|
||||
GridType dataType;
|
||||
uint64_t count;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit PyCreateNanoGrid(nb::object src)
|
||||
: mSrc(std::move(src))
|
||||
{
|
||||
if (!(matches<float>() || matches<double>() ||
|
||||
matches<int32_t>() || matches<Vec3f>())) {
|
||||
throw nb::type_error(
|
||||
"CreateNanoGrid: source must be a FloatGrid, DoubleGrid, "
|
||||
"Int32Grid, Vec3fGrid, or the matching "
|
||||
"nanovdb.tools.build.* mutable grid.");
|
||||
}
|
||||
}
|
||||
|
||||
// Validates eagerly (the C++ ctor only NANOVDB_ASSERTs, which release
|
||||
// builds skip) so mistakes surface here rather than as an invalid grid.
|
||||
uint64_t addBlindData(const std::string& name,
|
||||
uint64_t count,
|
||||
GridType dataType,
|
||||
GridBlindDataSemantic semantic,
|
||||
GridBlindDataClass dataClass,
|
||||
uint32_t size)
|
||||
{
|
||||
if (name.size() >= GridBlindMetaData::MaxNameSize) {
|
||||
throw nb::value_error(
|
||||
"addBlindData: name exceeds the 255 character limit.");
|
||||
}
|
||||
if (size == 0u) size = blindDataTypeSize(dataType);
|
||||
if (size == 0u) {
|
||||
throw nb::value_error(
|
||||
"addBlindData: the element size cannot be derived from this "
|
||||
"dataType — pass size explicitly.");
|
||||
}
|
||||
const GridBlindMetaData meta(0, count, size, semantic, dataClass, dataType);
|
||||
if (!meta.isValid()) {
|
||||
throw nb::value_error(
|
||||
"addBlindData: invalid combination of dataSemantic, "
|
||||
"dataClass, dataType, and size.");
|
||||
}
|
||||
mBlind.push_back(BlindDataSpec{name, semantic, dataClass, dataType, count, size});
|
||||
return static_cast<uint64_t>(mBlind.size() - 1);
|
||||
}
|
||||
|
||||
void setStats(tools::StatsMode mode) { mStats = mode; }
|
||||
void setChecksum(CheckMode mode) { mChecksum = mode; }
|
||||
void setVerbose(int mode) { mVerbose = mode; }
|
||||
void enableDithering(bool on) { mDither = on; }
|
||||
|
||||
nb::object getHandle() const
|
||||
{
|
||||
if (auto r = tryGetHandle<float>(); r.is_valid()) return r;
|
||||
if (auto r = tryGetHandle<double>(); r.is_valid()) return r;
|
||||
if (auto r = tryGetHandle<int32_t>(); r.is_valid()) return r;
|
||||
if (auto r = tryGetHandle<Vec3f>(); r.is_valid()) return r;
|
||||
throw nb::type_error("CreateNanoGrid: unsupported source grid type.");
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename SrcBuildT> bool matches() const
|
||||
{
|
||||
return nb::isinstance<NanoGrid<SrcBuildT>>(mSrc) ||
|
||||
nb::isinstance<tools::build::Grid<SrcBuildT>>(mSrc);
|
||||
}
|
||||
|
||||
template<typename SrcBuildT> nb::object tryGetHandle() const
|
||||
{
|
||||
using NanoSrcT = NanoGrid<SrcBuildT>;
|
||||
using BuildSrcT = tools::build::Grid<SrcBuildT>;
|
||||
if (nb::isinstance<NanoSrcT>(mSrc)) return this->bake(nb::cast<const NanoSrcT&>(mSrc));
|
||||
if (nb::isinstance<BuildSrcT>(mSrc)) return this->bake(nb::cast<const BuildSrcT&>(mSrc));
|
||||
return nb::object();
|
||||
}
|
||||
|
||||
// Same GIL pattern as tryQuantizeFpX: the dispatch above runs with the
|
||||
// GIL held, the traversal runs without it (the source's lifetime is
|
||||
// anchored by mSrc).
|
||||
template<typename SrcGridT> nb::object bake(const SrcGridT& src) const
|
||||
{
|
||||
GridHandle<HostBuffer> handle;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
tools::CreateNanoGrid<SrcGridT> converter(src);
|
||||
converter.setStats(mStats);
|
||||
converter.setChecksum(mChecksum);
|
||||
converter.setVerbose(mVerbose);
|
||||
converter.enableDithering(mDither);
|
||||
for (const auto& b : mBlind) {
|
||||
converter.addBlindData(b.name, b.semantic, b.dataClass, b.dataType,
|
||||
static_cast<size_t>(b.count),
|
||||
static_cast<size_t>(b.size));
|
||||
}
|
||||
handle = converter.getHandle();
|
||||
// The C++ converter allocates authored channels without clearing
|
||||
// them (C++ callers memcpy their payload in). Zero-fill here so
|
||||
// the NumPy view starts deterministic. The authored channels are
|
||||
// the first mBlind.size() blind-data entries — any converter-
|
||||
// added channel (e.g. a long grid name) is appended after them.
|
||||
if (!mBlind.empty()) {
|
||||
if (auto* dst = const_cast<GridData*>(handle.gridData())) {
|
||||
for (size_t i = 0; i < mBlind.size(); ++i) {
|
||||
const GridBlindMetaData* meta = dst->blindMetaData(uint32_t(i));
|
||||
std::memset(const_cast<void*>(meta->blindData()), 0,
|
||||
meta->blindDataSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nb::cast(std::move(handle));
|
||||
}
|
||||
|
||||
nb::object mSrc;
|
||||
std::vector<BlindDataSpec> mBlind;
|
||||
tools::StatsMode mStats = tools::StatsMode::Default;
|
||||
CheckMode mChecksum = CheckMode::Default;
|
||||
int mVerbose = 0;
|
||||
bool mDither = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void defineCreateNanoGridConversions(nb::module_& toolsModule)
|
||||
{
|
||||
// ------ Oracle classes ------
|
||||
nb::class_<tools::AbsDiff>(toolsModule, "AbsDiff",
|
||||
"Compression oracle for FpN: accept the approximation when "
|
||||
"|exact - approx| <= tolerance. A tolerance of -1.0 (the "
|
||||
"default) means uninitialized; any non-negative value (including "
|
||||
"0.0) is treated as initialized by the operator bool() check, "
|
||||
"or the C++ create function can fill it in via init().")
|
||||
.def(nb::init<float>(), "tolerance"_a = -1.0f,
|
||||
"Construct an AbsDiff oracle with the given absolute tolerance.")
|
||||
.def("getTolerance", &tools::AbsDiff::getTolerance,
|
||||
"Return the current absolute tolerance.")
|
||||
.def("setTolerance", &tools::AbsDiff::setTolerance, "tolerance"_a,
|
||||
"Replace the current absolute tolerance.")
|
||||
.def("__bool__",
|
||||
[](const tools::AbsDiff& self) { return bool(self); },
|
||||
"True iff the tolerance has been initialized (>= 0).");
|
||||
|
||||
nb::class_<tools::RelDiff>(toolsModule, "RelDiff",
|
||||
"Compression oracle for FpN: accept the approximation when "
|
||||
"|exact - approx| / max(|exact|, |approx|) <= tolerance.")
|
||||
.def(nb::init<float>(), "tolerance"_a = -1.0f,
|
||||
"Construct a RelDiff oracle with the given relative tolerance.")
|
||||
.def("getTolerance", &tools::RelDiff::getTolerance,
|
||||
"Return the current relative tolerance.")
|
||||
.def("setTolerance", &tools::RelDiff::setTolerance, "tolerance"_a,
|
||||
"Replace the current relative tolerance.")
|
||||
.def("__bool__",
|
||||
[](const tools::RelDiff& self) { return bool(self); },
|
||||
"True iff the tolerance has been initialized (>= 0).");
|
||||
|
||||
// ------ Quantized fixed-width: Fp4 / Fp8 / Fp16 ------
|
||||
toolsModule.def("createNanoGridFp4",
|
||||
[](nb::handle src, tools::StatsMode sMode, CheckMode cMode,
|
||||
bool ditherOn, int verbose) {
|
||||
return createNanoGridFpX<Fp4>(src, sMode, cMode, ditherOn, verbose,
|
||||
"createNanoGridFp4");
|
||||
},
|
||||
"src"_a, "sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default, "ditherOn"_a = false, "verbose"_a = 0,
|
||||
"Quantize a NanoGrid<float> or nanovdb.tools.build.FloatGrid into a "
|
||||
"NanoGrid<Fp4> (4 bits per voxel). ditherOn adds sub-quantum "
|
||||
"noise to break up banding.");
|
||||
|
||||
toolsModule.def("createNanoGridFp8",
|
||||
[](nb::handle src, tools::StatsMode sMode, CheckMode cMode,
|
||||
bool ditherOn, int verbose) {
|
||||
return createNanoGridFpX<Fp8>(src, sMode, cMode, ditherOn, verbose,
|
||||
"createNanoGridFp8");
|
||||
},
|
||||
"src"_a, "sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default, "ditherOn"_a = false, "verbose"_a = 0,
|
||||
"Quantize a NanoGrid<float> or nanovdb.tools.build.FloatGrid into a NanoGrid<Fp8> (8 bits "
|
||||
"per voxel). ditherOn adds sub-quantum noise.");
|
||||
|
||||
toolsModule.def("createNanoGridFp16",
|
||||
[](nb::handle src, tools::StatsMode sMode, CheckMode cMode,
|
||||
bool ditherOn, int verbose) {
|
||||
return createNanoGridFpX<Fp16>(src, sMode, cMode, ditherOn, verbose,
|
||||
"createNanoGridFp16");
|
||||
},
|
||||
"src"_a, "sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default, "ditherOn"_a = false, "verbose"_a = 0,
|
||||
"Quantize a NanoGrid<float> or nanovdb.tools.build.FloatGrid into a NanoGrid<Fp16> (16 bits "
|
||||
"per voxel). ditherOn adds sub-quantum noise.");
|
||||
|
||||
// ------ Variable bit-width: FpN with AbsDiff or RelDiff oracle ------
|
||||
//
|
||||
// Two overloads — one per oracle type. Python dispatch picks the
|
||||
// right one from the oracle argument's type. The createNanoGrid C++
|
||||
// template's parameter order is (src, sMode, cMode, ditherOn, verbose,
|
||||
// oracle, buffer); we reorder for Python so oracle comes second
|
||||
// (most callers want to specify it explicitly), then mode/dither
|
||||
// parameters as kwargs with defaults.
|
||||
toolsModule.def("createNanoGridFpN",
|
||||
[](nb::handle src, const tools::AbsDiff& oracle,
|
||||
tools::StatsMode sMode, CheckMode cMode, bool ditherOn, int verbose) {
|
||||
return createNanoGridFpNImpl(src, oracle, sMode, cMode, ditherOn, verbose);
|
||||
},
|
||||
"src"_a, "oracle"_a = tools::AbsDiff(),
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default, "ditherOn"_a = false, "verbose"_a = 0,
|
||||
"Quantize a NanoGrid<float> or nanovdb.tools.build.FloatGrid into a NanoGrid<FpN> (variable "
|
||||
"bits per voxel; each leaf picks the smallest N that satisfies "
|
||||
"the oracle's tolerance). Pass an AbsDiff oracle for absolute "
|
||||
"error bound, or use the RelDiff overload for relative error.");
|
||||
|
||||
toolsModule.def("createNanoGridFpN",
|
||||
[](nb::handle src, const tools::RelDiff& oracle,
|
||||
tools::StatsMode sMode, CheckMode cMode, bool ditherOn, int verbose) {
|
||||
return createNanoGridFpNImpl(src, oracle, sMode, cMode, ditherOn, verbose);
|
||||
},
|
||||
"src"_a, "oracle"_a,
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default, "ditherOn"_a = false, "verbose"_a = 0,
|
||||
"FpN overload accepting a RelDiff oracle for relative error.");
|
||||
|
||||
// ------ Index / OnIndex ------
|
||||
//
|
||||
// createNanoGridIndex / createNanoGridOnIndex are the canonical names
|
||||
// for the broad-source-coverage index conversion bindings. A narrower
|
||||
// createOnIndexGrid factory still lives in PyVoxelBlockManager.cc as
|
||||
// the test scaffolding entry point used by the VBM unit tests.
|
||||
toolsModule.def("createNanoGridIndex",
|
||||
[](nb::handle src, uint32_t channels, bool includeStats,
|
||||
bool includeTiles, int verbose) {
|
||||
return createIndexImpl<ValueIndex>(
|
||||
src, channels, includeStats, includeTiles, verbose,
|
||||
"createNanoGridIndex");
|
||||
},
|
||||
"src"_a, "channels"_a = 0u, "includeStats"_a = true,
|
||||
"includeTiles"_a = true, "verbose"_a = 0,
|
||||
"Convert a source grid into a NanoGrid<ValueIndex>. Every voxel "
|
||||
"(active or inactive) gets a unique uint64 sequential index, with "
|
||||
"the original values stored as blind data when channels > 0.");
|
||||
|
||||
toolsModule.def("createNanoGridOnIndex",
|
||||
[](nb::handle src, uint32_t channels, bool includeStats,
|
||||
bool includeTiles, int verbose) {
|
||||
return createIndexImpl<ValueOnIndex>(
|
||||
src, channels, includeStats, includeTiles, verbose,
|
||||
"createNanoGridOnIndex");
|
||||
},
|
||||
"src"_a, "channels"_a = 0u, "includeStats"_a = true,
|
||||
"includeTiles"_a = true, "verbose"_a = 0,
|
||||
"Convert a source grid into a NanoGrid<ValueOnIndex>. Only the "
|
||||
"active voxels get a sequential index — the canonical input to "
|
||||
"buildVoxelBlockManager.");
|
||||
|
||||
// ------ CreateNanoGrid converter class (blind-data authoring) ------
|
||||
nb::class_<PyCreateNanoGrid>(toolsModule, "CreateNanoGrid",
|
||||
"Reusable converter mirroring nanovdb::tools::CreateNanoGrid. "
|
||||
"Construct from a NanoGrid or nanovdb.tools.build.* grid (float, "
|
||||
"double, int32, or Vec3f), optionally declare blind-data channels "
|
||||
"with addBlindData(), then bake a fresh NanoGrid of the same BuildT "
|
||||
"with getHandle(). Authored channels come back zero-filled — write "
|
||||
"their contents through the writable NumPy view returned by "
|
||||
"grid.getBlindData(n). For quantized (Fp*) or index destinations "
|
||||
"use the createNanoGridFp* / createNanoGridIndex functions instead.")
|
||||
.def(nb::init<nb::object>(), "srcGrid"_a,
|
||||
"Construct a converter reading from srcGrid (a NanoGrid or "
|
||||
"nanovdb.tools.build.* grid of BuildT float, double, int32, or "
|
||||
"Vec3f). The converter keeps srcGrid alive.")
|
||||
.def("addBlindData", &PyCreateNanoGrid::addBlindData,
|
||||
"name"_a, "count"_a, "dataType"_a = GridType::Float,
|
||||
"dataSemantic"_a = GridBlindDataSemantic::Unknown,
|
||||
"dataClass"_a = GridBlindDataClass::AttributeArray,
|
||||
"size"_a = 0u,
|
||||
"Declare a blind-data channel of count elements of dataType to "
|
||||
"be allocated in the destination grid, and return its channel "
|
||||
"index. size (bytes per element) is derived from dataType when "
|
||||
"omitted; pass it explicitly for dataTypes without a fixed "
|
||||
"element size. The C++ signature orders the parameters (name, "
|
||||
"dataSemantic, dataClass, dataType, count, size) — reordered "
|
||||
"here so the common case reads addBlindData(name, count).")
|
||||
.def("setStats", &PyCreateNanoGrid::setStats, "mode"_a,
|
||||
"Set the StatsMode used when baking the destination grid.")
|
||||
.def("setChecksum", &PyCreateNanoGrid::setChecksum, "mode"_a,
|
||||
"Set the CheckMode used when baking the destination grid.")
|
||||
.def("setVerbose", &PyCreateNanoGrid::setVerbose, "mode"_a = 1,
|
||||
"Set the verbosity level used when baking the destination grid.")
|
||||
.def("enableDithering", &PyCreateNanoGrid::enableDithering, "on"_a = true,
|
||||
"Toggle dithering of the destination grid (only meaningful for "
|
||||
"quantized BuildTs; kept for parity with the C++ class).")
|
||||
.def("getHandle", &PyCreateNanoGrid::getHandle,
|
||||
"Bake and return a GridHandle owning a NanoGrid of the source's "
|
||||
"BuildT, including any channels declared via addBlindData(). "
|
||||
"Each call bakes a fresh grid.");
|
||||
}
|
||||
|
||||
#define NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix) \
|
||||
template void defineCreateNanoGrid<T>(nb::module_&, const char*);
|
||||
#include "BuildTypes.def"
|
||||
|
||||
template void defineOpenToNanoVDB<HostBuffer>(nb::module_&);
|
||||
|
||||
|
||||
@@ -15,6 +15,21 @@ template<typename BuildT> void defineCreateNanoGrid(nb::module_& m, const char*
|
||||
template<typename BufferT> void defineOpenToNanoVDB(nb::module_& m);
|
||||
#endif
|
||||
|
||||
/// @brief Bind the AbsDiff / RelDiff quantization oracle classes and the
|
||||
/// polymorphic createNanoGridFp4 / Fp8 / Fp16 / FpN / Index / OnIndex
|
||||
/// free functions on the nanovdb.tools submodule. Sources accepted
|
||||
/// include both NanoGrid<SrcBuildT> and tools::build::Grid<SrcBuildT>:
|
||||
/// the quantized createNanoGridFp* / FpN paths accept float only
|
||||
/// (C++ Fp{4,8,16,N}::preProcess static-asserts SrcValueT == float);
|
||||
/// the createNanoGridIndex / OnIndex paths accept float, double,
|
||||
/// int32_t, and Vec3f sources. Additional source BuildTs can be
|
||||
/// added by extending the explicit try-each-SrcBuildT chains in
|
||||
/// createNanoGridFpX / FpNImpl / createIndexImpl. Also binds the
|
||||
/// tools.CreateNanoGrid converter class, which mirrors the C++
|
||||
/// tools::CreateNanoGrid and adds blind-data authoring via
|
||||
/// addBlindData() (same source BuildT set as the index paths).
|
||||
void defineCreateNanoGridConversions(nb::module_& toolsModule);
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,10 @@ namespace pynanovdb {
|
||||
|
||||
void defineCheckMode(nb::module_& m)
|
||||
{
|
||||
nb::enum_<CheckMode>(m, "CheckMode")
|
||||
nb::enum_<CheckMode>(m, "CheckMode",
|
||||
"Selector controlling how aggressively a grid checksum is computed: "
|
||||
"Disable skips checksumming, Partial covers only the header, Full "
|
||||
"covers the whole grid, and Default picks the recommended mode.")
|
||||
.value("Disable", CheckMode::Disable)
|
||||
.value("Partial", CheckMode::Partial)
|
||||
.value("Full", CheckMode::Full)
|
||||
@@ -24,13 +27,65 @@ void defineCheckMode(nb::module_& m)
|
||||
|
||||
void defineChecksum(nb::module_& m)
|
||||
{
|
||||
nb::class_<Checksum>(m, "Checksum").def(nb::self == nb::self, "rhs"_a).def(nb::self != nb::self, "rhs"_a);
|
||||
nb::class_<Checksum>(m, "Checksum",
|
||||
"64-bit checksum value stored in a grid header. Produced by "
|
||||
"tools.evalChecksum and compared against the stored one by "
|
||||
"tools.validateChecksum.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Equality of two Checksum values.")
|
||||
.def(nb::self != nb::self, "rhs"_a,
|
||||
"Inequality of two Checksum values.")
|
||||
.def("isEmpty", &Checksum::isEmpty,
|
||||
"True iff no checksum is stored (checksumming was disabled).")
|
||||
.def("isHalf", &Checksum::isHalf,
|
||||
"True iff only the header portion (grid + tree + root) is checksummed.")
|
||||
.def("isFull", &Checksum::isFull,
|
||||
"True iff both the header portion and all nodes are checksummed.")
|
||||
.def("mode", &Checksum::mode,
|
||||
"CheckMode this checksum was computed with (Disable, Partial, or Full).");
|
||||
}
|
||||
|
||||
void defineUpdateChecksum(nb::module_& m)
|
||||
{
|
||||
m.def(
|
||||
"updateChecksum", [](GridData* gridData, CheckMode mode) { tools::updateChecksum(gridData, mode); }, "gridData"_a, "mode"_a);
|
||||
"updateChecksum", [](GridData* gridData, CheckMode mode) { tools::updateChecksum(gridData, mode); }, "gridData"_a, "mode"_a,
|
||||
"Recompute and store the checksum of gridData using the given CheckMode.");
|
||||
}
|
||||
|
||||
void defineEvalChecksumModule(nb::module_& toolsModule)
|
||||
{
|
||||
// tools.evalChecksum(grid, mode) — compute a fresh checksum for the
|
||||
// given grid without writing it back. Mirrors the GridData* overload
|
||||
// in tools/GridChecksum.h; the polymorphism over BuildT is implicit
|
||||
// because every NanoGrid<T> is-a GridData in the C++ hierarchy (and
|
||||
// the Python class binding declares NanoGrid<T> as derived from
|
||||
// GridData).
|
||||
toolsModule.def("evalChecksum",
|
||||
[](const GridData* gridData, CheckMode mode) -> Checksum {
|
||||
if (gridData == nullptr) {
|
||||
throw nb::value_error("evalChecksum: grid is None.");
|
||||
}
|
||||
return tools::evalChecksum(gridData, mode);
|
||||
},
|
||||
"grid"_a, "mode"_a = CheckMode::Default,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Compute and return the Checksum for the given grid using the "
|
||||
"specified CheckMode. Does not modify the grid.");
|
||||
|
||||
// tools.validateChecksum(grid, mode) — compare the stored checksum
|
||||
// against a freshly computed one and return a bool.
|
||||
toolsModule.def("validateChecksum",
|
||||
[](const GridData* gridData, CheckMode mode) -> bool {
|
||||
if (gridData == nullptr) {
|
||||
throw nb::value_error("validateChecksum: grid is None.");
|
||||
}
|
||||
return tools::validateChecksum(gridData, mode);
|
||||
},
|
||||
"grid"_a, "mode"_a = CheckMode::Default,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Return True iff the grid's stored checksum matches a freshly "
|
||||
"computed one for the given CheckMode. A grid with no stored "
|
||||
"checksum (Checksum.isEmpty()) is considered valid.");
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -13,6 +13,11 @@ void defineCheckMode(nb::module_& m);
|
||||
void defineChecksum(nb::module_& m);
|
||||
void defineUpdateChecksum(nb::module_& m);
|
||||
|
||||
/// @brief Bind tools.evalChecksum and tools.validateChecksum on the
|
||||
/// nanovdb.tools submodule. Both accept any bound NanoGrid via
|
||||
/// GridData* upcast (handled by nanobind's class hierarchy).
|
||||
void defineEvalChecksumModule(nb::module_& toolsModule);
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,7 @@ void defineHostGridHandle(nb::module_& m)
|
||||
new (&handle) GridHandle<BufferT>(std::move(buffer));
|
||||
},
|
||||
"t"_a.noconvert());
|
||||
defineGridHandleUtilities<BufferT>(m);
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -7,62 +7,226 @@
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include <nanovdb/GridHandle.h>
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include <nanovdb/tools/GridChecksum.h> // for tools::updateGridCount
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// Polymorphic host-side `handle.grid(n)`: dispatch on gridType(n) to the
|
||||
// matching NanoGrid<BuildT> subclass currently bound in Python. Returns
|
||||
// None when the underlying BuildT is not bound in this build — see the
|
||||
// list of bound types in BuildTypes.def. The returned object is parented
|
||||
// to the handle so the handle is kept alive at least as long as the grid.
|
||||
template<typename BufferT>
|
||||
inline nb::object pyHostGrid(nb::handle py_handle, uint32_t n)
|
||||
{
|
||||
auto& handle = nb::cast<nanovdb::GridHandle<BufferT>&>(py_handle);
|
||||
if (n >= handle.gridCount()) return nb::none();
|
||||
switch (handle.gridType(n)) {
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template grid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template grid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template grid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template grid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#include "BuildTypes.def"
|
||||
default:
|
||||
return nb::none();
|
||||
}
|
||||
}
|
||||
|
||||
// Free functions splitGrids / mergeGrids exposed at module scope. Templated
|
||||
// on BufferT so the same definitions work for both the host GridHandle and
|
||||
// the device GridHandle bindings.
|
||||
template<typename BufferT> void defineGridHandleUtilities(nb::module_& m)
|
||||
{
|
||||
using HandleT = nanovdb::GridHandle<BufferT>;
|
||||
m.def("splitGrids", [](const HandleT& handle) {
|
||||
auto handles = nanovdb::splitGrids(handle);
|
||||
nb::list out;
|
||||
for (auto& h : handles) {
|
||||
out.append(std::move(h));
|
||||
}
|
||||
return out;
|
||||
}, nb::arg("handle"),
|
||||
"Split a multi-grid handle into a list of single-grid handles, "
|
||||
"each owning a freshly-allocated buffer.");
|
||||
// mergeGrids: walk the Python sequence by CONST ref to each handle and
|
||||
// concatenate buffer bytes into a freshly-allocated output. The original
|
||||
// nanovdb::mergeGrids takes a `const std::vector<GridHandle>&`, but
|
||||
// building such a vector from a Python list requires moving from each
|
||||
// wrapper (GridHandle is move-only) — which would silently empty the
|
||||
// caller's `h1`/`h2` Python objects. Instead we inline the merge logic
|
||||
// here so we never need to move from the inputs.
|
||||
m.def("mergeGrids", [](nb::sequence handles) {
|
||||
// Collect const refs so we touch each Python wrapper exactly once.
|
||||
std::vector<const HandleT*> sources;
|
||||
sources.reserve(nb::len(handles));
|
||||
for (nb::handle item : handles) {
|
||||
sources.push_back(&nb::cast<const HandleT&>(item));
|
||||
}
|
||||
|
||||
uint64_t totalSize = 0;
|
||||
uint32_t totalGrids = 0;
|
||||
for (const HandleT* h : sources) {
|
||||
totalGrids += h->gridCount();
|
||||
for (uint32_t n = 0; n < h->gridCount(); ++n) {
|
||||
totalSize += h->gridSize(n);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to merge (empty sequence, or only empty handles): return an
|
||||
// empty handle. BufferT::create(0) is ill-defined — for HostBuffer it
|
||||
// yields a non-null data() over a zero-byte region, and the
|
||||
// GridHandle(buffer) ctor would then read a full GridData header out of
|
||||
// it (heap overflow / "invalid host buffer" throw).
|
||||
if (totalGrids == 0) return HandleT();
|
||||
|
||||
auto buffer = BufferT::create(totalSize);
|
||||
uint8_t* dst = static_cast<uint8_t*>(buffer.data());
|
||||
uint32_t writeIndex = 0;
|
||||
for (const HandleT* h : sources) {
|
||||
for (uint32_t n = 0; n < h->gridCount(); ++n) {
|
||||
// gridData(n) is the authoritative per-grid start pointer (it
|
||||
// applies mMetaData[n].offset), so we don't assume the source
|
||||
// grids are laid out contiguously in the buffer.
|
||||
const uint64_t gs = h->gridSize(n);
|
||||
std::memcpy(dst, h->gridData(n), gs);
|
||||
auto* gd = reinterpret_cast<nanovdb::GridData*>(dst);
|
||||
nanovdb::tools::updateGridCount(gd, writeIndex++, totalGrids);
|
||||
dst += gs;
|
||||
}
|
||||
}
|
||||
return HandleT(std::move(buffer));
|
||||
}, nb::arg("handles"),
|
||||
"Combine a list of GridHandles into a single multi-grid GridHandle. "
|
||||
"Input handles are read by const reference; the new handle owns a "
|
||||
"freshly-allocated buffer and the inputs are left untouched.");
|
||||
}
|
||||
|
||||
template<typename BufferT> nb::class_<nanovdb::GridHandle<BufferT>> defineGridHandle(nb::module_& m, const char* name)
|
||||
{
|
||||
return nb::class_<nanovdb::GridHandle<BufferT>>(m, name)
|
||||
.def(nb::init<>())
|
||||
.def("reset", &nanovdb::GridHandle<BufferT>::reset)
|
||||
.def("size", &nanovdb::GridHandle<BufferT>::bufferSize)
|
||||
.def("isEmpty", &nanovdb::GridHandle<BufferT>::isEmpty)
|
||||
.def("empty", &nanovdb::GridHandle<BufferT>::empty)
|
||||
return nb::class_<nanovdb::GridHandle<BufferT>>(m, name,
|
||||
"Owns a buffer holding one or more serialized NanoVDB grids. "
|
||||
"Construct via nanovdb.tools.create* factories or nanovdb.io.readGrid(s); "
|
||||
"access individual grids via handle.grid(n).")
|
||||
.def(nb::init<>(),
|
||||
"Construct an empty handle. Use the nanovdb.tools.create* "
|
||||
"factories or nanovdb.io.readGrid(s) instead in normal use.")
|
||||
.def("reset", &nanovdb::GridHandle<BufferT>::reset,
|
||||
"Drop the underlying buffer; the handle becomes empty.")
|
||||
.def("size", &nanovdb::GridHandle<BufferT>::bufferSize,
|
||||
"Total byte size of the buffer backing this handle (sum of "
|
||||
"every grid plus any internal padding).")
|
||||
.def("isEmpty", &nanovdb::GridHandle<BufferT>::isEmpty,
|
||||
"True iff the handle owns no buffer.")
|
||||
.def("empty", &nanovdb::GridHandle<BufferT>::empty,
|
||||
"Same as isEmpty(). Retained for parity with the C++ "
|
||||
"GridHandle::empty() member.")
|
||||
.def(
|
||||
"__bool__", [](const nanovdb::GridHandle<BufferT>& handle) { handle.empty(); }, nb::is_operator())
|
||||
.def("floatGrid", nb::overload_cast<uint32_t>(&nanovdb::GridHandle<BufferT>::template grid<float>), nb::arg("n") = 0, nb::rv_policy::reference_internal)
|
||||
.def("doubleGrid",
|
||||
nb::overload_cast<uint32_t>(&nanovdb::GridHandle<BufferT>::template grid<double>),
|
||||
nb::arg("n") = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
.def("int32Grid",
|
||||
nb::overload_cast<uint32_t>(&nanovdb::GridHandle<BufferT>::template grid<int32_t>),
|
||||
nb::arg("n") = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
.def("vec3fGrid",
|
||||
nb::overload_cast<uint32_t>(&nanovdb::GridHandle<BufferT>::template grid<nanovdb::Vec3f>),
|
||||
nb::arg("n") = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
.def("rgba8Grid",
|
||||
nb::overload_cast<uint32_t>(&nanovdb::GridHandle<BufferT>::template grid<nanovdb::math::Rgba8>),
|
||||
nb::arg("n") = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
.def("isPadded", &nanovdb::GridHandle<BufferT>::isPadded)
|
||||
.def("gridCount", &nanovdb::GridHandle<BufferT>::gridCount)
|
||||
.def("gridSize", &nanovdb::GridHandle<BufferT>::gridSize, nb::arg("n") = 0)
|
||||
.def("gridType", &nanovdb::GridHandle<BufferT>::gridType, nb::arg("n") = 0)
|
||||
"__bool__",
|
||||
[](const nanovdb::GridHandle<BufferT>& handle) { return !handle.empty(); },
|
||||
nb::is_operator(),
|
||||
"True iff the handle owns a non-empty buffer (`not isEmpty()`).")
|
||||
.def("copy",
|
||||
[](const nanovdb::GridHandle<BufferT>& handle) {
|
||||
return handle.template copy<BufferT>();
|
||||
},
|
||||
"Return a deep copy of this GridHandle backed by a freshly-allocated buffer.")
|
||||
.def("grid", &pyHostGrid<BufferT>, nb::arg("n") = 0,
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return the n-th grid as a typed Grid subclass selected by "
|
||||
"gridType(n), or None if the BuildT is not bound in Python. "
|
||||
"The returned grid keeps this handle alive.")
|
||||
.def("isPadded", &nanovdb::GridHandle<BufferT>::isPadded,
|
||||
"True iff this handle's buffer is aligned past the natural "
|
||||
"GridData alignment (used by the I/O code path).")
|
||||
.def("gridCount", &nanovdb::GridHandle<BufferT>::gridCount,
|
||||
"Number of grids stored in this handle.")
|
||||
.def(
|
||||
"gridSize",
|
||||
[](const nanovdb::GridHandle<BufferT>& handle, uint32_t n) {
|
||||
// GridHandle::gridSize(n) indexes mMetaData[n] unchecked, so an
|
||||
// out-of-range n (including any n on an empty handle) is UB.
|
||||
if (n >= handle.gridCount())
|
||||
throw nb::index_error("gridSize: grid index out of range [0, gridCount()).");
|
||||
return handle.gridSize(n);
|
||||
},
|
||||
nb::arg("n") = 0,
|
||||
"Byte size of the n-th grid (without padding).")
|
||||
.def(
|
||||
"gridType",
|
||||
[](const nanovdb::GridHandle<BufferT>& handle, uint32_t n) {
|
||||
// GridHandle::gridType(n) indexes mMetaData[n] unchecked, so an
|
||||
// out-of-range n (including any n on an empty handle) is UB.
|
||||
if (n >= handle.gridCount())
|
||||
throw nb::index_error("gridType: grid index out of range [0, gridCount()).");
|
||||
return handle.gridType(n);
|
||||
},
|
||||
nb::arg("n") = 0,
|
||||
"GridType enumerator of the n-th grid (e.g. GridType.Float). "
|
||||
"Cheap to query — does not require materializing the grid.")
|
||||
.def(
|
||||
"gridData",
|
||||
[](nanovdb::GridHandle<BufferT>& handle, uint32_t n) { return nb::bytes(handle.gridData(n), handle.gridSize(n)); },
|
||||
[](nanovdb::GridHandle<BufferT>& handle, uint32_t n) {
|
||||
// gridData(n) returns nullptr for out-of-range n, but gridSize(n)
|
||||
// below indexes mMetaData[n] unchecked — guard both here.
|
||||
if (n >= handle.gridCount())
|
||||
throw nb::index_error("gridData: grid index out of range [0, gridCount()).");
|
||||
return nb::bytes(handle.gridData(n), handle.gridSize(n));
|
||||
},
|
||||
nb::arg("n") = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
.def("write", nb::overload_cast<const std::string&>(&nanovdb::GridHandle<BufferT>::write, nb::const_), nb::arg("fileName"))
|
||||
.def("write", nb::overload_cast<const std::string&, uint32_t>(&nanovdb::GridHandle<BufferT>::write, nb::const_), nb::arg("fileName"), nb::arg("n"))
|
||||
nb::rv_policy::reference_internal,
|
||||
"Raw byte contents of the n-th grid as a Python bytes object. "
|
||||
"Useful for hashing or for handing off to non-NanoVDB tooling.")
|
||||
.def("write", nb::overload_cast<const std::string&>(&nanovdb::GridHandle<BufferT>::write, nb::const_),
|
||||
nb::arg("fileName"),
|
||||
"Write every grid in this handle to the given .nvdb file.")
|
||||
.def("write", nb::overload_cast<const std::string&, uint32_t>(&nanovdb::GridHandle<BufferT>::write, nb::const_),
|
||||
nb::arg("fileName"), nb::arg("n"),
|
||||
"Write just the n-th grid in this handle to the given .nvdb file.")
|
||||
.def(
|
||||
"read", [](nanovdb::GridHandle<BufferT>& handle, const std::string& fileName) { handle.read(fileName); }, nb::arg("fileName"))
|
||||
"read", [](nanovdb::GridHandle<BufferT>& handle, const std::string& fileName) { handle.read(fileName); },
|
||||
nb::arg("fileName"),
|
||||
"Replace this handle's contents with every grid read from the "
|
||||
"given .nvdb file.")
|
||||
.def(
|
||||
"read",
|
||||
[](nanovdb::GridHandle<BufferT>& handle, const std::string& fileName, uint32_t n) { handle.read(fileName, n); },
|
||||
nb::arg("fileName"),
|
||||
nb::arg("n"))
|
||||
nb::arg("n"),
|
||||
"Replace this handle's contents with the n-th grid read from "
|
||||
"the given .nvdb file.")
|
||||
.def(
|
||||
"read",
|
||||
[](nanovdb::GridHandle<BufferT>& handle, const std::string& fileName, const std::string& gridName) { handle.read(fileName, gridName); },
|
||||
nb::arg("fileName"),
|
||||
nb::arg("gridName"));
|
||||
|
||||
nb::arg("gridName"),
|
||||
"Replace this handle's contents with the grid of the given "
|
||||
"name read from the .nvdb file.");
|
||||
}
|
||||
|
||||
void defineHostGridHandle(nb::module_& m);
|
||||
|
||||
@@ -2,16 +2,26 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "PyGridStats.h"
|
||||
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include <nanovdb/tools/GridStats.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
using namespace nanovdb;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
void defineStatsMode(nb::module_& m)
|
||||
{
|
||||
nb::enum_<tools::StatsMode>(m, "StatsMode")
|
||||
nb::enum_<tools::StatsMode>(m, "StatsMode",
|
||||
"Selector controlling which per-node statistics are computed by "
|
||||
"tools.updateGridStats: Disable skips stats, BBox refreshes only "
|
||||
"bounding boxes, MinMax adds min/max, and All adds average and "
|
||||
"standard deviation as well.")
|
||||
.value("Disable", tools::StatsMode::Disable)
|
||||
.value("BBox", tools::StatsMode::BBox)
|
||||
.value("MinMax", tools::StatsMode::MinMax)
|
||||
@@ -20,4 +30,201 @@ void defineStatsMode(nb::module_& m)
|
||||
.value("End", tools::StatsMode::End);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// ----- Extrema<ValueT> binding (rank 0 and rank 1 share the same surface) -----
|
||||
template<typename BuildT>
|
||||
static void defineExtrema(nb::module_& m, const char* name)
|
||||
{
|
||||
using ValueT = typename NanoGrid<BuildT>::ValueType;
|
||||
using ExtremaT = tools::Extrema<ValueT>;
|
||||
|
||||
nb::class_<ExtremaT>(m, name,
|
||||
"Running minimum / maximum accumulator over a stream of values. "
|
||||
"Build via repeated add(v) calls or via tools.getExtrema(grid, bbox).")
|
||||
.def(nb::init<>(),
|
||||
"Default-construct an Extrema with min = numeric_limits::max and "
|
||||
"max = numeric_limits::lowest, so any subsequent .add(v) gives "
|
||||
"exact min/max.")
|
||||
.def("min",
|
||||
[](const ExtremaT& self) -> ValueT { return self.min(); },
|
||||
"Return the minimum value observed so far.")
|
||||
.def("max",
|
||||
[](const ExtremaT& self) -> ValueT { return self.max(); },
|
||||
"Return the maximum value observed so far.")
|
||||
.def("add",
|
||||
[](ExtremaT& self, const ValueT& v) { self.add(v); },
|
||||
"value"_a,
|
||||
"Update min/max with a single sample.")
|
||||
.def("__bool__",
|
||||
[](const ExtremaT& self) { return bool(self); },
|
||||
"True iff the Extrema has accumulated at least one sample "
|
||||
"(i.e. min <= max).")
|
||||
.def_static("hasMinMax", &ExtremaT::hasMinMax,
|
||||
"True for value types where min/max is meaningful "
|
||||
"(everything except bool).")
|
||||
.def_static("hasAverage", &ExtremaT::hasAverage,
|
||||
"Always False — Extrema does not compute averages; "
|
||||
"use Stats for that.")
|
||||
.def_static("hasStdDeviation", &ExtremaT::hasStdDeviation,
|
||||
"Always False — Extrema does not compute standard "
|
||||
"deviation; use Stats for that.")
|
||||
.def_static("hasStats", &ExtremaT::hasStats,
|
||||
"True iff the value type supports the min/max bookkeeping "
|
||||
"(everything except bool).");
|
||||
}
|
||||
|
||||
// ----- Stats<ValueT> binding (inherits Extrema) -----
|
||||
template<typename BuildT>
|
||||
static void defineStats(nb::module_& m, const char* name)
|
||||
{
|
||||
using ValueT = typename NanoGrid<BuildT>::ValueType;
|
||||
using BaseT = tools::Extrema<ValueT>;
|
||||
using StatsT = tools::Stats<ValueT>;
|
||||
|
||||
nb::class_<StatsT, BaseT>(m, name,
|
||||
"Running min/max/mean/variance/std accumulator over a stream of "
|
||||
"values. Extends Extrema with sample-count-weighted moments.")
|
||||
.def(nb::init<>(),
|
||||
"Default-construct a Stats accumulator with zero samples.")
|
||||
.def("add",
|
||||
[](StatsT& self, const ValueT& v) { self.add(v); },
|
||||
"value"_a,
|
||||
"Add a single sample.")
|
||||
.def("size",
|
||||
[](const StatsT& self) -> size_t { return self.size(); },
|
||||
"Number of samples accumulated so far.")
|
||||
.def("avg",
|
||||
[](const StatsT& self) -> double { return self.avg(); },
|
||||
"Arithmetic mean of all samples.")
|
||||
.def("mean",
|
||||
[](const StatsT& self) -> double { return self.mean(); },
|
||||
"Alias for avg().")
|
||||
.def("var",
|
||||
[](const StatsT& self) -> double { return self.var(); },
|
||||
"Population variance (Sum(x-mean)^2 / N). Returns 0 if "
|
||||
"fewer than two samples have been added.")
|
||||
.def("variance",
|
||||
[](const StatsT& self) -> double { return self.variance(); },
|
||||
"Alias for var().")
|
||||
.def("std",
|
||||
[](const StatsT& self) -> double { return self.std(); },
|
||||
"Standard deviation = sqrt(var()).")
|
||||
.def("stdDev",
|
||||
[](const StatsT& self) -> double { return self.stdDev(); },
|
||||
"Alias for std().")
|
||||
.def_static("hasMinMax", &StatsT::hasMinMax,
|
||||
"True for value types where min/max is meaningful.")
|
||||
.def_static("hasAverage", &StatsT::hasAverage,
|
||||
"True for value types that support mean/variance.")
|
||||
.def_static("hasStdDeviation", &StatsT::hasStdDeviation,
|
||||
"True for value types that support standard deviation.")
|
||||
.def_static("hasStats", &StatsT::hasStats,
|
||||
"True for value types where full statistics is meaningful.");
|
||||
}
|
||||
|
||||
// ----- updateGridStats polymorphic dispatch ----------------------------------
|
||||
//
|
||||
// Match the IsNanoGridValid/callNanoGrid pattern: an Op struct with `known<T>`
|
||||
// (called for every BuildT that's in scope) and `unknown` (fallback). Only
|
||||
// scalar + vector BuildTs have a meaningful Stats specialization, so the
|
||||
// other arms raise.
|
||||
struct UpdateGridStatsOp
|
||||
{
|
||||
template<typename BuildT>
|
||||
static void known(GridData* gridData, tools::StatsMode mode)
|
||||
{
|
||||
using GridT = NanoGrid<BuildT>;
|
||||
using ValueT = typename GridT::ValueType;
|
||||
if constexpr (BuildTraits<BuildT>::is_special &&
|
||||
!util::is_same<bool, BuildT>::value) {
|
||||
// Special / quantized / index / mask BuildTs don't have an
|
||||
// arithmetic ValueT, so tools::updateGridStats's MinMax / All
|
||||
// branches would instantiate Stats<ValueT> / Extrema<ValueT>
|
||||
// with no meaningful semantics (and may not even compile).
|
||||
// The Disable and BBox branches use NoopStats, which works
|
||||
// for any ValueT — drive that directly here so the BBox path
|
||||
// remains available on special grids (it just recomputes
|
||||
// node bounding boxes without touching min/max/avg).
|
||||
if (mode == tools::StatsMode::Disable) {
|
||||
return;
|
||||
} else if (mode == tools::StatsMode::BBox) {
|
||||
tools::GridStats<GridT, tools::NoopStats<ValueT>> stats;
|
||||
stats.update(*static_cast<GridT*>(gridData));
|
||||
} else {
|
||||
throw nb::value_error(
|
||||
"updateGridStats: this grid's BuildT (special / "
|
||||
"quantized / index / mask) has no arithmetic value "
|
||||
"type — only StatsMode.Disable and StatsMode.BBox "
|
||||
"are supported.");
|
||||
}
|
||||
} else {
|
||||
tools::updateGridStats(static_cast<GridT*>(gridData), mode);
|
||||
}
|
||||
}
|
||||
static void unknown(GridData*, tools::StatsMode) {
|
||||
throw nb::value_error(
|
||||
"updateGridStats: unsupported GridType / BuildT combination.");
|
||||
}
|
||||
};
|
||||
|
||||
// ----- getExtrema (per-BuildT factory) ---------------------------------------
|
||||
template<typename BuildT>
|
||||
static tools::Extrema<typename NanoGrid<BuildT>::ValueType>
|
||||
pyGetExtrema(const NanoGrid<BuildT>& grid, const CoordBBox& bbox)
|
||||
{
|
||||
return tools::getExtrema<BuildT>(grid, bbox);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void defineGridStatsModule(nb::module_& toolsModule)
|
||||
{
|
||||
// Per-BuildT Extrema + Stats. One pair per scalar/vector BuildT — the
|
||||
// value types are all distinct so we get N pairs of new Python classes.
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
defineExtrema<T>(toolsModule, #Suffix "Extrema"); \
|
||||
defineStats<T>(toolsModule, #Suffix "Stats");
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
defineExtrema<T>(toolsModule, #Suffix "Extrema"); \
|
||||
defineStats<T>(toolsModule, #Suffix "Stats");
|
||||
#include "BuildTypes.def"
|
||||
|
||||
// Polymorphic updateGridStats. Accepts any bound NanoGrid<BuildT> (via
|
||||
// upcast to GridData*) and dispatches on its mGridType.
|
||||
toolsModule.def("updateGridStats",
|
||||
[](GridData* gridData, tools::StatsMode mode) {
|
||||
if (gridData == nullptr) {
|
||||
throw nb::value_error("updateGridStats: grid is None.");
|
||||
}
|
||||
callNanoGrid<UpdateGridStatsOp>(gridData, mode);
|
||||
},
|
||||
"grid"_a, "mode"_a = tools::StatsMode::Default,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Recompute and write per-node statistics into the given grid in "
|
||||
"place. Polymorphic over BuildT. Scalar, vector, and Boolean "
|
||||
"grids accept every StatsMode (Disable / BBox / MinMax / All); "
|
||||
"Boolean grids use the C++ NoopStats path internally regardless "
|
||||
"of mode because there's no arithmetic min/max/avg/dev on bool. "
|
||||
"Other special (quantized / index / mask) grids accept Disable "
|
||||
"and BBox (the latter recomputes node bounding boxes only); "
|
||||
"MinMax and All raise ValueError because their value type has "
|
||||
"no arithmetic semantics.");
|
||||
|
||||
// Per-BuildT getExtrema. We expose one overload per scalar/vector
|
||||
// BuildT — they each return a Python-side Extrema<ValueT> of the
|
||||
// matching name.
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
toolsModule.def("getExtrema", &pyGetExtrema<T>, \
|
||||
"grid"_a, "bbox"_a, nb::call_guard<nb::gil_scoped_release>(), \
|
||||
"Return the Extrema of all values in the grid that intersect " \
|
||||
"the given bbox.");
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
toolsModule.def("getExtrema", &pyGetExtrema<T>, \
|
||||
"grid"_a, "bbox"_a, nb::call_guard<nb::gil_scoped_release>(), \
|
||||
"Return the Extrema of all values in the grid that intersect " \
|
||||
"the given bbox.");
|
||||
#include "BuildTypes.def"
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace pynanovdb {
|
||||
|
||||
void defineStatsMode(nb::module_& m);
|
||||
|
||||
/// @brief Register per-BuildT Extrema and Stats classes (one set per
|
||||
/// scalar / vector BuildT in BuildTypes.def) and the polymorphic
|
||||
/// tools.updateGridStats / tools.getExtrema helpers under the
|
||||
/// nanovdb.tools submodule.
|
||||
void defineGridStatsModule(nb::module_& toolsModule);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,13 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "PyGridValidator.h"
|
||||
|
||||
#include <nanobind/operators.h>
|
||||
#include <nanobind/stl/pair.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include <nanovdb/GridHandle.h>
|
||||
#include <nanovdb/HostBuffer.h>
|
||||
#include <nanovdb/tools/GridValidator.h>
|
||||
#ifdef NANOVDB_USE_CUDA
|
||||
#include <nanovdb/cuda/DeviceBuffer.h>
|
||||
#endif
|
||||
|
||||
#include <nanobind/operators.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
@@ -18,7 +26,8 @@ namespace pynanovdb {
|
||||
|
||||
template<typename BufferT> void defineValidateGrids(nb::module_& m)
|
||||
{
|
||||
m.def("validateGrids", &tools::validateGrids<GridHandle<BufferT>>, "handle"_a, "mode"_a, "verbose"_a);
|
||||
m.def("validateGrids", &tools::validateGrids<GridHandle<BufferT>>,
|
||||
"handle"_a, "mode"_a, "verbose"_a);
|
||||
}
|
||||
|
||||
template void defineValidateGrids<HostBuffer>(nb::module_&);
|
||||
@@ -26,4 +35,126 @@ template void defineValidateGrids<HostBuffer>(nb::module_&);
|
||||
template void defineValidateGrids<cuda::DeviceBuffer>(nb::module_&);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// callNanoGrid op for checkGrid: writes into a fixed-size buffer and returns
|
||||
// it as a std::pair<bool, std::string> for nb::make_tuple consumption.
|
||||
//
|
||||
// tools::checkGrid writes error messages with util::sprint / util::strcpy,
|
||||
// neither of which is bounded — they trust the caller's buffer is big
|
||||
// enough. Size the buffer well above what any current error message
|
||||
// produces (the longest formatted message in GridValidator.h is on the
|
||||
// order of ~80 characters once both GridType and GridClass enumerator
|
||||
// names are stringified) and leave generous headroom for future
|
||||
// additions.
|
||||
struct CheckGridOp
|
||||
{
|
||||
static constexpr size_t kErrorBufSize = 4096;
|
||||
|
||||
template<typename BuildT>
|
||||
static std::pair<bool, std::string> known(const GridData* gridData,
|
||||
CheckMode mode)
|
||||
{
|
||||
char buf[kErrorBufSize];
|
||||
buf[0] = '\0';
|
||||
tools::checkGrid<BuildT>(
|
||||
static_cast<const NanoGrid<BuildT>*>(gridData), buf, mode);
|
||||
const bool ok = (buf[0] == '\0');
|
||||
return {ok, std::string(buf)};
|
||||
}
|
||||
static std::pair<bool, std::string> unknown(const GridData* gridData,
|
||||
CheckMode /*mode*/)
|
||||
{
|
||||
(void)gridData;
|
||||
return {false, "Unsupported GridType for checkGrid"};
|
||||
}
|
||||
};
|
||||
|
||||
// callNanoGrid op for isValid — wraps tools::isValid<BuildT> for every
|
||||
// switched-over BuildT.
|
||||
struct IsValidOp
|
||||
{
|
||||
template<typename BuildT>
|
||||
static bool known(const GridData* gridData, CheckMode mode, bool verbose)
|
||||
{
|
||||
return tools::isValid<BuildT>(
|
||||
static_cast<const NanoGrid<BuildT>*>(gridData), mode, verbose);
|
||||
}
|
||||
static bool unknown(const GridData* gridData, CheckMode /*mode*/, bool verbose)
|
||||
{
|
||||
if (verbose && gridData != nullptr) {
|
||||
char str[16];
|
||||
std::cerr << "Validation failed: Unsupported GridType: \""
|
||||
<< toStr(str, gridData->mGridType) << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void defineGridValidatorModule(nb::module_& toolsModule)
|
||||
{
|
||||
// Single-grid validate. Takes a handle, a grid index, and the
|
||||
// usual mode + verbose flags. Returns true iff the grid passes all
|
||||
// tests for the given mode. Bound for both host and device handles
|
||||
// (when CUDA is enabled), matching validateGrids' coverage —
|
||||
// tools::validateGrid does host-side dispatch via callNanoGrid on
|
||||
// the host-resident gridData() pointer that DeviceGridHandle also
|
||||
// exposes, so the same overload pair is appropriate.
|
||||
toolsModule.def("validateGrid",
|
||||
&tools::validateGrid<GridHandle<HostBuffer>>,
|
||||
"handle"_a, "gridID"_a,
|
||||
"mode"_a = CheckMode::Default, "verbose"_a = false,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Validate the gridID'th grid in the handle against the given "
|
||||
"CheckMode. Returns False (without raising) if gridID is out "
|
||||
"of range or the grid fails any check. CheckMode.Disable is a "
|
||||
"short-circuit that always returns True without inspecting "
|
||||
"the grid (even when gridID is out of range), matching the "
|
||||
"C++ behavior. Complements validateGrids() which checks the "
|
||||
"whole handle.");
|
||||
#ifdef NANOVDB_USE_CUDA
|
||||
toolsModule.def("validateGrid",
|
||||
&tools::validateGrid<GridHandle<cuda::DeviceBuffer>>,
|
||||
"handle"_a, "gridID"_a,
|
||||
"mode"_a = CheckMode::Default, "verbose"_a = false,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Validate the gridID'th grid in the device handle (uses the "
|
||||
"host-resident copy of the grid metadata for the actual "
|
||||
"checks). Same semantics as the host-handle overload.");
|
||||
#endif
|
||||
|
||||
// Polymorphic checkGrid — returns (ok, error_message). Mirrors the C++
|
||||
// char-buffer-out signature, but the buffer is hidden inside the
|
||||
// binding so Python callers get a Python str.
|
||||
toolsModule.def("checkGrid",
|
||||
[](const GridData* gridData, CheckMode mode)
|
||||
-> std::pair<bool, std::string> {
|
||||
if (gridData == nullptr) {
|
||||
return {false, "Grid is None"};
|
||||
}
|
||||
return callNanoGrid<CheckGridOp>(gridData, mode);
|
||||
},
|
||||
"grid"_a, "mode"_a = CheckMode::Full,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Run structural validation checks on the grid for the given "
|
||||
"CheckMode. Returns a (ok, error_message) tuple — error_message "
|
||||
"is empty when ok is True.");
|
||||
|
||||
// Polymorphic isValid — convenience wrapper. Same as checkGrid + a
|
||||
// checksum check, returning just the bool.
|
||||
toolsModule.def("isValid",
|
||||
[](const GridData* gridData, CheckMode mode, bool verbose) {
|
||||
if (gridData == nullptr) return false;
|
||||
return callNanoGrid<IsValidOp>(gridData, mode, verbose);
|
||||
},
|
||||
"grid"_a, "mode"_a = CheckMode::Default, "verbose"_a = false,
|
||||
nb::call_guard<nb::gil_scoped_release>(),
|
||||
"Return True iff the grid passes structural validation AND its "
|
||||
"stored checksum matches a freshly computed one for the given "
|
||||
"CheckMode.");
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -11,6 +11,12 @@ namespace pynanovdb {
|
||||
|
||||
template<typename BufferT> void defineValidateGrids(nb::module_& m);
|
||||
|
||||
/// @brief Register tools.validateGrid (single grid in a handle),
|
||||
/// tools.checkGrid (polymorphic, returns (bool, error_str)) and
|
||||
/// tools.isValid (polymorphic shortcut) under the nanovdb.tools
|
||||
/// submodule. Bound once; polymorphic dispatch uses callNanoGrid.
|
||||
void defineGridValidatorModule(nb::module_& toolsModule);
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,7 +11,10 @@ namespace pynanovdb {
|
||||
|
||||
void defineHostBuffer(nb::module_& m)
|
||||
{
|
||||
nb::class_<HostBuffer>(m, "HostBuffer");
|
||||
nb::class_<HostBuffer>(m, "HostBuffer",
|
||||
"Default host-side buffer used to back a GridHandle. Memory is "
|
||||
"owned by this buffer and freed when the handle (and therefore "
|
||||
"the buffer) is destroyed.");
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -21,40 +21,67 @@ namespace {
|
||||
|
||||
void defineFileGridMetaData(nb::module_& m)
|
||||
{
|
||||
nb::class_<io::FileMetaData>(m, "FileMetaData")
|
||||
.def_ro("gridSize", &io::FileMetaData::gridSize)
|
||||
.def_ro("fileSize", &io::FileMetaData::fileSize)
|
||||
.def_ro("nameKey", &io::FileMetaData::nameKey)
|
||||
.def_ro("voxelCount", &io::FileMetaData::voxelCount)
|
||||
.def_ro("gridType", &io::FileMetaData::gridType)
|
||||
.def_ro("gridClass", &io::FileMetaData::gridClass)
|
||||
.def_ro("indexBBox", &io::FileMetaData::indexBBox)
|
||||
.def_ro("worldBBox", &io::FileMetaData::worldBBox)
|
||||
.def_ro("voxelSize", &io::FileMetaData::voxelSize)
|
||||
.def_ro("nameSize", &io::FileMetaData::nameSize)
|
||||
nb::class_<io::FileMetaData>(m, "FileMetaData",
|
||||
"Per-grid header read from the .nvdb file index. Mirrors the C++ "
|
||||
"io::FileMetaData layout; subclassed by FileGridMetaData which adds "
|
||||
"the grid name string.")
|
||||
.def_ro("gridSize", &io::FileMetaData::gridSize,
|
||||
"Uncompressed grid size in bytes.")
|
||||
.def_ro("fileSize", &io::FileMetaData::fileSize,
|
||||
"On-disk byte size of this grid (post-codec).")
|
||||
.def_ro("nameKey", &io::FileMetaData::nameKey,
|
||||
"Hash of the grid name used as a fast lookup key.")
|
||||
.def_ro("voxelCount", &io::FileMetaData::voxelCount,
|
||||
"Number of active voxels in this grid.")
|
||||
.def_ro("gridType", &io::FileMetaData::gridType,
|
||||
"GridType enumerator naming the BuildT of this grid.")
|
||||
.def_ro("gridClass", &io::FileMetaData::gridClass,
|
||||
"GridClass enumerator (LevelSet, FogVolume, ...).")
|
||||
.def_ro("indexBBox", &io::FileMetaData::indexBBox,
|
||||
"Axis-aligned bounding box of active voxels in index space.")
|
||||
.def_ro("worldBBox", &io::FileMetaData::worldBBox,
|
||||
"Axis-aligned bounding box of active voxels in world space.")
|
||||
.def_ro("voxelSize", &io::FileMetaData::voxelSize,
|
||||
"World-space size of a single voxel.")
|
||||
.def_ro("nameSize", &io::FileMetaData::nameSize,
|
||||
"Length of the grid name string including the null terminator.")
|
||||
.def_prop_ro("nodeCount",
|
||||
[](io::FileMetaData& metaData) {
|
||||
return std::make_tuple(metaData.nodeCount[0], metaData.nodeCount[1], metaData.nodeCount[2], metaData.nodeCount[3]);
|
||||
})
|
||||
},
|
||||
"Tuple (leaf, lower, upper, root) of node counts in this grid.")
|
||||
.def_prop_ro("tileCount",
|
||||
[](io::FileMetaData& metaData) { return std::make_tuple(metaData.tileCount[0], metaData.tileCount[1], metaData.tileCount[2]); })
|
||||
.def_ro("codec", &io::FileMetaData::codec)
|
||||
.def_ro("blindDataCount", &io::FileMetaData::blindDataCount)
|
||||
.def_ro("version", &io::FileMetaData::version);
|
||||
[](io::FileMetaData& metaData) { return std::make_tuple(metaData.tileCount[0], metaData.tileCount[1], metaData.tileCount[2]); },
|
||||
"Tuple (lower-tile, upper-tile, root-tile) of active-tile counts.")
|
||||
.def_ro("codec", &io::FileMetaData::codec,
|
||||
"Codec used to compress this grid on disk.")
|
||||
.def_ro("blindDataCount", &io::FileMetaData::blindDataCount,
|
||||
"Number of blind-data channels attached to this grid.")
|
||||
.def_ro("version", &io::FileMetaData::version,
|
||||
"NanoVDB version stored in the file when this grid was written.");
|
||||
|
||||
nb::bind_vector<std::vector<io::FileMetaData>>(m, "FileMetaDataVector");
|
||||
nb::bind_vector<std::vector<io::FileMetaData>>(m, "FileMetaDataVector",
|
||||
"List of FileMetaData entries, one per grid in a .nvdb file.");
|
||||
|
||||
nb::class_<io::FileGridMetaData, io::FileMetaData>(m, "FileGridMetaData")
|
||||
.def_ro("gridName", &io::FileGridMetaData::gridName)
|
||||
.def("memUsage", &io::FileGridMetaData::memUsage);
|
||||
nb::class_<io::FileGridMetaData, io::FileMetaData>(m, "FileGridMetaData",
|
||||
"FileMetaData extended with the grid name. Returned by "
|
||||
"readGridMetaData() so callers can identify grids by name without "
|
||||
"materializing them.")
|
||||
.def_ro("gridName", &io::FileGridMetaData::gridName,
|
||||
"Grid name as a Python string.")
|
||||
.def("memUsage", &io::FileGridMetaData::memUsage,
|
||||
"Byte size of this metadata record in memory.");
|
||||
|
||||
nb::bind_vector<std::vector<io::FileGridMetaData>>(m, "FileGridMetaDataVector");
|
||||
nb::bind_vector<std::vector<io::FileGridMetaData>>(m, "FileGridMetaDataVector",
|
||||
"List of FileGridMetaData entries, one per grid in a .nvdb file.");
|
||||
}
|
||||
|
||||
template<typename BufferT> void defineReadWriteGrid(nb::module_& m)
|
||||
{
|
||||
m.def("hasGrid", nb::overload_cast<const std::string&, const std::string&>(&io::hasGrid), "fileName"_a, "gridName"_a);
|
||||
m.def("readGridMetaData", nb::overload_cast<const std::string&>(&io::readGridMetaData), "fileName"_a);
|
||||
m.def("hasGrid", nb::overload_cast<const std::string&, const std::string&>(&io::hasGrid), "fileName"_a, "gridName"_a,
|
||||
"Return True iff the .nvdb file at fileName contains a grid named gridName.");
|
||||
m.def("readGridMetaData", nb::overload_cast<const std::string&>(&io::readGridMetaData), "fileName"_a,
|
||||
"Return a FileGridMetaDataVector describing every grid stored in the .nvdb file.");
|
||||
}
|
||||
|
||||
template<typename BufferT> nb::list readGrids(const std::string& fileName, int verbose, const BufferT& buffer)
|
||||
@@ -69,8 +96,15 @@ template<typename BufferT> nb::list readGrids(const std::string& fileName, int v
|
||||
|
||||
template<typename BufferT> void writeGrids(const std::string& fileName, nb::list handles, io::Codec codec, int verbose)
|
||||
{
|
||||
std::ofstream os(fileName, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
if (!os.is_open()) {
|
||||
throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for output");
|
||||
}
|
||||
for (size_t i = 0; i < handles.size(); ++i) {
|
||||
nanovdb::io::writeGrid(fileName, nb::cast<const GridHandle<BufferT>&>(handles[i]), codec, verbose);
|
||||
nanovdb::io::writeGrid(os, nb::cast<const GridHandle<BufferT>&>(handles[i]), codec);
|
||||
}
|
||||
if (verbose) {
|
||||
std::cout << "Wrote " << handles.size() << " nanovdb::Grid(s) to file named \"" << fileName << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,21 +118,26 @@ void defineHostReadWriteGrid(nb::module_& m)
|
||||
"fileName"_a,
|
||||
"handle"_a,
|
||||
"codec"_a = io::Codec::NONE,
|
||||
"verbose"_a = 0);
|
||||
m.def("writeGrids", &writeGrids<BufferT>, "fileName"_a, "handles"_a, "codec"_a = io::Codec::NONE, "verbose"_a = 0);
|
||||
"verbose"_a = 0,
|
||||
"Write the grids in handle to the .nvdb file at fileName using the given codec.");
|
||||
m.def("writeGrids", &writeGrids<BufferT>, "fileName"_a, "handles"_a, "codec"_a = io::Codec::NONE, "verbose"_a = 0,
|
||||
"Write every GridHandle in the handles list to the .nvdb file at fileName.");
|
||||
m.def("readGrid",
|
||||
nb::overload_cast<const std::string&, int, int, const BufferT&>(&io::template readGrid<BufferT>),
|
||||
"fileName"_a,
|
||||
"n"_a = 0,
|
||||
"verbose"_a = 0,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Read the n-th grid from the .nvdb file at fileName into a fresh GridHandle.");
|
||||
m.def("readGrid",
|
||||
nb::overload_cast<const std::string&, const std::string&, int, const BufferT&>(&io::template readGrid<BufferT>),
|
||||
"fileName"_a,
|
||||
"gridName"_a,
|
||||
"verbose"_a = 0,
|
||||
"buffer"_a = BufferT());
|
||||
m.def("readGrids", &readGrids<BufferT>, "fileName"_a, "verbose"_a = 0, "buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Read the grid named gridName from the .nvdb file at fileName into a fresh GridHandle.");
|
||||
m.def("readGrids", &readGrids<BufferT>, "fileName"_a, "verbose"_a = 0, "buffer"_a = BufferT(),
|
||||
"Read every grid from the .nvdb file at fileName, returning a list of GridHandles.");
|
||||
}
|
||||
|
||||
#ifdef NANOVDB_USE_CUDA
|
||||
@@ -112,21 +151,26 @@ void defineDeviceReadWriteGrid(nb::module_& m)
|
||||
"fileName"_a,
|
||||
"handle"_a,
|
||||
"codec"_a = io::Codec::NONE,
|
||||
"verbose"_a = 0);
|
||||
m.def("deviceWriteGrids", &writeGrids<BufferT>, "fileName"_a, "handles"_a, "codec"_a = io::Codec::NONE, "verbose"_a = 0);
|
||||
"verbose"_a = 0,
|
||||
"Write the grids in a device-backed handle to the .nvdb file at fileName.");
|
||||
m.def("deviceWriteGrids", &writeGrids<BufferT>, "fileName"_a, "handles"_a, "codec"_a = io::Codec::NONE, "verbose"_a = 0,
|
||||
"Write every device-backed GridHandle in handles to the .nvdb file at fileName.");
|
||||
m.def("deviceReadGrid",
|
||||
nb::overload_cast<const std::string&, int, int, const BufferT&>(&io::template readGrid<BufferT>),
|
||||
"fileName"_a,
|
||||
"n"_a = 0,
|
||||
"verbose"_a = 0,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Read the n-th grid from the .nvdb file at fileName into a fresh DeviceGridHandle.");
|
||||
m.def("deviceReadGrid",
|
||||
nb::overload_cast<const std::string&, const std::string&, int, const BufferT&>(&io::template readGrid<BufferT>),
|
||||
"fileName"_a,
|
||||
"gridName"_a,
|
||||
"verbose"_a = 0,
|
||||
"buffer"_a = BufferT());
|
||||
m.def("deviceReadGrids", &readGrids<BufferT>, "fileName"_a, "verbose"_a = 0, "buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Read the grid named gridName from the .nvdb file at fileName into a fresh DeviceGridHandle.");
|
||||
m.def("deviceReadGrids", &readGrids<BufferT>, "fileName"_a, "verbose"_a = 0, "buffer"_a = BufferT(),
|
||||
"Read every grid from the .nvdb file at fileName into device-backed handles.");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -134,16 +178,19 @@ void defineDeviceReadWriteGrid(nb::module_& m)
|
||||
|
||||
void defineIOModule(nb::module_& m)
|
||||
{
|
||||
nb::enum_<io::Codec>(m, "Codec")
|
||||
nb::enum_<io::Codec>(m, "Codec",
|
||||
"Compression codec selector used when writing a .nvdb file. NONE "
|
||||
"writes raw bytes; ZIP uses zlib; BLOSC uses the blosc codec when "
|
||||
"compiled in.")
|
||||
.value("NONE", io::Codec::NONE)
|
||||
.value("ZIP", io::Codec::ZIP)
|
||||
.value("BLOSC", io::Codec::BLOSC)
|
||||
.export_values();
|
||||
// .def("__repr__", [](const io::Codec& codec) {
|
||||
// char str[strlen<io::Codec>()];
|
||||
// toStr(str, codec);
|
||||
// return std::string(str);
|
||||
// });
|
||||
.export_values()
|
||||
.def("__repr__", [](const io::Codec& codec) {
|
||||
char str[strlen<io::Codec>()];
|
||||
toStr(str, codec);
|
||||
return std::string(str);
|
||||
});
|
||||
|
||||
defineFileGridMetaData(m);
|
||||
defineHostReadWriteGrid(m);
|
||||
|
||||
+325
-187
@@ -11,7 +11,6 @@
|
||||
#include <nanobind/stl/bind_vector.h>
|
||||
|
||||
#include "PySampleFromVoxels.h"
|
||||
#include "cuda/PySampleFromVoxels.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
@@ -26,18 +25,27 @@ void defineCoord(nb::module_& m)
|
||||
using ValueType = math::Coord::ValueType;
|
||||
|
||||
nb::class_<math::Coord>(m, "Coord", "Signed (i, j, k) 32-bit integer coordinate class, similar to openvdb::math::Coord")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<ValueType>(), "n"_a)
|
||||
.def(nb::init<ValueType, ValueType, ValueType>(), "i"_a, "j"_a, "k"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct (0, 0, 0).")
|
||||
.def(nb::init<ValueType>(), "n"_a,
|
||||
"Construct (n, n, n).")
|
||||
.def(nb::init<ValueType, ValueType, ValueType>(), "i"_a, "j"_a, "k"_a,
|
||||
"Construct (i, j, k).")
|
||||
.def_prop_rw(
|
||||
"x", [](const math::Coord& ijk) { return ijk.x(); }, [](math::Coord& ijk, int32_t i) { ijk.x() = i; })
|
||||
"x", [](const math::Coord& ijk) { return ijk.x(); }, [](math::Coord& ijk, int32_t i) { ijk.x() = i; },
|
||||
"First component of the (i, j, k) triple.")
|
||||
.def_prop_rw(
|
||||
"y", [](const math::Coord& ijk) { return ijk.y(); }, [](math::Coord& ijk, int32_t j) { ijk.y() = j; })
|
||||
"y", [](const math::Coord& ijk) { return ijk.y(); }, [](math::Coord& ijk, int32_t j) { ijk.y() = j; },
|
||||
"Second component of the (i, j, k) triple.")
|
||||
.def_prop_rw(
|
||||
"z", [](const math::Coord& ijk) { return ijk.z(); }, [](math::Coord& ijk, int32_t k) { ijk.z() = k; })
|
||||
.def_static("max", &math::Coord::max)
|
||||
.def_static("min", &math::Coord::min)
|
||||
.def_static("memUsage", &math::Coord::memUsage)
|
||||
"z", [](const math::Coord& ijk) { return ijk.z(); }, [](math::Coord& ijk, int32_t k) { ijk.z() = k; },
|
||||
"Third component of the (i, j, k) triple.")
|
||||
.def_static("max", &math::Coord::max,
|
||||
"Largest representable Coord (INT32_MAX in every component).")
|
||||
.def_static("min", &math::Coord::min,
|
||||
"Smallest representable Coord (INT32_MIN in every component).")
|
||||
.def_static("memUsage", &math::Coord::memUsage,
|
||||
"Byte size of a Coord instance.")
|
||||
.def(
|
||||
"__getitem__",
|
||||
[](const math::Coord& ijk, size_t i) {
|
||||
@@ -46,7 +54,8 @@ void defineCoord(nb::module_& m)
|
||||
}
|
||||
return ijk[static_cast<math::Coord::IndexType>(i)];
|
||||
},
|
||||
"i"_a)
|
||||
"i"_a,
|
||||
"Read the i-th component (0=x, 1=y, 2=z).")
|
||||
.def(
|
||||
"__setitem__",
|
||||
[](math::Coord& ijk, size_t i, ValueType value) {
|
||||
@@ -56,41 +65,69 @@ void defineCoord(nb::module_& m)
|
||||
ijk[static_cast<math::Coord::IndexType>(i)] = value;
|
||||
},
|
||||
"i"_a,
|
||||
"value"_a)
|
||||
"value"_a,
|
||||
"Write the i-th component (0=x, 1=y, 2=z).")
|
||||
.def(
|
||||
"__and__", [](const math::Coord& a, math::Coord::IndexType b) { return a & b; }, nb::is_operator(), "n"_a)
|
||||
"__and__", [](const math::Coord& a, math::Coord::IndexType b) { return a & b; }, nb::is_operator(), "n"_a,
|
||||
"Component-wise bitwise AND with the scalar n.")
|
||||
.def(
|
||||
"__lshift__", [](const math::Coord& a, math::Coord::IndexType b) { return a << b; }, nb::is_operator(), "n"_a)
|
||||
"__lshift__", [](const math::Coord& a, math::Coord::IndexType b) { return a << b; }, nb::is_operator(), "n"_a,
|
||||
"Component-wise left shift by n bits.")
|
||||
.def(
|
||||
"__rshift__", [](const math::Coord& a, math::Coord::IndexType b) { return a >> b; }, nb::is_operator(), "n"_a)
|
||||
.def(nb::self < nb::self, "rhs"_a)
|
||||
.def(nb::self == nb::self, "rhs"_a)
|
||||
.def(nb::self != nb::self, "rhs"_a)
|
||||
"__rshift__", [](const math::Coord& a, math::Coord::IndexType b) { return a >> b; }, nb::is_operator(), "n"_a,
|
||||
"Component-wise right shift by n bits.")
|
||||
.def(nb::self < nb::self, "rhs"_a,
|
||||
"Lexicographic less-than comparison.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Equality of all three components.")
|
||||
.def(nb::self != nb::self, "rhs"_a,
|
||||
"Inequality of any one component.")
|
||||
.def(
|
||||
"__iand__", [](math::Coord& a, int b) { return a &= b; }, nb::is_operator(), "n"_a)
|
||||
"__iand__", [](math::Coord& a, int b) { return a &= b; }, nb::is_operator(), "n"_a,
|
||||
"In-place component-wise bitwise AND with the scalar n.")
|
||||
.def(
|
||||
"__ilshift__", [](math::Coord& a, uint32_t b) { return a <<= b; }, nb::is_operator(), "n"_a)
|
||||
"__ilshift__", [](math::Coord& a, uint32_t b) { return a <<= b; }, nb::is_operator(), "n"_a,
|
||||
"In-place component-wise left shift by n bits.")
|
||||
.def(
|
||||
"__irshift__", [](math::Coord& a, uint32_t b) { return a >>= b; }, nb::is_operator(), "n"_a)
|
||||
"__irshift__", [](math::Coord& a, uint32_t b) { return a >>= b; }, nb::is_operator(), "n"_a,
|
||||
"In-place component-wise right shift by n bits.")
|
||||
.def(
|
||||
"__iadd__", [](math::Coord& a, int b) { return a += b; }, nb::is_operator(), "n"_a)
|
||||
.def(nb::self + nb::self, "rhs"_a)
|
||||
.def(nb::self - nb::self, "rhs"_a)
|
||||
.def(-nb::self)
|
||||
.def(nb::self += nb::self, "rhs"_a)
|
||||
.def(nb::self -= nb::self, "rhs"_a)
|
||||
.def("minComponent", &math::Coord::minComponent, "other"_a)
|
||||
.def("maxComponent", &math::Coord::maxComponent, "other"_a)
|
||||
.def("offsetBy", nb::overload_cast<ValueType, ValueType, ValueType>(&math::Coord::offsetBy, nb::const_), "dx"_a, "dy"_a, "dz"_a)
|
||||
.def("offsetBy", nb::overload_cast<ValueType>(&math::Coord::offsetBy, nb::const_), "n"_a)
|
||||
.def_static("lessThan", &math::Coord::lessThan, "a"_a, "b"_a)
|
||||
.def_static("Floor", &math::Coord::template Floor<math::Vec3<float>>, "xyz"_a)
|
||||
.def_static("Floor", &math::Coord::template Floor<math::Vec3<double>>, "xyz"_a)
|
||||
.def("hash", &math::Coord::template hash<12>)
|
||||
.def("octant", &math::Coord::octant)
|
||||
.def("asVec3s", &math::Coord::asVec3s)
|
||||
.def("asVec3d", &math::Coord::asVec3d)
|
||||
.def("round", &math::Coord::round)
|
||||
"__iadd__", [](math::Coord& a, int b) { return a += b; }, nb::is_operator(), "n"_a,
|
||||
"In-place add the scalar n to every component.")
|
||||
.def(nb::self + nb::self, "rhs"_a,
|
||||
"Component-wise addition.")
|
||||
.def(nb::self - nb::self, "rhs"_a,
|
||||
"Component-wise subtraction.")
|
||||
.def(-nb::self,
|
||||
"Negate every component.")
|
||||
.def(nb::self += nb::self, "rhs"_a,
|
||||
"In-place component-wise addition.")
|
||||
.def(nb::self -= nb::self, "rhs"_a,
|
||||
"In-place component-wise subtraction.")
|
||||
.def("minComponent", &math::Coord::minComponent, "other"_a,
|
||||
"Component-wise minimum with other. See nanovdb::math::Coord::minComponent in NanoVDB.h.")
|
||||
.def("maxComponent", &math::Coord::maxComponent, "other"_a,
|
||||
"Component-wise maximum with other. See nanovdb::math::Coord::maxComponent in NanoVDB.h.")
|
||||
.def("offsetBy", nb::overload_cast<ValueType, ValueType, ValueType>(&math::Coord::offsetBy, nb::const_), "dx"_a, "dy"_a, "dz"_a,
|
||||
"Return a new Coord offset by (dx, dy, dz). See nanovdb::math::Coord::offsetBy in NanoVDB.h.")
|
||||
.def("offsetBy", nb::overload_cast<ValueType>(&math::Coord::offsetBy, nb::const_), "n"_a,
|
||||
"Return a new Coord offset by n in every component.")
|
||||
.def_static("lessThan", &math::Coord::lessThan, "a"_a, "b"_a,
|
||||
"Component-wise a < b returning a Coord of 0 / 1 flags.")
|
||||
.def_static("Floor", &math::Coord::template Floor<math::Vec3<float>>, "xyz"_a,
|
||||
"Floor each component of a Vec3f to produce an integer Coord.")
|
||||
.def_static("Floor", &math::Coord::template Floor<math::Vec3<double>>, "xyz"_a,
|
||||
"Floor each component of a Vec3d to produce an integer Coord.")
|
||||
.def("hash", &math::Coord::template hash<12>,
|
||||
"Spatial hash of this coordinate suited for hashed root-table lookups.")
|
||||
.def("octant", &math::Coord::octant,
|
||||
"Return the 0..7 octant index of this coordinate's sign bits.")
|
||||
.def("asVec3s", &math::Coord::asVec3s,
|
||||
"Convert to a Vec3f (float) with no scaling.")
|
||||
.def("asVec3d", &math::Coord::asVec3d,
|
||||
"Convert to a Vec3d (double) with no scaling.")
|
||||
.def("round", &math::Coord::round,
|
||||
"Component-wise round; for an integer Coord this is the identity.")
|
||||
.def("__repr__", [](const math::Coord& ijk) {
|
||||
std::stringstream ostr;
|
||||
ostr << ijk;
|
||||
@@ -101,13 +138,20 @@ void defineCoord(nb::module_& m)
|
||||
template<typename T> void defineVec3(nb::module_& m, const char* name, const char* doc)
|
||||
{
|
||||
nb::class_<math::Vec3<T>>(m, name, doc)
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<T>(), "x"_a)
|
||||
.def(nb::init<T, T, T>(), "x"_a, "y"_a, "z"_a)
|
||||
.def(nb::init<math::Vec3<T>>(), "v"_a)
|
||||
.def(nb::init<math::Coord>(), "ijk"_a)
|
||||
.def(nb::self == nb::self, "rhs"_a)
|
||||
.def(nb::self != nb::self, "rhs"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct a zero-initialized vector.")
|
||||
.def(nb::init<T>(), "x"_a,
|
||||
"Construct (x, x, x).")
|
||||
.def(nb::init<T, T, T>(), "x"_a, "y"_a, "z"_a,
|
||||
"Construct (x, y, z).")
|
||||
.def(nb::init<math::Vec3<T>>(), "v"_a,
|
||||
"Copy-construct from another Vec3.")
|
||||
.def(nb::init<math::Coord>(), "ijk"_a,
|
||||
"Construct from an integer Coord, casting each component.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Component-wise equality.")
|
||||
.def(nb::self != nb::self, "rhs"_a,
|
||||
"Component-wise inequality.")
|
||||
.def(
|
||||
"__getitem__",
|
||||
[](const math::Vec3<T>& v, size_t i) {
|
||||
@@ -116,7 +160,8 @@ template<typename T> void defineVec3(nb::module_& m, const char* name, const cha
|
||||
}
|
||||
return v[static_cast<int>(i)];
|
||||
},
|
||||
"i"_a)
|
||||
"i"_a,
|
||||
"Read the i-th component (0=x, 1=y, 2=z).")
|
||||
.def(
|
||||
"__setitem__",
|
||||
[](math::Vec3<T>& v, size_t i, T value) {
|
||||
@@ -126,38 +171,68 @@ template<typename T> void defineVec3(nb::module_& m, const char* name, const cha
|
||||
v[static_cast<int>(i)] = value;
|
||||
},
|
||||
"i"_a,
|
||||
"value"_a)
|
||||
.def("dot", &math::Vec3<T>::template dot<math::Vec3<T>>, "v"_a)
|
||||
.def("cross", &math::Vec3<T>::template cross<math::Vec3<T>>, "v"_a)
|
||||
.def("lengthSqr", &math::Vec3<T>::lengthSqr)
|
||||
.def("length", &math::Vec3<T>::length)
|
||||
.def(-nb::self)
|
||||
.def(nb::self * nb::self, "v"_a)
|
||||
.def(nb::self / nb::self, "v"_a)
|
||||
.def(nb::self + nb::self, "v"_a)
|
||||
.def(nb::self - nb::self, "v"_a)
|
||||
.def(nb::self + math::Coord(), "ijk"_a)
|
||||
.def(nb::self - math::Coord(), "ijk"_a)
|
||||
.def(nb::self * T(), "s"_a)
|
||||
.def(nb::self / T(), "s"_a)
|
||||
.def(nb::self += nb::self, "v"_a)
|
||||
.def(nb::self += math::Coord(), "ijk"_a)
|
||||
.def(nb::self -= nb::self, "v"_a)
|
||||
.def(nb::self -= math::Coord(), "ijk"_a)
|
||||
.def(nb::self *= T(), "s"_a)
|
||||
.def(nb::self /= T(), "s"_a)
|
||||
.def("normalize", &math::Vec3<T>::normalize)
|
||||
.def("minComponent", &math::Vec3<T>::minComponent, "other"_a)
|
||||
.def("maxComponent", &math::Vec3<T>::maxComponent, "other"_a)
|
||||
.def("min", &math::Vec3<T>::min)
|
||||
.def("max", &math::Vec3<T>::max)
|
||||
.def("floor", &math::Vec3<T>::floor)
|
||||
.def("ceil", &math::Vec3<T>::ceil)
|
||||
.def("round", &math::Vec3<T>::round)
|
||||
"value"_a,
|
||||
"Write the i-th component (0=x, 1=y, 2=z).")
|
||||
.def("dot", &math::Vec3<T>::template dot<math::Vec3<T>>, "v"_a,
|
||||
"Dot product with another vector.")
|
||||
.def("cross", &math::Vec3<T>::template cross<math::Vec3<T>>, "v"_a,
|
||||
"Cross product with another vector.")
|
||||
.def("lengthSqr", &math::Vec3<T>::lengthSqr,
|
||||
"Squared Euclidean length (cheaper than length()).")
|
||||
.def("length", &math::Vec3<T>::length,
|
||||
"Euclidean length of this vector.")
|
||||
.def(-nb::self,
|
||||
"Negate every component.")
|
||||
.def(nb::self * nb::self, "v"_a,
|
||||
"Component-wise multiplication.")
|
||||
.def(nb::self / nb::self, "v"_a,
|
||||
"Component-wise division.")
|
||||
.def(nb::self + nb::self, "v"_a,
|
||||
"Component-wise addition.")
|
||||
.def(nb::self - nb::self, "v"_a,
|
||||
"Component-wise subtraction.")
|
||||
.def(nb::self + math::Coord(), "ijk"_a,
|
||||
"Add an integer Coord component-wise.")
|
||||
.def(nb::self - math::Coord(), "ijk"_a,
|
||||
"Subtract an integer Coord component-wise.")
|
||||
.def(nb::self * T(), "s"_a,
|
||||
"Multiply every component by the scalar s.")
|
||||
.def(nb::self / T(), "s"_a,
|
||||
"Divide every component by the scalar s.")
|
||||
.def(nb::self += nb::self, "v"_a,
|
||||
"In-place component-wise addition.")
|
||||
.def(nb::self += math::Coord(), "ijk"_a,
|
||||
"In-place add an integer Coord.")
|
||||
.def(nb::self -= nb::self, "v"_a,
|
||||
"In-place component-wise subtraction.")
|
||||
.def(nb::self -= math::Coord(), "ijk"_a,
|
||||
"In-place subtract an integer Coord.")
|
||||
.def(nb::self *= T(), "s"_a,
|
||||
"In-place scalar multiply.")
|
||||
.def(nb::self /= T(), "s"_a,
|
||||
"In-place scalar divide.")
|
||||
.def("normalize", &math::Vec3<T>::normalize,
|
||||
"Scale this vector to unit length in place.")
|
||||
.def("minComponent", &math::Vec3<T>::minComponent, "other"_a,
|
||||
"Component-wise minimum with other.")
|
||||
.def("maxComponent", &math::Vec3<T>::maxComponent, "other"_a,
|
||||
"Component-wise maximum with other.")
|
||||
.def("min", &math::Vec3<T>::min,
|
||||
"Smallest single component of this vector.")
|
||||
.def("max", &math::Vec3<T>::max,
|
||||
"Largest single component of this vector.")
|
||||
.def("floor", &math::Vec3<T>::floor,
|
||||
"Component-wise floor.")
|
||||
.def("ceil", &math::Vec3<T>::ceil,
|
||||
"Component-wise ceiling.")
|
||||
.def("round", &math::Vec3<T>::round,
|
||||
"Component-wise round.")
|
||||
.def(
|
||||
"__mul__", [](const T& a, math::Vec3<T> b) { return a * b; }, nb::is_operator(), "b"_a)
|
||||
"__mul__", [](const T& a, math::Vec3<T> b) { return a * b; }, nb::is_operator(), "b"_a,
|
||||
"Right-multiply: scalar * Vec3.")
|
||||
.def(
|
||||
"__truediv__", [](const T& a, math::Vec3<T> b) { return a / b; }, nb::is_operator(), "b"_a)
|
||||
"__truediv__", [](const T& a, math::Vec3<T> b) { return a / b; }, nb::is_operator(), "b"_a,
|
||||
"Right-divide: scalar / Vec3, component-wise.")
|
||||
.def("__repr__", [](const math::Vec3<T>& v) {
|
||||
std::stringstream ostr;
|
||||
ostr << v;
|
||||
@@ -168,12 +243,18 @@ template<typename T> void defineVec3(nb::module_& m, const char* name, const cha
|
||||
template<typename T> void defineVec4(nb::module_& m, const char* name, const char* doc)
|
||||
{
|
||||
nb::class_<math::Vec4<T>>(m, name, doc)
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<T>(), "x"_a)
|
||||
.def(nb::init<T, T, T, T>(), "x"_a, "y"_a, "z"_a, "w"_a)
|
||||
.def(nb::init<math::Vec4<T>>(), "v"_a)
|
||||
.def(nb::self == nb::self, "rhs"_a)
|
||||
.def(nb::self != nb::self, "rhs"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct a zero-initialized vector.")
|
||||
.def(nb::init<T>(), "x"_a,
|
||||
"Construct (x, x, x, x).")
|
||||
.def(nb::init<T, T, T, T>(), "x"_a, "y"_a, "z"_a, "w"_a,
|
||||
"Construct (x, y, z, w).")
|
||||
.def(nb::init<math::Vec4<T>>(), "v"_a,
|
||||
"Copy-construct from another Vec4.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Component-wise equality.")
|
||||
.def(nb::self != nb::self, "rhs"_a,
|
||||
"Component-wise inequality.")
|
||||
.def(
|
||||
"__getitem__",
|
||||
[](const math::Vec4<T>& v, size_t i) {
|
||||
@@ -182,7 +263,8 @@ template<typename T> void defineVec4(nb::module_& m, const char* name, const cha
|
||||
}
|
||||
return v[static_cast<int>(i)];
|
||||
},
|
||||
"i"_a)
|
||||
"i"_a,
|
||||
"Read the i-th component (0=x, 1=y, 2=z, 3=w).")
|
||||
.def(
|
||||
"__setitem__",
|
||||
[](math::Vec4<T>& v, size_t i, T value) {
|
||||
@@ -192,28 +274,48 @@ template<typename T> void defineVec4(nb::module_& m, const char* name, const cha
|
||||
v[static_cast<int>(i)] = value;
|
||||
},
|
||||
"i"_a,
|
||||
"value"_a)
|
||||
.def("dot", &math::Vec4<T>::template dot<math::Vec4<T>>, "v"_a)
|
||||
.def("lengthSqr", &math::Vec4<T>::lengthSqr)
|
||||
.def("length", &math::Vec4<T>::length)
|
||||
.def(-nb::self)
|
||||
.def(nb::self * nb::self, "v"_a)
|
||||
.def(nb::self / nb::self, "v"_a)
|
||||
.def(nb::self + nb::self, "v"_a)
|
||||
.def(nb::self - nb::self, "v"_a)
|
||||
.def(nb::self * T(), "s"_a)
|
||||
.def(nb::self / T(), "s"_a)
|
||||
.def(nb::self += nb::self, "v"_a)
|
||||
.def(nb::self -= nb::self, "v"_a)
|
||||
.def(nb::self *= T(), "s"_a)
|
||||
.def(nb::self /= T(), "s"_a)
|
||||
.def("normalize", &math::Vec4<T>::normalize)
|
||||
.def("minComponent", &math::Vec4<T>::minComponent, "other"_a)
|
||||
.def("maxComponent", &math::Vec4<T>::maxComponent, "other"_a)
|
||||
"value"_a,
|
||||
"Write the i-th component (0=x, 1=y, 2=z, 3=w).")
|
||||
.def("dot", &math::Vec4<T>::template dot<math::Vec4<T>>, "v"_a,
|
||||
"Dot product with another vector.")
|
||||
.def("lengthSqr", &math::Vec4<T>::lengthSqr,
|
||||
"Squared Euclidean length (cheaper than length()).")
|
||||
.def("length", &math::Vec4<T>::length,
|
||||
"Euclidean length of this vector.")
|
||||
.def(-nb::self,
|
||||
"Negate every component.")
|
||||
.def(nb::self * nb::self, "v"_a,
|
||||
"Component-wise multiplication.")
|
||||
.def(nb::self / nb::self, "v"_a,
|
||||
"Component-wise division.")
|
||||
.def(nb::self + nb::self, "v"_a,
|
||||
"Component-wise addition.")
|
||||
.def(nb::self - nb::self, "v"_a,
|
||||
"Component-wise subtraction.")
|
||||
.def(nb::self * T(), "s"_a,
|
||||
"Multiply every component by the scalar s.")
|
||||
.def(nb::self / T(), "s"_a,
|
||||
"Divide every component by the scalar s.")
|
||||
.def(nb::self += nb::self, "v"_a,
|
||||
"In-place component-wise addition.")
|
||||
.def(nb::self -= nb::self, "v"_a,
|
||||
"In-place component-wise subtraction.")
|
||||
.def(nb::self *= T(), "s"_a,
|
||||
"In-place scalar multiply.")
|
||||
.def(nb::self /= T(), "s"_a,
|
||||
"In-place scalar divide.")
|
||||
.def("normalize", &math::Vec4<T>::normalize,
|
||||
"Scale this vector to unit length in place.")
|
||||
.def("minComponent", &math::Vec4<T>::minComponent, "other"_a,
|
||||
"Component-wise minimum with other.")
|
||||
.def("maxComponent", &math::Vec4<T>::maxComponent, "other"_a,
|
||||
"Component-wise maximum with other.")
|
||||
.def(
|
||||
"__mul__", [](const T& a, math::Vec4<T> b) { return a * b; }, nb::is_operator(), "b"_a)
|
||||
"__mul__", [](const T& a, math::Vec4<T> b) { return a * b; }, nb::is_operator(), "b"_a,
|
||||
"Right-multiply: scalar * Vec4.")
|
||||
.def(
|
||||
"__truediv__", [](const T& a, math::Vec4<T> b) { return a / b; }, nb::is_operator(), "b"_a)
|
||||
"__truediv__", [](const T& a, math::Vec4<T> b) { return a / b; }, nb::is_operator(), "b"_a,
|
||||
"Right-divide: scalar / Vec4, component-wise.")
|
||||
.def("__repr__", [](const math::Vec4<T>& v) {
|
||||
std::stringstream ostr;
|
||||
ostr << v;
|
||||
@@ -226,18 +328,30 @@ void defineRgba8(nb::module_& m)
|
||||
using ValueType = math::Rgba8::ValueType;
|
||||
|
||||
nb::class_<math::Rgba8>(m, "Rgba8", "8-bit red, green, blue, alpha packed into 32 bit unsigned int")
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const math::Rgba8&>(), "other"_a)
|
||||
.def(nb::init<uint8_t, uint8_t, uint8_t, uint8_t>(), "r"_a, "g"_a, "b"_a, "a"_a = 255)
|
||||
.def(nb::init<uint8_t>(), "v"_a)
|
||||
.def(nb::init<float, float, float, float>(), "r"_a, "g"_a, "b"_a, "a"_a = 1.0)
|
||||
.def(nb::init<Vec3f>(), "rgb"_a)
|
||||
.def(nb::init<Vec4f>(), "rgba"_a)
|
||||
.def(nb::self < nb::self, "rhs"_a)
|
||||
.def(nb::self == nb::self, "rhs"_a)
|
||||
.def("lengthSqr", &math::Rgba8::lengthSqr)
|
||||
.def("length", &math::Rgba8::length)
|
||||
.def("asFloat", &math::Rgba8::asFloat, "n"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct a fully transparent black Rgba8 (all components 0).")
|
||||
.def(nb::init<const math::Rgba8&>(), "other"_a,
|
||||
"Copy-construct from another Rgba8.")
|
||||
.def(nb::init<uint8_t, uint8_t, uint8_t, uint8_t>(), "r"_a, "g"_a, "b"_a, "a"_a = 255,
|
||||
"Construct from four 0..255 uint8 channels; a defaults to fully opaque.")
|
||||
.def(nb::init<uint8_t>(), "v"_a,
|
||||
"Construct a gray Rgba8 with every channel set to v.")
|
||||
.def(nb::init<float, float, float, float>(), "r"_a, "g"_a, "b"_a, "a"_a = 1.0,
|
||||
"Construct from four 0..1 floats, clamped and quantized to uint8.")
|
||||
.def(nb::init<Vec3f>(), "rgb"_a,
|
||||
"Construct from an RGB float triple; alpha defaults to opaque.")
|
||||
.def(nb::init<Vec4f>(), "rgba"_a,
|
||||
"Construct from an RGBA float quadruple.")
|
||||
.def(nb::self < nb::self, "rhs"_a,
|
||||
"Less-than comparison on the packed uint32 representation.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Equality on the packed uint32 representation.")
|
||||
.def("lengthSqr", &math::Rgba8::lengthSqr,
|
||||
"Squared length over (r, g, b, a) as integers.")
|
||||
.def("length", &math::Rgba8::length,
|
||||
"Euclidean length over (r, g, b, a) as floats.")
|
||||
.def("asFloat", &math::Rgba8::asFloat, "n"_a,
|
||||
"Return the n-th channel as a 0..1 float.")
|
||||
.def(
|
||||
"__getitem__",
|
||||
[](const math::Rgba8& rgba, size_t i) {
|
||||
@@ -246,7 +360,8 @@ void defineRgba8(nb::module_& m)
|
||||
}
|
||||
return rgba[static_cast<int>(i)];
|
||||
},
|
||||
"i"_a)
|
||||
"i"_a,
|
||||
"Read the i-th channel as a uint8 (0=r, 1=g, 2=b, 3=a).")
|
||||
.def(
|
||||
"__setitem__",
|
||||
[](math::Rgba8& rgba, size_t i, ValueType value) {
|
||||
@@ -256,26 +371,38 @@ void defineRgba8(nb::module_& m)
|
||||
rgba[static_cast<int>(i)] = value;
|
||||
},
|
||||
"i"_a,
|
||||
"value"_a)
|
||||
"value"_a,
|
||||
"Write the i-th channel as a uint8 (0=r, 1=g, 2=b, 3=a).")
|
||||
.def_prop_rw(
|
||||
"packed", [](const math::Rgba8& rgba) { return rgba.packed(); }, [](math::Rgba8& rgba, uint32_t packed) { rgba.packed() = packed; })
|
||||
"packed", [](const math::Rgba8& rgba) { return rgba.packed(); }, [](math::Rgba8& rgba, uint32_t packed) { rgba.packed() = packed; },
|
||||
"The raw 32-bit packed RGBA representation.")
|
||||
.def_prop_rw(
|
||||
"r", [](const math::Rgba8& rgba) { return rgba.r(); }, [](math::Rgba8& rgba, uint8_t r) { rgba.r() = r; })
|
||||
"r", [](const math::Rgba8& rgba) { return rgba.r(); }, [](math::Rgba8& rgba, uint8_t r) { rgba.r() = r; },
|
||||
"Red channel as a uint8 0..255.")
|
||||
.def_prop_rw(
|
||||
"g", [](const math::Rgba8& rgba) { return rgba.g(); }, [](math::Rgba8& rgba, uint8_t g) { rgba.g() = g; })
|
||||
"g", [](const math::Rgba8& rgba) { return rgba.g(); }, [](math::Rgba8& rgba, uint8_t g) { rgba.g() = g; },
|
||||
"Green channel as a uint8 0..255.")
|
||||
.def_prop_rw(
|
||||
"b", [](const math::Rgba8& rgba) { return rgba.b(); }, [](math::Rgba8& rgba, uint8_t b) { rgba.b() = b; })
|
||||
"b", [](const math::Rgba8& rgba) { return rgba.b(); }, [](math::Rgba8& rgba, uint8_t b) { rgba.b() = b; },
|
||||
"Blue channel as a uint8 0..255.")
|
||||
.def_prop_rw(
|
||||
"a", [](const math::Rgba8& rgba) { return rgba.a(); }, [](math::Rgba8& rgba, uint8_t a) { rgba.a() = a; })
|
||||
.def("asVec3f", [](const math::Rgba8& rgba) { return Vec3f(rgba); })
|
||||
.def("asVec4f", [](const math::Rgba8& rgba) { return Vec4f(rgba); });
|
||||
"a", [](const math::Rgba8& rgba) { return rgba.a(); }, [](math::Rgba8& rgba, uint8_t a) { rgba.a() = a; },
|
||||
"Alpha channel as a uint8 0..255.")
|
||||
.def("asVec3f", [](const math::Rgba8& rgba) { return Vec3f(rgba); },
|
||||
"Convert RGB channels to a Vec3f of 0..1 floats (alpha dropped).")
|
||||
.def("asVec4f", [](const math::Rgba8& rgba) { return Vec4f(rgba); },
|
||||
"Convert RGBA channels to a Vec4f of 0..1 floats.");
|
||||
}
|
||||
|
||||
template<typename Vec3T> void defineBaseBBox(nb::module_& m, const char* name)
|
||||
{
|
||||
nb::class_<math::BaseBBox<Vec3T>>(m, name)
|
||||
.def(nb::self == nb::self, "rhs"_a)
|
||||
.def(nb::self != nb::self, "rhs"_a)
|
||||
nb::class_<math::BaseBBox<Vec3T>>(m, name,
|
||||
"Axis-aligned bounding-box base class. Stores a min / max corner; "
|
||||
"concrete subclasses add the open-interval vs closed-interval semantics.")
|
||||
.def(nb::self == nb::self, "rhs"_a,
|
||||
"Equality of both min and max corners.")
|
||||
.def(nb::self != nb::self, "rhs"_a,
|
||||
"Inequality of either min or max corner.")
|
||||
.def(
|
||||
"__getitem__",
|
||||
[](const math::BaseBBox<Vec3T>& bbox, size_t i) {
|
||||
@@ -284,7 +411,8 @@ template<typename Vec3T> void defineBaseBBox(nb::module_& m, const char* name)
|
||||
}
|
||||
return bbox[static_cast<int>(i)];
|
||||
},
|
||||
"i"_a)
|
||||
"i"_a,
|
||||
"Read corner 0 (min) or corner 1 (max).")
|
||||
.def(
|
||||
"__setitem__",
|
||||
[](math::BaseBBox<Vec3T>& bbox, size_t i, const Vec3T& value) {
|
||||
@@ -294,29 +422,45 @@ template<typename Vec3T> void defineBaseBBox(nb::module_& m, const char* name)
|
||||
bbox[static_cast<int>(i)] = value;
|
||||
},
|
||||
"i"_a,
|
||||
"value"_a)
|
||||
"value"_a,
|
||||
"Write corner 0 (min) or corner 1 (max).")
|
||||
.def_prop_rw(
|
||||
"min", [](const math::BaseBBox<Vec3T>& bbox) { return bbox.min(); }, [](math::BaseBBox<Vec3T>& bbox, const Vec3T& min) { bbox.min() = min; })
|
||||
"min", [](const math::BaseBBox<Vec3T>& bbox) { return bbox.min(); }, [](math::BaseBBox<Vec3T>& bbox, const Vec3T& min) { bbox.min() = min; },
|
||||
"Minimum corner of the bounding box.")
|
||||
.def_prop_rw(
|
||||
"max", [](const math::BaseBBox<Vec3T>& bbox) { return bbox.max(); }, [](math::BaseBBox<Vec3T>& bbox, const Vec3T& max) { bbox.max() = max; })
|
||||
.def("translate", &math::BaseBBox<Vec3T>::translate, "xyz"_a)
|
||||
.def("expand", nb::overload_cast<const Vec3T&>(&math::BaseBBox<Vec3T>::expand), "xyz"_a)
|
||||
.def("expand", nb::overload_cast<const math::BaseBBox<Vec3T>&>(&math::BaseBBox<Vec3T>::expand), "bbox"_a)
|
||||
.def("intersect", &math::BaseBBox<Vec3T>::intersect, "bbox"_a)
|
||||
.def("isInside", &math::BaseBBox<Vec3T>::isInside, "xyz"_a);
|
||||
"max", [](const math::BaseBBox<Vec3T>& bbox) { return bbox.max(); }, [](math::BaseBBox<Vec3T>& bbox, const Vec3T& max) { bbox.max() = max; },
|
||||
"Maximum corner of the bounding box.")
|
||||
.def("translate", &math::BaseBBox<Vec3T>::translate, "xyz"_a,
|
||||
"Translate this bounding box by xyz in place.")
|
||||
.def("expand", nb::overload_cast<const Vec3T&>(&math::BaseBBox<Vec3T>::expand), "xyz"_a,
|
||||
"Grow this bounding box to include the point xyz.")
|
||||
.def("expand", nb::overload_cast<const math::BaseBBox<Vec3T>&>(&math::BaseBBox<Vec3T>::expand), "bbox"_a,
|
||||
"Grow this bounding box to include another bbox in its entirety.")
|
||||
.def("intersect", &math::BaseBBox<Vec3T>::intersect, "bbox"_a,
|
||||
"Shrink this bounding box to the intersection with bbox.")
|
||||
.def("isInside", &math::BaseBBox<Vec3T>::isInside, "xyz"_a,
|
||||
"True iff xyz lies inside this bounding box.");
|
||||
}
|
||||
|
||||
template<typename Vec3T> void defineBBoxFloatingPoint(nb::module_& m, const char* name, const char* doc)
|
||||
{
|
||||
nb::class_<math::BBox<Vec3T, true>, math::BaseBBox<Vec3T>>(m, name, doc)
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const Vec3T&, const Vec3T&>(), "min"_a, "max"_a)
|
||||
.def(nb::init<const math::Coord&, const math::Coord&>(), "min"_a, "max"_a)
|
||||
.def_static("createCube", &math::BBox<Vec3T>::createCube, "min"_a, "dim"_a)
|
||||
.def(nb::init<const math::BaseBBox<math::Coord>&>(), "bbox"_a)
|
||||
.def("empty", &math::BBox<Vec3T>::empty)
|
||||
.def("dim", &math::BBox<Vec3T>::dim)
|
||||
.def("isInside", &math::BBox<Vec3T>::isInside, "p"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct an empty bounding box (min > max sentinel).")
|
||||
.def(nb::init<const Vec3T&, const Vec3T&>(), "min"_a, "max"_a,
|
||||
"Construct from explicit min and max corners.")
|
||||
.def(nb::init<const math::Coord&, const math::Coord&>(), "min"_a, "max"_a,
|
||||
"Construct from integer Coord corners, cast to floating-point.")
|
||||
.def_static("createCube", &math::BBox<Vec3T>::createCube, "min"_a, "dim"_a,
|
||||
"Construct an axis-aligned cube of side dim anchored at min.")
|
||||
.def(nb::init<const math::BaseBBox<math::Coord>&>(), "bbox"_a,
|
||||
"Construct from an integer CoordBBox, cast to floating-point.")
|
||||
.def("empty", &math::BBox<Vec3T>::empty,
|
||||
"True iff this bbox is empty (any min component > the matching max).")
|
||||
.def("dim", &math::BBox<Vec3T>::dim,
|
||||
"Return max - min as a Vec3 of side lengths.")
|
||||
.def("isInside", &math::BBox<Vec3T>::isInside, "p"_a,
|
||||
"True iff p lies inside this bounding box (half-open interval).")
|
||||
.def("__repr__", [](const math::BBox<Vec3T>& b) {
|
||||
std::stringstream ostr;
|
||||
ostr << b;
|
||||
@@ -329,24 +473,39 @@ template<typename CoordT> void defineBBoxInteger(nb::module_& m, const char* nam
|
||||
using ValueType = typename CoordT::ValueType;
|
||||
|
||||
nb::class_<math::BBox<CoordT, false>, math::BaseBBox<CoordT>>(m, name, doc)
|
||||
.def(nb::init<>())
|
||||
.def(nb::init<const CoordT&, const CoordT&>(), "min"_a, "max"_a)
|
||||
.def(nb::init<>(),
|
||||
"Construct an empty CoordBBox (min > max sentinel).")
|
||||
.def(nb::init<const CoordT&, const CoordT&>(), "min"_a, "max"_a,
|
||||
"Construct from explicit min and max Coord corners (inclusive).")
|
||||
.def(
|
||||
"__iter__",
|
||||
[](const math::BBox<CoordT>& b) { return nb::make_iterator(nb::type<math::BBox<CoordT>>(), "CoordBBoxIterator", b.begin(), b.end()); },
|
||||
nb::keep_alive<0, 1>())
|
||||
.def_static("createCube", nb::overload_cast<const CoordT&, ValueType>(&math::BBox<CoordT>::createCube), "min"_a, "dim"_a)
|
||||
.def_static("createCube", nb::overload_cast<ValueType, ValueType>(&math::BBox<CoordT>::createCube), "min"_a, "max"_a)
|
||||
.def("is_divisible", &math::BBox<CoordT>::is_divisible)
|
||||
.def("empty", &math::BBox<CoordT>::empty)
|
||||
.def("dim", &math::BBox<CoordT>::dim)
|
||||
.def("volume", &math::BBox<CoordT>::volume)
|
||||
.def("isInside", nb::overload_cast<const CoordT&>(&math::BBox<CoordT>::isInside, nb::const_), "p"_a)
|
||||
.def("isInside", nb::overload_cast<const math::BBox<CoordT>&>(&math::BBox<CoordT>::isInside, nb::const_), "b"_a)
|
||||
.def("asFloat", &math::BBox<CoordT>::template asReal<float>)
|
||||
.def("asDouble", &math::BBox<CoordT>::template asReal<double>)
|
||||
.def("hasOverlap", &math::BBox<CoordT>::hasOverlap, "b"_a)
|
||||
.def("expandBy", &math::BBox<CoordT>::expandBy, "padding"_a)
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Iterate over every Coord in this CoordBBox in row-major order.")
|
||||
.def_static("createCube", nb::overload_cast<const CoordT&, ValueType>(&math::BBox<CoordT>::createCube), "min"_a, "dim"_a,
|
||||
"Construct a cube of side dim voxels anchored at the min Coord.")
|
||||
.def_static("createCube", nb::overload_cast<ValueType, ValueType>(&math::BBox<CoordT>::createCube), "min"_a, "max"_a,
|
||||
"Construct a cube spanning [min, max] in every axis.")
|
||||
.def("is_divisible", &math::BBox<CoordT>::is_divisible,
|
||||
"True iff this CoordBBox has more than one voxel in every axis.")
|
||||
.def("empty", &math::BBox<CoordT>::empty,
|
||||
"True iff this CoordBBox is empty (any min component > the matching max).")
|
||||
.def("dim", &math::BBox<CoordT>::dim,
|
||||
"Return max - min + 1 as a Coord of side lengths (inclusive).")
|
||||
.def("volume", &math::BBox<CoordT>::volume,
|
||||
"Total number of voxels enclosed by this CoordBBox.")
|
||||
.def("isInside", nb::overload_cast<const CoordT&>(&math::BBox<CoordT>::isInside, nb::const_), "p"_a,
|
||||
"True iff the integer point p lies inside this CoordBBox.")
|
||||
.def("isInside", nb::overload_cast<const math::BBox<CoordT>&>(&math::BBox<CoordT>::isInside, nb::const_), "b"_a,
|
||||
"True iff b lies entirely inside this CoordBBox.")
|
||||
.def("asFloat", &math::BBox<CoordT>::template asReal<float>,
|
||||
"Convert this CoordBBox to a floating-point BBox of Vec3f.")
|
||||
.def("asDouble", &math::BBox<CoordT>::template asReal<double>,
|
||||
"Convert this CoordBBox to a floating-point BBox of Vec3d.")
|
||||
.def("hasOverlap", &math::BBox<CoordT>::hasOverlap, "b"_a,
|
||||
"True iff this CoordBBox shares any voxel with b.")
|
||||
.def("expandBy", &math::BBox<CoordT>::expandBy, "padding"_a,
|
||||
"Grow this CoordBBox by the given padding in every direction.")
|
||||
.def("__repr__", [](const CoordBBox& b) {
|
||||
std::stringstream ostr;
|
||||
ostr << b;
|
||||
@@ -377,33 +536,12 @@ void defineMathModule(nb::module_& m)
|
||||
defineBaseBBox<math::Coord>(m, "CoordBaseBBox");
|
||||
defineBBoxInteger<math::Coord>(m, "CoordBBox", "Bounding box for Coord minimum and maximum");
|
||||
|
||||
defineNearestNeighborSampler<float>(m, "FloatNearestNeighborSampler");
|
||||
defineTrilinearSampler<float>(m, "FloatTrilinearSampler");
|
||||
defineTriquadraticSampler<float>(m, "FloatTriquadraticSampler");
|
||||
defineTricubicSampler<float>(m, "FloatTricubicSampler");
|
||||
|
||||
defineNearestNeighborSampler<double>(m, "DoubleNearestNeighborSampler");
|
||||
defineTrilinearSampler<double>(m, "DoubleTrilinearSampler");
|
||||
defineTriquadraticSampler<double>(m, "DoubleTriquadraticSampler");
|
||||
defineTricubicSampler<double>(m, "DoubleTricubicSampler");
|
||||
|
||||
defineNearestNeighborSampler<int32_t>(m, "Int32NearestNeighborSampler");
|
||||
defineTrilinearSampler<int32_t>(m, "Int32TrilinearSampler");
|
||||
defineTriquadraticSampler<int32_t>(m, "Int32TriquadraticSampler");
|
||||
defineTricubicSampler<int32_t>(m, "Int32TricubicSampler");
|
||||
|
||||
defineNearestNeighborSampler<Vec3f>(m, "Vec3fNearestNeighborSampler");
|
||||
defineTrilinearSampler<Vec3f>(m, "Vec3fTrilinearSampler");
|
||||
defineTriquadraticSampler<Vec3f>(m, "Vec3fTriquadraticSampler");
|
||||
defineTricubicSampler<Vec3f>(m, "Vec3fTricubicSampler");
|
||||
|
||||
#ifdef NANOVDB_USE_CUDA
|
||||
nb::module_ cudaModule = m.def_submodule("cuda");
|
||||
cudaModule.doc() = "A submodule that implements CUDA-accelerated math functions";
|
||||
|
||||
defineSampleFromVoxels<float>(cudaModule, "sampleFromVoxels");
|
||||
defineSampleFromVoxels<double>(cudaModule, "sampleFromVoxels");
|
||||
#endif
|
||||
#define NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix) \
|
||||
defineNearestNeighborSampler<T>(m, #Suffix "NearestNeighborSampler"); \
|
||||
defineTrilinearSampler<T>(m, #Suffix "TrilinearSampler"); \
|
||||
defineTriquadraticSampler<T>(m, #Suffix "TriquadraticSampler"); \
|
||||
defineTricubicSampler<T>(m, #Suffix "TricubicSampler");
|
||||
#include "BuildTypes.def"
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -32,11 +32,9 @@ GridHandle<BufferT> createLevelSetSphere(GridType gridType,
|
||||
switch (gridType) {
|
||||
case GridType::Float: return createLevelSetSphere<float, BufferT>(radius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double: return createLevelSetSphere<double, BufferT>(radius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
// ss << "Cannot createLevelSetSphere for grid of type \"" << toStr(gridType);
|
||||
throw std::runtime_error(ss.str() + "\"");
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createLevelSetSphere: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +56,9 @@ GridHandle<BufferT> createLevelSetTorus(GridType gridType,
|
||||
return createLevelSetTorus<float, BufferT>(majorRadius, minorRadius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return createLevelSetTorus<double, BufferT>(majorRadius, minorRadius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
// ss << "Cannot createLevelSetTorus for grid of type \"" << toStr(gridType);
|
||||
throw std::runtime_error(ss.str() + "\"");
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createLevelSetTorus: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +77,9 @@ GridHandle<BufferT> createFogVolumeSphere(GridType gridType,
|
||||
switch (gridType) {
|
||||
case GridType::Float: return createFogVolumeSphere<float, BufferT>(radius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double: return createFogVolumeSphere<double, BufferT>(radius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
// ss << "Cannot createFogVolumeSphere for grid of type \"" << toStr(gridType);
|
||||
throw std::runtime_error(ss.str() + "\"");
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createFogVolumeSphere: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,14 +101,218 @@ GridHandle<BufferT> createFogVolumeTorus(GridType gridType,
|
||||
return createFogVolumeTorus<float, BufferT>(majorRadius, minorRadius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return createFogVolumeTorus<double, BufferT>(majorRadius, minorRadius, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default: {
|
||||
std::stringstream ss;
|
||||
// ss << "Cannot createFogVolumeTorus for grid of type \"" << toStr(gridType);
|
||||
throw std::runtime_error(ss.str() + "\"");
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createFogVolumeTorus: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- New primitives ----------
|
||||
// Same float/double switch pattern as the four existing primitives above.
|
||||
// The C++ templates also accept Fp4/Fp8/Fp16/FpN; those flavors live on
|
||||
// the createNanoGrid path with explicit oracle and dither parameters
|
||||
// rather than being expressed as primitive overloads here.
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createLevelSetBox(GridType gridType,
|
||||
double width,
|
||||
double height,
|
||||
double depth,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
double halfWidth,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
switch (gridType) {
|
||||
case GridType::Float:
|
||||
return tools::createLevelSetBox<float, BufferT>(
|
||||
width, height, depth, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return tools::createLevelSetBox<double, BufferT>(
|
||||
width, height, depth, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createLevelSetBox: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createLevelSetBBox(GridType gridType,
|
||||
double width,
|
||||
double height,
|
||||
double depth,
|
||||
double thickness,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
double halfWidth,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
switch (gridType) {
|
||||
case GridType::Float:
|
||||
return tools::createLevelSetBBox<float, BufferT>(
|
||||
width, height, depth, thickness, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return tools::createLevelSetBBox<double, BufferT>(
|
||||
width, height, depth, thickness, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createLevelSetBBox: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createLevelSetOctahedron(GridType gridType,
|
||||
double scale,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
double halfWidth,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
switch (gridType) {
|
||||
case GridType::Float:
|
||||
return tools::createLevelSetOctahedron<float, BufferT>(
|
||||
scale, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return tools::createLevelSetOctahedron<double, BufferT>(
|
||||
scale, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createLevelSetOctahedron: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createFogVolumeBox(GridType gridType,
|
||||
double width,
|
||||
double height,
|
||||
double depth,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
double halfWidth,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
switch (gridType) {
|
||||
case GridType::Float:
|
||||
return tools::createFogVolumeBox<float, BufferT>(
|
||||
width, height, depth, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return tools::createFogVolumeBox<double, BufferT>(
|
||||
width, height, depth, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createFogVolumeBox: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createFogVolumeOctahedron(GridType gridType,
|
||||
double scale,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
double halfWidth,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
tools::StatsMode sMode,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
switch (gridType) {
|
||||
case GridType::Float:
|
||||
return tools::createFogVolumeOctahedron<float, BufferT>(
|
||||
scale, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
case GridType::Double:
|
||||
return tools::createFogVolumeOctahedron<double, BufferT>(
|
||||
scale, center, voxelSize, halfWidth, origin, name, sMode, cMode, buffer);
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"createFogVolumeOctahedron: only float and double grid types are supported");
|
||||
}
|
||||
}
|
||||
|
||||
// Point primitives. The result is always a PointDataGrid (uint32 storage),
|
||||
// so unlike the level-set / fog-volume primitives there's no value-type
|
||||
// dispatch worth exposing — the intermediate level-set's precision is
|
||||
// not user-controllable in this binding. The C++ template also accepts
|
||||
// BuildT=double, but that path segfaults during scatter at least with the
|
||||
// current C++ implementation, so the binding stays on the float-only
|
||||
// instantiation that's exercised by the C++ unit tests.
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createPointSphere(int pointsPerVoxel,
|
||||
double radius,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
CheckMode mode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
return tools::createPointSphere<float, BufferT>(
|
||||
pointsPerVoxel, radius, center, voxelSize, origin, name, mode, buffer);
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createPointTorus(int pointsPerVoxel,
|
||||
double majorRadius,
|
||||
double minorRadius,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
CheckMode cMode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
return tools::createPointTorus<float, BufferT>(
|
||||
pointsPerVoxel, majorRadius, minorRadius, center, voxelSize, origin, name, cMode, buffer);
|
||||
}
|
||||
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createPointBox(int pointsPerVoxel,
|
||||
double width,
|
||||
double height,
|
||||
double depth,
|
||||
const Vec3d& center,
|
||||
double voxelSize,
|
||||
const Vec3d& origin,
|
||||
const std::string& name,
|
||||
CheckMode mode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
return tools::createPointBox<float, BufferT>(
|
||||
pointsPerVoxel, width, height, depth, center, voxelSize, origin, name, mode, buffer);
|
||||
}
|
||||
|
||||
// createPointScatter takes an existing level set as its source. We bind
|
||||
// the float source variant — the C++ template also accepts double, but
|
||||
// the existing primitives and tests use float, and the source grid is
|
||||
// the runtime-typed nanovdb::NanoGrid<float>, so a single overload keeps
|
||||
// the Python surface simple.
|
||||
template<typename BufferT>
|
||||
GridHandle<BufferT> createPointScatter(const NanoGrid<float>& srcGrid,
|
||||
int pointsPerVoxel,
|
||||
const std::string& name,
|
||||
CheckMode mode,
|
||||
const BufferT& buffer)
|
||||
{
|
||||
return tools::createPointScatter<float, BufferT>(
|
||||
srcGrid, pointsPerVoxel, name, mode, buffer);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename BufferT> void definePrimitives(nb::module_& m)
|
||||
@@ -131,7 +329,8 @@ template<typename BufferT> void definePrimitives(nb::module_& m)
|
||||
"name"_a = "sphere_ls",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Narrow-band level set of a sphere of the given radius and center.");
|
||||
|
||||
m.def("createLevelSetTorus",
|
||||
nb::overload_cast<GridType,
|
||||
@@ -155,7 +354,8 @@ template<typename BufferT> void definePrimitives(nb::module_& m)
|
||||
"name"_a = "torus_ls",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Narrow-band level set of a torus with the given major and minor radii.");
|
||||
|
||||
m.def("createFogVolumeSphere",
|
||||
nb::overload_cast<GridType, double, const Vec3d&, double, double, const Vec3d&, const std::string&, tools::StatsMode, CheckMode, const BufferT&>(
|
||||
@@ -169,7 +369,8 @@ template<typename BufferT> void definePrimitives(nb::module_& m)
|
||||
"name"_a = "sphere_fog",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Sparse fog volume of a sphere of the given radius and center.");
|
||||
|
||||
m.def("createFogVolumeTorus",
|
||||
nb::overload_cast<GridType,
|
||||
@@ -193,7 +394,139 @@ template<typename BufferT> void definePrimitives(nb::module_& m)
|
||||
"name"_a = "torus_fog",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT());
|
||||
"buffer"_a = BufferT(),
|
||||
"Sparse fog volume of a torus with the given major and minor radii.");
|
||||
|
||||
// ---------- Level-set / fog-volume box / bbox / octahedron primitives ----
|
||||
m.def("createLevelSetBox", &createLevelSetBox<BufferT>,
|
||||
"gridType"_a = GridType::Float,
|
||||
"width"_a = 40.0,
|
||||
"height"_a = 60.0,
|
||||
"depth"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"halfWidth"_a = 3.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "box_ls",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Narrow-band level set of an axis-aligned box.");
|
||||
|
||||
m.def("createLevelSetBBox", &createLevelSetBBox<BufferT>,
|
||||
"gridType"_a = GridType::Float,
|
||||
"width"_a = 40.0,
|
||||
"height"_a = 60.0,
|
||||
"depth"_a = 100.0,
|
||||
"thickness"_a = 10.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"halfWidth"_a = 3.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "bbox_ls",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Narrow-band level set of a hollow box wireframe (BBox = bounding "
|
||||
"box edges with the given thickness).");
|
||||
|
||||
m.def("createLevelSetOctahedron", &createLevelSetOctahedron<BufferT>,
|
||||
"gridType"_a = GridType::Float,
|
||||
"scale"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"halfWidth"_a = 3.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
// Default name spells the shape correctly even though the
|
||||
// upstream C++ default still carries the historical
|
||||
// "octadedron_ls" typo. Callers can override either way.
|
||||
"name"_a = "octahedron_ls",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Narrow-band level set of an octahedron.");
|
||||
|
||||
m.def("createFogVolumeBox", &createFogVolumeBox<BufferT>,
|
||||
"gridType"_a = GridType::Float,
|
||||
"width"_a = 40.0,
|
||||
"height"_a = 60.0,
|
||||
"depth"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"halfWidth"_a = 3.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "box_fog",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Sparse fog volume of a box (exterior 0/inactive, interior active "
|
||||
"with values smoothly varying from 0 at the surface to 1 inside).");
|
||||
|
||||
m.def("createFogVolumeOctahedron", &createFogVolumeOctahedron<BufferT>,
|
||||
"gridType"_a = GridType::Float,
|
||||
"scale"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"halfWidth"_a = 3.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "octahedron_fog",
|
||||
"sMode"_a = tools::StatsMode::Default,
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Sparse fog volume of an octahedron.");
|
||||
|
||||
// ---------- Point primitives ----------
|
||||
m.def("createPointSphere", &createPointSphere<BufferT>,
|
||||
"pointsPerVoxel"_a = 1,
|
||||
"radius"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "sphere_points",
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"PointDataGrid of points scattered on the surface of a sphere. "
|
||||
"The output grid is always a UInt32 PointDataGrid; the "
|
||||
"intermediate level-set's value type is hard-coded to float.");
|
||||
|
||||
m.def("createPointTorus", &createPointTorus<BufferT>,
|
||||
"pointsPerVoxel"_a = 1,
|
||||
"majorRadius"_a = 100.0,
|
||||
"minorRadius"_a = 50.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "torus_points",
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"PointDataGrid of points scattered on the surface of a torus. "
|
||||
"Always returns a UInt32 PointDataGrid.");
|
||||
|
||||
m.def("createPointBox", &createPointBox<BufferT>,
|
||||
"pointsPerVoxel"_a = 1,
|
||||
"width"_a = 40.0,
|
||||
"height"_a = 60.0,
|
||||
"depth"_a = 100.0,
|
||||
"center"_a = Vec3d(0.0),
|
||||
"voxelSize"_a = 1.0,
|
||||
"origin"_a = Vec3d(0.0),
|
||||
"name"_a = "box_points",
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"PointDataGrid of points scattered on the surface of a box. "
|
||||
"Always returns a UInt32 PointDataGrid.");
|
||||
|
||||
m.def("createPointScatter", &createPointScatter<BufferT>,
|
||||
"srcGrid"_a,
|
||||
"pointsPerVoxel"_a = 1,
|
||||
"name"_a = "point_scatter",
|
||||
"cMode"_a = CheckMode::Default,
|
||||
"buffer"_a = BufferT(),
|
||||
"Scatter a PointDataGrid into the active voxels of a "
|
||||
"NanoGrid<float> level set. The source grid must satisfy "
|
||||
"srcGrid.isLevelSet() and have an active bounding box; "
|
||||
"non-level-set sources (e.g. fog volumes) raise RuntimeError. "
|
||||
"Point coordinates are stored as blind data in world space.");
|
||||
}
|
||||
|
||||
template void definePrimitives<HostBuffer>(nb::module_&);
|
||||
|
||||
@@ -15,20 +15,53 @@ namespace {
|
||||
|
||||
template<typename TreeT, int Order> void defineSampleFromVoxels(nb::module_& m, const char* name)
|
||||
{
|
||||
using CoordT = typename TreeT::CoordType;
|
||||
nb::class_<math::SampleFromVoxels<TreeT, Order, false>>(m, name)
|
||||
using CoordT = typename TreeT::CoordType;
|
||||
using ValueT = typename TreeT::ValueType;
|
||||
using SamplerT = math::SampleFromVoxels<TreeT, Order, false>;
|
||||
auto cls = nb::class_<SamplerT>(m, name,
|
||||
"Callable sampler that reconstructs a grid value at an arbitrary "
|
||||
"index-space position. Build via the matching create*Sampler() factory.")
|
||||
.def(
|
||||
"__call__", [](const math::SampleFromVoxels<TreeT, Order, false>& sampler, const CoordT& ijk) { return sampler(ijk); }, nb::is_operator(), "ijk"_a)
|
||||
"__call__", [](const SamplerT& sampler, const CoordT& ijk) { return sampler(ijk); }, nb::is_operator(), "ijk"_a,
|
||||
"Sample the grid at integer voxel coordinate ijk.")
|
||||
.def(
|
||||
"__call__", [](const math::SampleFromVoxels<TreeT, Order, false>& sampler, const Vec3f& xyz) { return sampler(xyz); }, nb::is_operator(), "xyz"_a)
|
||||
"__call__", [](const SamplerT& sampler, const Vec3f& xyz) { return sampler(xyz); }, nb::is_operator(), "xyz"_a,
|
||||
"Sample the grid at fractional index-space position xyz.")
|
||||
.def(
|
||||
"__call__", [](const math::SampleFromVoxels<TreeT, Order, false>& sampler, const Vec3d& xyz) { return sampler(xyz); }, nb::is_operator(), "xyz"_a);
|
||||
"__call__", [](const SamplerT& sampler, const Vec3d& xyz) { return sampler(xyz); }, nb::is_operator(), "xyz"_a,
|
||||
"Sample the grid at fractional index-space position xyz (double).");
|
||||
// gradient() exists on the trilinear sampler only, zeroCrossing() on the
|
||||
// trilinear and triquadratic samplers, and both static_assert a
|
||||
// floating-point ValueT in C++ — mirror that gating here.
|
||||
if constexpr (Order == 1 && util::is_floating_point<ValueT>::value) {
|
||||
cls.def(
|
||||
"gradient", [](const SamplerT& sampler, const Vec3f& xyz) { return sampler.gradient(xyz); }, "xyz"_a,
|
||||
"Return the index-space gradient of the trilinear reconstruction "
|
||||
"at fractional index-space position xyz. Use "
|
||||
"grid.indexToWorldGrad() to move it to world space.")
|
||||
.def(
|
||||
"gradient", [](const SamplerT& sampler, const Vec3d& xyz) { return sampler.gradient(xyz); }, "xyz"_a,
|
||||
"Return the index-space gradient at fractional index-space "
|
||||
"position xyz (double).");
|
||||
}
|
||||
if constexpr ((Order == 1 || Order == 2) && util::is_floating_point<ValueT>::value) {
|
||||
cls.def(
|
||||
"zeroCrossing", [](const SamplerT& sampler, const Vec3f& xyz) { return sampler.zeroCrossing(xyz); }, "xyz"_a,
|
||||
"True iff the reconstruction stencil at fractional index-space "
|
||||
"position xyz straddles the zero iso-surface.")
|
||||
.def(
|
||||
"zeroCrossing", [](const SamplerT& sampler, const Vec3d& xyz) { return sampler.zeroCrossing(xyz); }, "xyz"_a,
|
||||
"True iff the reconstruction stencil at fractional index-space "
|
||||
"position xyz (double) straddles the zero iso-surface.");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TreeT, int Order> void defineCreateSampler(nb::module_& m, const char* name)
|
||||
{
|
||||
m.def(
|
||||
name, [](const Grid<TreeT>& grid) { return math::createSampler<Order, TreeT, false>(grid.tree()); }, "grid"_a);
|
||||
name, [](const Grid<TreeT>& grid) { return math::createSampler<Order, TreeT, false>(grid.tree()); }, "grid"_a,
|
||||
"Build a sampler of the matching order that reads values from the "
|
||||
"given grid's tree.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -65,24 +98,11 @@ template<typename BuildT> void defineTricubicSampler(nb::module_& m, const char*
|
||||
defineCreateSampler<TreeT, 3>(m, "createTricubicSampler");
|
||||
}
|
||||
|
||||
template void defineNearestNeighborSampler<float>(nb::module_&, const char*);
|
||||
template void defineTrilinearSampler<float>(nb::module_&, const char*);
|
||||
template void defineTriquadraticSampler<float>(nb::module_&, const char*);
|
||||
template void defineTricubicSampler<float>(nb::module_&, const char*);
|
||||
|
||||
template void defineNearestNeighborSampler<double>(nb::module_&, const char*);
|
||||
template void defineTrilinearSampler<double>(nb::module_&, const char*);
|
||||
template void defineTriquadraticSampler<double>(nb::module_&, const char*);
|
||||
template void defineTricubicSampler<double>(nb::module_&, const char*);
|
||||
|
||||
template void defineNearestNeighborSampler<int32_t>(nb::module_&, const char*);
|
||||
template void defineTrilinearSampler<int32_t>(nb::module_&, const char*);
|
||||
template void defineTriquadraticSampler<int32_t>(nb::module_&, const char*);
|
||||
template void defineTricubicSampler<int32_t>(nb::module_&, const char*);
|
||||
|
||||
template void defineNearestNeighborSampler<Vec3f>(nb::module_&, const char*);
|
||||
template void defineTrilinearSampler<Vec3f>(nb::module_&, const char*);
|
||||
template void defineTriquadraticSampler<Vec3f>(nb::module_&, const char*);
|
||||
template void defineTricubicSampler<Vec3f>(nb::module_&, const char*);
|
||||
#define NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix) \
|
||||
template void defineNearestNeighborSampler<T>(nb::module_&, const char*); \
|
||||
template void defineTrilinearSampler<T>(nb::module_&, const char*); \
|
||||
template void defineTriquadraticSampler<T>(nb::module_&, const char*); \
|
||||
template void defineTricubicSampler<T>(nb::module_&, const char*);
|
||||
#include "BuildTypes.def"
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "PyNanoToOpenVDB.h"
|
||||
#ifdef NANOVDB_USE_CUDA
|
||||
#include "cuda/PyPointsToGrid.h"
|
||||
#include "cuda/PySampleFromVoxels.h"
|
||||
#include "cuda/PySignedFloodFill.h"
|
||||
#endif
|
||||
|
||||
@@ -31,12 +32,17 @@ void defineToolsModule(nb::module_& m)
|
||||
|
||||
defineStatsMode(m);
|
||||
|
||||
defineGridStatsModule(m);
|
||||
defineGridValidatorModule(m);
|
||||
defineEvalChecksumModule(m);
|
||||
|
||||
defineCreateNanoGridConversions(m);
|
||||
|
||||
definePrimitives<HostBuffer>(m);
|
||||
|
||||
defineCreateNanoGrid<float>(m, "createFloatGrid");
|
||||
defineCreateNanoGrid<double>(m, "createDoubleGrid");
|
||||
defineCreateNanoGrid<int32_t>(m, "createInt32Grid");
|
||||
defineCreateNanoGrid<Vec3f>(m, "createVec3fGrid");
|
||||
#define NANOVDB_PY_FOR_EACH_SAMPLEABLE_BUILDT(T, Suffix) \
|
||||
defineCreateNanoGrid<T>(m, "create" #Suffix "Grid");
|
||||
#include "BuildTypes.def"
|
||||
|
||||
#ifdef NANOVDB_USE_OPENVDB
|
||||
defineOpenToNanoVDB<HostBuffer>(m);
|
||||
@@ -55,6 +61,9 @@ void defineToolsModule(nb::module_& m)
|
||||
defineSignedFloodFill<double>(cudaModule, "signedFloodFill");
|
||||
|
||||
definePointsToGrid<math::Rgba8>(cudaModule, "pointsToRGBA8Grid");
|
||||
|
||||
defineSampleFromVoxels<float>(cudaModule, "sampleFromVoxels");
|
||||
defineSampleFromVoxels<double>(cudaModule, "sampleFromVoxels");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "PyTree.h"
|
||||
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
using namespace nanovdb;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// Polymorphic mgr() that returns the right typed NodeManager based on the
|
||||
// handle's stored gridType. Dispatch follows the same X-macro pattern as
|
||||
// pyHostGrid / pyDeviceGrid; unbound BuildTs return None rather than the
|
||||
// generic getMgr<BuildT>() ptr that would be reinterpreted.
|
||||
template<typename BufferT>
|
||||
static nb::object pyNodeMgr(nb::handle py_self)
|
||||
{
|
||||
using HandleT = NodeManagerHandle<BufferT>;
|
||||
auto& handle = nb::cast<HandleT&>(py_self);
|
||||
if (!handle.data()) return nb::none();
|
||||
// We need to read the stored gridType, but it's private. The public
|
||||
// mgr<BuildT>() returns NULL for type mismatch, so iterate by BuildT.
|
||||
// The X-macro produces one case per bound BuildT; first non-null wins.
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto* m = handle.template mgr<T>()) { \
|
||||
return nb::cast(m, nb::rv_policy::reference, py_self); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
if (auto* m = handle.template mgr<T>()) { \
|
||||
return nb::cast(m, nb::rv_policy::reference, py_self); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto* m = handle.template mgr<T>()) { \
|
||||
return nb::cast(m, nb::rv_policy::reference, py_self); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto* m = handle.template mgr<T>()) { \
|
||||
return nb::cast(m, nb::rv_policy::reference, py_self); \
|
||||
}
|
||||
#include "BuildTypes.def"
|
||||
return nb::none();
|
||||
}
|
||||
|
||||
void defineNodeManagerHandle(nb::module_& m)
|
||||
{
|
||||
using HandleT = NodeManagerHandle<HostBuffer>;
|
||||
nb::class_<HandleT>(m, "NodeManagerHandle",
|
||||
"Owns the memory backing a NodeManager. Move-only. "
|
||||
"Obtain via nanovdb.createNodeManager(grid).")
|
||||
.def("size",
|
||||
[](const HandleT& h) { return h.size(); },
|
||||
"Byte size of the buffer backing this NodeManagerHandle.")
|
||||
.def(
|
||||
"__bool__",
|
||||
[](const HandleT& h) { return h.data() != nullptr; },
|
||||
nb::is_operator(),
|
||||
"True iff this handle owns a non-empty buffer.")
|
||||
.def("mgr", &pyNodeMgr<HostBuffer>,
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return the typed NodeManager for the grid this handle was "
|
||||
"built from, or None if the BuildT is not Python-visible. The "
|
||||
"returned NodeManager keeps this handle alive.");
|
||||
}
|
||||
|
||||
// createNodeManager has one template instantiation per BuildT. We expose a
|
||||
// single polymorphic `createNodeManager(grid)` that picks the right one
|
||||
// based on the runtime type of `grid` (any nb::class_-bound NanoGrid<T>).
|
||||
// nb::isinstance is a fast type check that avoids the exception-on-mismatch
|
||||
// overhead that would come from trying nb::cast and catching cast_error for
|
||||
// every non-matching BuildT.
|
||||
template<typename BuildT>
|
||||
static nb::object tryCreateNodeManager(nb::handle py_grid)
|
||||
{
|
||||
using GridT = NanoGrid<BuildT>;
|
||||
if (!nb::isinstance<GridT>(py_grid)) {
|
||||
return nb::object(); // sentinel: "not this BuildT, try next"
|
||||
}
|
||||
auto& grid = nb::cast<GridT&>(py_grid);
|
||||
return nb::cast(createNodeManager<BuildT, HostBuffer>(grid));
|
||||
}
|
||||
|
||||
void defineCreateNodeManager(nb::module_& m)
|
||||
{
|
||||
m.def("createNodeManager",
|
||||
[](nb::handle py_grid) -> nb::object {
|
||||
// Try every bound BuildT; first successful cast wins.
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto obj = tryCreateNodeManager<T>(py_grid); obj.is_valid()) { \
|
||||
return obj; \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
if (auto obj = tryCreateNodeManager<T>(py_grid); obj.is_valid()) { \
|
||||
return obj; \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto obj = tryCreateNodeManager<T>(py_grid); obj.is_valid()) { \
|
||||
return obj; \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
if (auto obj = tryCreateNodeManager<T>(py_grid); obj.is_valid()) { \
|
||||
return obj; \
|
||||
}
|
||||
#include "BuildTypes.def"
|
||||
throw nb::type_error(
|
||||
"createNodeManager: argument is not a NanoVDB grid of any "
|
||||
"bound BuildT");
|
||||
},
|
||||
"grid"_a,
|
||||
// The constructed NodeManager stores a raw pointer back to the
|
||||
// grid; the handle must therefore keep the grid (and transitively
|
||||
// the GridHandle that owns the grid's buffer) alive.
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Build a NodeManager for the given grid, returning a "
|
||||
"NodeManagerHandle that owns the underlying buffer. The handle's "
|
||||
"mgr() method returns the typed NodeManager. The handle keeps the "
|
||||
"source grid alive for as long as it lives.");
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
@@ -0,0 +1,486 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#ifndef NANOVDB_PYTREE_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_PYTREE_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
#include <nanobind/ndarray.h>
|
||||
#include <nanobind/stl/tuple.h>
|
||||
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include <nanovdb/NodeManager.h>
|
||||
#include <nanovdb/HostBuffer.h>
|
||||
|
||||
#include <type_traits> // std::is_arithmetic_v, std::enable_if
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// -------------------- NanoLeaf<BuildT> --------------------
|
||||
//
|
||||
// Binds the 8^3 leaf node. Methods that don't depend on whether the leaf
|
||||
// stores a contiguous T[512] array are bound unconditionally; the
|
||||
// zero-copy 512-element NumPy values() view is only bound when the leaf
|
||||
// layout actually carries T mValues[512] (the BuildTraits<T>::is_special
|
||||
// types use packed / index / mask layouts where a 512-element T view is
|
||||
// either impossible or misleading).
|
||||
template<typename BuildT> void defineNanoLeaf(nb::module_& m, const char* name)
|
||||
{
|
||||
using LeafT = nanovdb::NanoLeaf<BuildT>;
|
||||
using ValueT = typename LeafT::ValueType;
|
||||
using CoordT = typename LeafT::CoordType;
|
||||
|
||||
auto cls = nb::class_<LeafT>(m, name,
|
||||
"Leaf node — 8x8x8 voxels. Inherits stats and bbox from the same "
|
||||
"leaf-data block bound across BuildTs.");
|
||||
|
||||
cls.def("origin", &LeafT::origin,
|
||||
"Index-space origin (minimum corner) of this leaf node.")
|
||||
.def("bbox", &LeafT::bbox,
|
||||
"Index-space bounding box of this leaf's active voxels.")
|
||||
.def("hasBBox", &LeafT::hasBBox,
|
||||
"True iff this leaf carries a cached active bounding box.")
|
||||
.def_static("dim", &LeafT::dim,
|
||||
"Side length of a leaf node in voxels (always 8).")
|
||||
.def_static("voxelCount", &LeafT::voxelCount,
|
||||
"Total voxel count in a leaf (always 512).")
|
||||
.def("memUsage", &LeafT::memUsage,
|
||||
"Byte size of this leaf node.")
|
||||
.def("isActive",
|
||||
nb::overload_cast<const CoordT&>(&LeafT::isActive, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"True iff the voxel at index-space ijk is active.")
|
||||
.def("isActive",
|
||||
[](const LeafT& leaf, uint32_t n) {
|
||||
// Underlying mValueMask.isOn(n) is unchecked; release builds
|
||||
// skip the C++ NANOVDB_ASSERT and would silently read OOB.
|
||||
if (n >= LeafT::voxelCount()) {
|
||||
throw nb::index_error(
|
||||
"Leaf.isActive(n): n out of range [0, voxelCount)");
|
||||
}
|
||||
return leaf.isActive(n);
|
||||
},
|
||||
nb::arg("n"),
|
||||
"True iff the n-th voxel (linear index into the 512-element leaf) is active.")
|
||||
.def("getValue",
|
||||
[](const LeafT& leaf, uint32_t offset) {
|
||||
// mValues[offset] is unchecked in C++; guard the Python side.
|
||||
if (offset >= LeafT::voxelCount()) {
|
||||
throw nb::index_error(
|
||||
"Leaf.getValue(offset): offset out of range [0, voxelCount)");
|
||||
}
|
||||
return leaf.getValue(offset);
|
||||
},
|
||||
nb::arg("offset"),
|
||||
"Return the value at linear offset (0..511) within this leaf.")
|
||||
.def("getValue",
|
||||
nb::overload_cast<const CoordT&>(&LeafT::getValue, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"Return the value at index-space ijk; ijk must lie inside this leaf.")
|
||||
.def("getFirstValue", &LeafT::getFirstValue,
|
||||
"Value at the leaf's first voxel (linear offset 0).")
|
||||
.def("getLastValue", &LeafT::getLastValue,
|
||||
"Value at the leaf's last voxel (linear offset 511).")
|
||||
.def("minimum", &LeafT::minimum,
|
||||
"Minimum active value within this leaf.")
|
||||
.def("maximum", &LeafT::maximum,
|
||||
"Maximum active value within this leaf.")
|
||||
.def("average", &LeafT::average,
|
||||
"Average of active values within this leaf.")
|
||||
.def("stdDeviation", &LeafT::stdDeviation,
|
||||
"Standard deviation of active values within this leaf.")
|
||||
// NOTE: variance() omitted — NanoVDB.h line 4388 uses unqualified
|
||||
// Pow2() which fails ADL for non-float ValueTs (ValueIndex /
|
||||
// ValueMask / etc.). Users can compute it as stdDeviation() ** 2.
|
||||
.def("flags", &LeafT::flags,
|
||||
"Raw leaf flag bits.")
|
||||
.def("valueMask", &LeafT::valueMask, nb::rv_policy::reference_internal,
|
||||
"Reference to the leaf's 512-bit active-value mask.")
|
||||
.def("probeValue",
|
||||
[](const LeafT& leaf, const CoordT& ijk) {
|
||||
ValueT v;
|
||||
bool on = leaf.probeValue(ijk, v);
|
||||
return std::make_tuple(v, on);
|
||||
},
|
||||
nb::arg("ijk"),
|
||||
"Return (value, isActive) for the voxel at index-space ijk.");
|
||||
|
||||
// Zero-copy 512-element NumPy view of mValues. Only enabled for
|
||||
// BuildTs whose ValueType is a primitive arithmetic type (float,
|
||||
// double, int*). For Fp* / Index / Mask / bool / Point the leaf uses
|
||||
// packed / mask / void layouts. For Vec3f / Vec3d / Vec4f / Vec4d /
|
||||
// Vec3u8 / Vec3u16 / Rgba8 the leaf carries a struct array that
|
||||
// nanobind's ndarray can't represent directly — those would need a
|
||||
// flattened (count, dim) component-typed view which a follow-up can
|
||||
// add. Users can still walk every leaf via the bound getValue().
|
||||
if constexpr (std::is_arithmetic_v<ValueT>
|
||||
&& !nanovdb::BuildTraits<BuildT>::is_special) {
|
||||
cls.def("values",
|
||||
[](nb::handle py_self) {
|
||||
auto& leaf = nb::cast<LeafT&>(py_self);
|
||||
size_t shape[1] = {LeafT::voxelCount()};
|
||||
return nb::cast(
|
||||
nb::ndarray<nb::numpy, ValueT, nb::ndim<1>, nb::c_contig, nb::device::cpu>(
|
||||
static_cast<void*>(leaf.data()->mValues),
|
||||
size_t(1), shape, py_self),
|
||||
nb::rv_policy::reference);
|
||||
},
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a zero-copy NumPy view of the 512 leaf values. The view "
|
||||
"keeps the leaf (and transitively the GridHandle that owns the "
|
||||
"underlying buffer) alive.");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- NanoUpper / NanoLower<BuildT> --------------------
|
||||
//
|
||||
// Both internal node levels share the same C++ API surface (just different
|
||||
// LOG2DIM). One helper templated on the concrete InternalNode type.
|
||||
template<typename InternalT>
|
||||
void defineInternalNodeBase(nb::class_<InternalT>& cls)
|
||||
{
|
||||
using ValueT = typename InternalT::ValueType;
|
||||
using CoordT = typename InternalT::CoordType;
|
||||
cls.def("origin", &InternalT::origin,
|
||||
"Index-space origin (minimum corner) of this internal node.")
|
||||
.def("bbox", &InternalT::bbox,
|
||||
"Index-space bounding box of this internal node's active voxels.")
|
||||
.def_static("dim", &InternalT::dim,
|
||||
"Side length in voxels covered by this internal node.")
|
||||
.def_static("memUsage", []() { return InternalT::memUsage(); },
|
||||
"Byte size of an internal node.")
|
||||
.def("minimum", &InternalT::minimum,
|
||||
"Minimum active value within this node's subtree.")
|
||||
.def("maximum", &InternalT::maximum,
|
||||
"Maximum active value within this node's subtree.")
|
||||
.def("average", &InternalT::average,
|
||||
"Average of active values within this node's subtree.")
|
||||
.def("stdDeviation", &InternalT::stdDeviation,
|
||||
"Standard deviation of active values within this node's subtree.")
|
||||
// variance() omitted for parity with the leaf binding; compute as
|
||||
// stdDeviation() ** 2 in Python.
|
||||
.def("valueMask", &InternalT::valueMask, nb::rv_policy::reference_internal,
|
||||
"Reference to the node's active-tile mask.")
|
||||
.def("childMask", &InternalT::childMask, nb::rv_policy::reference_internal,
|
||||
"Reference to the node's child-pointer mask (1 where a child node exists).")
|
||||
.def("getValue",
|
||||
nb::overload_cast<const CoordT&>(&InternalT::getValue, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"Return the value at index-space ijk by descending into the subtree.")
|
||||
.def("getFirstValue", &InternalT::getFirstValue,
|
||||
"Value at the first (lowest-indexed) tile in this node.")
|
||||
.def("getLastValue", &InternalT::getLastValue,
|
||||
"Value at the last (highest-indexed) tile in this node.")
|
||||
.def("isActive",
|
||||
nb::overload_cast<const CoordT&>(&InternalT::isActive, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"True iff the voxel at index-space ijk is active.")
|
||||
.def("probeValue",
|
||||
[](const InternalT& node, const CoordT& ijk) {
|
||||
ValueT v;
|
||||
bool on = node.probeValue(ijk, v);
|
||||
return std::make_tuple(v, on);
|
||||
},
|
||||
nb::arg("ijk"),
|
||||
"Return (value, isActive) for the voxel at index-space ijk.");
|
||||
}
|
||||
|
||||
template<typename BuildT> void defineNanoUpper(nb::module_& m, const char* name)
|
||||
{
|
||||
using UpperT = nanovdb::NanoUpper<BuildT>;
|
||||
nb::class_<UpperT> cls(m, name,
|
||||
"Upper internal node — 32x32x32 (covers a 4096^3 region in index space).");
|
||||
defineInternalNodeBase(cls);
|
||||
}
|
||||
|
||||
template<typename BuildT> void defineNanoLower(nb::module_& m, const char* name)
|
||||
{
|
||||
using LowerT = nanovdb::NanoLower<BuildT>;
|
||||
nb::class_<LowerT> cls(m, name,
|
||||
"Lower internal node — 16x16x16 (covers a 128^3 region in index space).");
|
||||
defineInternalNodeBase(cls);
|
||||
}
|
||||
|
||||
// -------------------- NanoRoot<BuildT> --------------------
|
||||
template<typename BuildT> void defineNanoRoot(nb::module_& m, const char* name)
|
||||
{
|
||||
using RootT = nanovdb::NanoRoot<BuildT>;
|
||||
using ValueT = typename RootT::ValueType;
|
||||
using CoordT = typename RootT::CoordType;
|
||||
|
||||
nb::class_<RootT>(m, name, "Root node — top of the tree, holds the tile table.")
|
||||
.def("background", &RootT::background, nb::rv_policy::reference_internal,
|
||||
"Background value returned for inactive voxels.")
|
||||
.def("tileCount", &RootT::tileCount,
|
||||
"Number of active tiles in the root tile table.")
|
||||
.def("getTableSize", &RootT::getTableSize,
|
||||
"Total number of entries in the root tile table.")
|
||||
.def("isEmpty", &RootT::isEmpty,
|
||||
"True iff the root has no active tiles.")
|
||||
.def("bbox", &RootT::bbox, nb::rv_policy::reference_internal,
|
||||
"Index-space bounding box of every active value in this tree.")
|
||||
.def("minimum", &RootT::minimum, nb::rv_policy::reference_internal,
|
||||
"Minimum active value in the tree.")
|
||||
.def("maximum", &RootT::maximum, nb::rv_policy::reference_internal,
|
||||
"Maximum active value in the tree.")
|
||||
.def("average", &RootT::average, nb::rv_policy::reference_internal,
|
||||
"Average of active values in the tree.")
|
||||
.def("stdDeviation", &RootT::stdDeviation, nb::rv_policy::reference_internal,
|
||||
"Standard deviation of active values in the tree.")
|
||||
.def("memUsage",
|
||||
nb::overload_cast<>(&RootT::memUsage, nb::const_),
|
||||
"Byte size of the root node and its tile table.")
|
||||
.def("getValue",
|
||||
nb::overload_cast<const CoordT&>(&RootT::getValue, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"Return the value at index-space ijk by walking from the root.")
|
||||
.def("isActive",
|
||||
nb::overload_cast<const CoordT&>(&RootT::isActive, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"True iff the voxel at index-space ijk is active.")
|
||||
.def("probeValue",
|
||||
[](const RootT& root, const CoordT& ijk) {
|
||||
ValueT v;
|
||||
bool on = root.probeValue(ijk, v);
|
||||
return std::make_tuple(v, on);
|
||||
},
|
||||
nb::arg("ijk"),
|
||||
"Return (value, isActive) for the voxel at index-space ijk.");
|
||||
}
|
||||
|
||||
// -------------------- NanoTree<BuildT> --------------------
|
||||
template<typename BuildT> void defineNanoTree(nb::module_& m, const char* name)
|
||||
{
|
||||
using TreeT = nanovdb::NanoTree<BuildT>;
|
||||
using ValueT = typename TreeT::ValueType;
|
||||
using CoordT = typename TreeT::CoordType;
|
||||
using RootT = typename TreeT::RootType;
|
||||
using UpperT = typename TreeT::UpperNodeType;
|
||||
using LowerT = typename TreeT::LowerNodeType;
|
||||
using LeafT = typename TreeT::LeafNodeType;
|
||||
|
||||
nb::class_<TreeT>(m, name,
|
||||
"Tree — owns the root and provides bulk metadata queries "
|
||||
"(node counts, active voxel count, extrema).")
|
||||
.def("root",
|
||||
nb::overload_cast<>(&TreeT::root, nb::const_),
|
||||
nb::rv_policy::reference_internal,
|
||||
"Root node of this tree. Lifetime is anchored to the tree.")
|
||||
.def("background", &TreeT::background, nb::rv_policy::reference_internal,
|
||||
"Background value returned for inactive voxels.")
|
||||
.def("activeVoxelCount", &TreeT::activeVoxelCount,
|
||||
"Total number of active voxels in this tree.")
|
||||
// activeTileCount(level): valid range is 1..3 (lower / upper / root
|
||||
// tile counts). C++ uses NANOVDB_ASSERT(level > 0 && level <= 3)
|
||||
// which is a no-op in release builds — so guard explicitly.
|
||||
.def("activeTileCount",
|
||||
[](const TreeT& tree, uint32_t level) -> uint32_t {
|
||||
if (level < 1 || level > 3) {
|
||||
throw nb::value_error(
|
||||
"Tree.activeTileCount(level): level must be 1, 2, or 3");
|
||||
}
|
||||
return tree.activeTileCount(level);
|
||||
},
|
||||
nb::arg("level"),
|
||||
"Number of active tiles at the given tree level (1=lower, 2=upper, 3=root).")
|
||||
// nodeCount(level): valid range is 0..2 (leaf / lower / upper).
|
||||
// C++ uses NANOVDB_ASSERT(level < 3), again no-op in release.
|
||||
// The lambda's `int level` argument disambiguates the call against
|
||||
// Tree's templated nodeCount<NodeT>() overload at the C++ level,
|
||||
// so we don't need an overload_cast / static_cast wrapper here.
|
||||
.def("nodeCount",
|
||||
[](const TreeT& tree, int level) -> uint32_t {
|
||||
if (level < 0 || level >= 3) {
|
||||
throw nb::value_error(
|
||||
"Tree.nodeCount(level): level must be 0, 1, or 2");
|
||||
}
|
||||
return tree.nodeCount(level);
|
||||
},
|
||||
nb::arg("level"),
|
||||
"Number of nodes at the given tree level (0=leaf, 1=lower, 2=upper).")
|
||||
.def("totalNodeCount", &TreeT::totalNodeCount,
|
||||
"Sum of node counts across every tree level.")
|
||||
.def_static("memUsage", &TreeT::memUsage,
|
||||
"Byte size of a single tree structure (header only).")
|
||||
.def("getValue",
|
||||
nb::overload_cast<const CoordT&>(&TreeT::getValue, nb::const_),
|
||||
nb::arg("ijk"),
|
||||
"Return the value at index-space ijk by descending from the root.")
|
||||
.def("isActive", &TreeT::isActive, nb::arg("ijk"),
|
||||
"True iff the voxel at index-space ijk is active.")
|
||||
.def("probeValue",
|
||||
[](const TreeT& tree, const CoordT& ijk) {
|
||||
ValueT v;
|
||||
bool on = tree.probeValue(ijk, v);
|
||||
return std::make_tuple(v, on);
|
||||
},
|
||||
nb::arg("ijk"),
|
||||
"Return (value, isActive) for the voxel at index-space ijk.")
|
||||
.def("extrema",
|
||||
[](const TreeT& tree) {
|
||||
ValueT mn, mx;
|
||||
tree.extrema(mn, mx);
|
||||
return std::make_tuple(mn, mx);
|
||||
},
|
||||
"Return (min, max) of the active values over the whole tree.")
|
||||
.def("getFirstLeaf",
|
||||
nb::overload_cast<>(&TreeT::getFirstLeaf, nb::const_),
|
||||
nb::rv_policy::reference_internal,
|
||||
"First leaf node in breadth-first order, or None if the tree is empty.")
|
||||
.def("getFirstLower",
|
||||
nb::overload_cast<>(&TreeT::getFirstLower, nb::const_),
|
||||
nb::rv_policy::reference_internal,
|
||||
"First lower internal node in breadth-first order, or None if none exists.")
|
||||
.def("getFirstUpper",
|
||||
nb::overload_cast<>(&TreeT::getFirstUpper, nb::const_),
|
||||
nb::rv_policy::reference_internal,
|
||||
"First upper internal node in breadth-first order, or None if none exists.");
|
||||
}
|
||||
|
||||
// -------------------- NodeManager<BuildT> --------------------
|
||||
//
|
||||
// NodeManager is heap-managed by a NodeManagerHandle (move-only, owns the
|
||||
// underlying memory). We bind one NodeManager class per BuildT and one
|
||||
// host-side NodeManagerHandle class. Users get a handle from
|
||||
// nanovdb.createNodeManager(grid); they then call handle.mgr() to obtain a
|
||||
// borrowed pointer to the typed NodeManager — its lifetime is anchored to
|
||||
// the handle via reference_internal.
|
||||
template<typename BuildT> void defineNodeManager(nb::module_& m, const char* name)
|
||||
{
|
||||
using NMT = nanovdb::NodeManager<BuildT>;
|
||||
nb::class_<NMT>(m, name,
|
||||
"Sequential breadth-first accessor for the leaf / lower / upper "
|
||||
"internal nodes of a NanoGrid. Construct via "
|
||||
"nanovdb.createNodeManager(grid).")
|
||||
.def("isLinear",
|
||||
nb::overload_cast<>(&NMT::isLinear, nb::const_),
|
||||
"True iff this NodeManager is laid out as a linear offset table over a breadth-first grid.")
|
||||
.def("memUsage",
|
||||
nb::overload_cast<>(&NMT::memUsage, nb::const_),
|
||||
"Byte size of this NodeManager.")
|
||||
.def("nodeCount",
|
||||
[](const NMT& nm, int level) -> uint64_t {
|
||||
// Mirror Tree.nodeCount bounds (NodeManager forwards to Tree).
|
||||
if (level < 0 || level >= 3) {
|
||||
throw nb::value_error(
|
||||
"NodeManager.nodeCount(level): level must be 0, 1, or 2");
|
||||
}
|
||||
return nm.nodeCount(level);
|
||||
},
|
||||
nb::arg("level"),
|
||||
"Number of nodes at the given tree level (0=leaf, 1=lower, 2=upper).")
|
||||
.def("leafCount", &NMT::leafCount,
|
||||
"Number of leaf nodes managed by this NodeManager.")
|
||||
.def("lowerCount", &NMT::lowerCount,
|
||||
"Number of lower internal nodes managed by this NodeManager.")
|
||||
.def("upperCount", &NMT::upperCount,
|
||||
"Number of upper internal nodes managed by this NodeManager.")
|
||||
// leaf / lower / upper: NANOVDB_ASSERT(i < nodeCount(LEVEL)) in C++ is
|
||||
// no-op in release, so guard explicitly to convert OOB access into a
|
||||
// Python IndexError instead of memory corruption.
|
||||
.def("leaf",
|
||||
[](const NMT& nm, uint32_t i) -> const nanovdb::NanoLeaf<BuildT>& {
|
||||
if (i >= nm.leafCount()) {
|
||||
throw nb::index_error(
|
||||
"NodeManager.leaf(i): i out of range [0, leafCount)");
|
||||
}
|
||||
return nm.leaf(i);
|
||||
},
|
||||
nb::rv_policy::reference_internal, nb::arg("i"),
|
||||
"Return the i-th leaf node in breadth-first order.")
|
||||
.def("lower",
|
||||
[](const NMT& nm, uint32_t i) -> const nanovdb::NanoLower<BuildT>& {
|
||||
if (i >= nm.lowerCount()) {
|
||||
throw nb::index_error(
|
||||
"NodeManager.lower(i): i out of range [0, lowerCount)");
|
||||
}
|
||||
return nm.lower(i);
|
||||
},
|
||||
nb::rv_policy::reference_internal, nb::arg("i"),
|
||||
"Return the i-th lower internal node in breadth-first order.")
|
||||
.def("upper",
|
||||
[](const NMT& nm, uint32_t i) -> const nanovdb::NanoUpper<BuildT>& {
|
||||
if (i >= nm.upperCount()) {
|
||||
throw nb::index_error(
|
||||
"NodeManager.upper(i): i out of range [0, upperCount)");
|
||||
}
|
||||
return nm.upper(i);
|
||||
},
|
||||
nb::rv_policy::reference_internal, nb::arg("i"),
|
||||
"Return the i-th upper internal node in breadth-first order.");
|
||||
}
|
||||
|
||||
void defineNodeManagerHandle(nb::module_& m);
|
||||
void defineCreateNodeManager(nb::module_& m);
|
||||
|
||||
// -------------------- grid.leaf_values() bulk extractor --------------------
|
||||
//
|
||||
// For non-special BuildTs with breadth-first, fixed-size leaves, the leaf
|
||||
// values can be reached as a contiguous (N_leaves, 512) array — every leaf
|
||||
// occupies sizeof(NanoLeaf<T>) bytes and mValues starts at a known offset
|
||||
// inside each leaf. We bind this on NanoGrid<T> as leaf_values() for
|
||||
// efficient bulk analytics from Python.
|
||||
template<typename BuildT, typename = void>
|
||||
struct PyLeafValuesBinder
|
||||
{
|
||||
template<typename ClsT> static void apply(ClsT&) {}
|
||||
};
|
||||
|
||||
template<typename BuildT>
|
||||
struct PyLeafValuesBinder<BuildT,
|
||||
typename std::enable_if<
|
||||
std::is_arithmetic_v<typename nanovdb::NanoLeaf<BuildT>::ValueType>
|
||||
&& !nanovdb::BuildTraits<BuildT>::is_special>::type>
|
||||
{
|
||||
template<typename ClsT>
|
||||
static void apply(ClsT& cls)
|
||||
{
|
||||
using GridT = nanovdb::NanoGrid<BuildT>;
|
||||
using LeafT = nanovdb::NanoLeaf<BuildT>;
|
||||
using ValueT = typename LeafT::ValueType;
|
||||
cls.def("leaf_values",
|
||||
[](nb::handle py_self) -> nb::object {
|
||||
auto& grid = nb::cast<GridT&>(py_self);
|
||||
const auto& tree = grid.tree();
|
||||
const uint32_t nLeaves = tree.template nodeCount<LeafT>();
|
||||
if (!grid.isBreadthFirst()) {
|
||||
throw nb::value_error(
|
||||
"leaf_values() requires a breadth-first grid "
|
||||
"layout; rebuild via "
|
||||
"nanovdb.tools.createNanoGrid(...).");
|
||||
}
|
||||
// For an empty grid (no leaves) we still return an ndarray
|
||||
// — shape (0, 512) — so callers can iterate / np.asarray()
|
||||
// / shape-test without branching on a None sentinel.
|
||||
LeafT* first = const_cast<LeafT*>(tree.getFirstLeaf());
|
||||
size_t shape[2] = {nLeaves, LeafT::voxelCount()};
|
||||
int64_t strides[2] = {
|
||||
static_cast<int64_t>(sizeof(LeafT) / sizeof(ValueT)),
|
||||
1
|
||||
};
|
||||
// first is non-null whenever nLeaves > 0; when nLeaves == 0
|
||||
// we pass a dummy non-null aligned pointer (the grid itself)
|
||||
// so nanobind has something to base the empty array on.
|
||||
// Nothing will be read since the leading shape is 0.
|
||||
void* data = (first != nullptr)
|
||||
? static_cast<void*>(first->data()->mValues)
|
||||
: static_cast<void*>(&grid);
|
||||
return nb::cast(
|
||||
nb::ndarray<nb::numpy, ValueT, nb::ndim<2>, nb::device::cpu>(
|
||||
data, size_t(2), shape, py_self, strides),
|
||||
nb::rv_policy::reference);
|
||||
},
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a zero-copy (N_leaves, 512) NumPy view of every leaf's "
|
||||
"values, in breadth-first leaf order. Available only for "
|
||||
"BuildTs whose leaf layout carries T mValues[512] (i.e. not "
|
||||
"Fp*, Index, Mask, bool, or Point) and only on breadth-first "
|
||||
"grids. Returns an empty (0, 512) array for grids with no "
|
||||
"leaves. The view keeps the grid alive.");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,547 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "PyVoxelBlockManager.h"
|
||||
|
||||
#include <nanobind/ndarray.h>
|
||||
#include <nanobind/stl/tuple.h>
|
||||
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
#include <nanovdb/HostBuffer.h>
|
||||
#include <nanovdb/tools/CreateNanoGrid.h>
|
||||
#include <nanovdb/tools/VoxelBlockManager.h>
|
||||
#include <nanovdb/util/Util.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
using namespace nanovdb;
|
||||
using nanovdb::tools::VoxelBlockManager;
|
||||
using nanovdb::tools::VoxelBlockManagerBase;
|
||||
using nanovdb::tools::VoxelBlockManagerHandle;
|
||||
using nanovdb::tools::buildVoxelBlockManager;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// ----------------------- Log2BlockWidth dispatch --------------------------
|
||||
//
|
||||
// Log2BlockWidth is a compile-time template parameter on every VBM helper.
|
||||
// We expose it to Python as a runtime int and dispatch via a switch that
|
||||
// instantiates the four useful widths (BlockWidth = 64, 128, 256, 512).
|
||||
// Larger widths are not bound by default; callers who need them can add a
|
||||
// new case below.
|
||||
|
||||
template<typename F>
|
||||
static auto dispatchLog2BlockWidth(int log2BlockWidth, F&& fn)
|
||||
{
|
||||
switch (log2BlockWidth) {
|
||||
case 6: return fn(std::integral_constant<int, 6>{});
|
||||
case 7: return fn(std::integral_constant<int, 7>{});
|
||||
case 8: return fn(std::integral_constant<int, 8>{});
|
||||
case 9: return fn(std::integral_constant<int, 9>{});
|
||||
default:
|
||||
throw nb::value_error(
|
||||
"VoxelBlockManager: log2_block_width must be 6, 7, 8, or 9 "
|
||||
"(BlockWidth = 64, 128, 256, or 512). Larger widths are not "
|
||||
"bound in Python by default.");
|
||||
}
|
||||
}
|
||||
|
||||
// PyVBMHandle wraps the C++ VoxelBlockManagerHandle and carries the
|
||||
// log2_block_width the handle was built with. The C++ handle does NOT store
|
||||
// log2_block_width itself, so without this wrapper the Python binding would
|
||||
// have to ask the caller every time — which the user can lie about and
|
||||
// trigger out-of-bounds reads of the metadata buffers. Storing it once at
|
||||
// build time and consulting it in every accessor closes that hole.
|
||||
struct PyVBMHandle
|
||||
{
|
||||
VoxelBlockManagerHandle<HostBuffer> handle;
|
||||
int log2BlockWidth = 6;
|
||||
|
||||
PyVBMHandle() = default;
|
||||
PyVBMHandle(VoxelBlockManagerHandle<HostBuffer>&& h, int lbw) noexcept
|
||||
: handle(std::move(h)), log2BlockWidth(lbw) {}
|
||||
|
||||
PyVBMHandle(const PyVBMHandle&) = delete;
|
||||
PyVBMHandle& operator=(const PyVBMHandle&) = delete;
|
||||
PyVBMHandle(PyVBMHandle&&) = default;
|
||||
PyVBMHandle& operator=(PyVBMHandle&&) = default;
|
||||
|
||||
uint64_t blockCount() const { return handle.blockCount(); }
|
||||
uint64_t firstOffset() const { return handle.firstOffset(); }
|
||||
uint64_t lastOffset() const { return handle.lastOffset(); }
|
||||
void reset() { handle.reset(); }
|
||||
int blockWidth() const { return 1 << log2BlockWidth; }
|
||||
int jumpMapLength() const { return 1 << (log2BlockWidth - 6); }
|
||||
};
|
||||
|
||||
// ----------------------- decodeInverseMaps helper -------------------------
|
||||
//
|
||||
// Common implementation used by both the free function and the
|
||||
// handle.decodeBlock(i) method. Allocates fresh leafIndex (uint32) and
|
||||
// voxelOffset (uint16) NumPy arrays of length BlockWidth and fills them.
|
||||
template<int Log2BlockWidth>
|
||||
static nb::object pyDecodeInverseMapsImpl(const NanoGrid<ValueOnIndex>& grid,
|
||||
uint32_t firstLeafID,
|
||||
const uint64_t* jumpMap,
|
||||
uint64_t blockFirstOffset)
|
||||
{
|
||||
constexpr int BlockWidth = 1 << Log2BlockWidth;
|
||||
constexpr int JumpMapLength =
|
||||
VoxelBlockManagerBase<Log2BlockWidth>::JumpMapLength;
|
||||
|
||||
// The C++ decodeInverseMaps iterates leafID = firstLeafID ..
|
||||
// firstLeafID + nExtraLeaves, where nExtraLeaves is the popcount of
|
||||
// this block's jumpMap (each set bit marks an additional leaf
|
||||
// boundary crossed within the block). If the jumpMap is corrupt or
|
||||
// was built against a different grid, the loop could read past
|
||||
// tree.getFirstNode<0>(). Pre-compute the upper bound and validate
|
||||
// it against grid.tree().nodeCount(0) before any allocation.
|
||||
uint32_t nExtraLeaves = 0;
|
||||
for (int i = 0; i < JumpMapLength; ++i)
|
||||
nExtraLeaves += util::countOn(jumpMap[i]);
|
||||
const uint32_t nLeaves = grid.tree().nodeCount(0);
|
||||
if (uint64_t(firstLeafID) + uint64_t(nExtraLeaves) >= nLeaves) {
|
||||
throw nb::value_error(
|
||||
"decodeInverseMaps: firstLeafID + popcount(jumpMap) would "
|
||||
"index past grid.tree().nodeCount(0) — the jumpMap is "
|
||||
"either corrupt or was paired with a different grid.");
|
||||
}
|
||||
|
||||
// Each call allocates fresh BlockWidth-sized output arrays for the
|
||||
// leaf-index and voxel-offset results. We use plain new[] (rather than
|
||||
// a numpy-allocated buffer) because the produced ndarrays are returned
|
||||
// by reference and Python owns them via the capsule deleters below —
|
||||
// when the ndarray is destroyed, the capsule's deleter runs delete[].
|
||||
//
|
||||
// The raw pointers live in std::unique_ptr until the matching capsule
|
||||
// has been constructed; that way if the second allocation, the
|
||||
// decodeInverseMaps call, or either capsule construction throws, the
|
||||
// unique_ptr unwinds the half-built state cleanly. After a capsule
|
||||
// takes ownership we release() so the unique_ptr no longer double-frees.
|
||||
std::unique_ptr<uint32_t[]> leafIndex(new uint32_t[BlockWidth]);
|
||||
std::unique_ptr<uint16_t[]> voxelOffset(new uint16_t[BlockWidth]);
|
||||
|
||||
using VBM = VoxelBlockManager<Log2BlockWidth>;
|
||||
{
|
||||
// Release the GIL around the pure-C++ decode kernel — the heavy part,
|
||||
// and the only part of this helper that touches no Python objects. The
|
||||
// GIL is re-acquired on scope exit (including during exception unwind)
|
||||
// before the capsules / ndarrays below are constructed.
|
||||
nb::gil_scoped_release release;
|
||||
VBM::template decodeInverseMaps<ValueOnIndex>(
|
||||
&grid, firstLeafID, jumpMap, blockFirstOffset,
|
||||
leafIndex.get(), voxelOffset.get());
|
||||
}
|
||||
|
||||
// nb::capsule wraps the raw pointer + matching delete[] so it can serve
|
||||
// as the ndarray's owner — the capsule lives as long as the ndarray and
|
||||
// its destruction runs the deleter.
|
||||
nb::capsule leafOwner(leafIndex.get(),
|
||||
[](void* p) noexcept { delete[] static_cast<uint32_t*>(p); });
|
||||
auto* leafRaw = leafIndex.release();
|
||||
nb::capsule offsetOwner(voxelOffset.get(),
|
||||
[](void* p) noexcept { delete[] static_cast<uint16_t*>(p); });
|
||||
auto* offsetRaw = voxelOffset.release();
|
||||
|
||||
size_t shape[1] = {static_cast<size_t>(BlockWidth)};
|
||||
nb::ndarray<nb::numpy, uint32_t, nb::ndim<1>, nb::c_contig, nb::device::cpu>
|
||||
leafArr(leafRaw, size_t(1), shape, leafOwner);
|
||||
nb::ndarray<nb::numpy, uint16_t, nb::ndim<1>, nb::c_contig, nb::device::cpu>
|
||||
offsetArr(offsetRaw, size_t(1), shape, offsetOwner);
|
||||
return nb::make_tuple(
|
||||
nb::cast(leafArr, nb::rv_policy::reference),
|
||||
nb::cast(offsetArr, nb::rv_policy::reference));
|
||||
}
|
||||
|
||||
// ------------------- VoxelBlockManagerHandle binding ----------------------
|
||||
|
||||
static const NanoGrid<ValueOnIndex>* castOnIndexGrid(nb::handle py_grid,
|
||||
const char* fn_name)
|
||||
{
|
||||
if (!nb::isinstance<NanoGrid<ValueOnIndex>>(py_grid)) {
|
||||
std::string msg(fn_name);
|
||||
msg += ": grid must be a NanoVDB grid of build type ValueOnIndex (OnIndexGrid)";
|
||||
throw nb::type_error(msg.c_str());
|
||||
}
|
||||
return &nb::cast<const NanoGrid<ValueOnIndex>&>(py_grid);
|
||||
}
|
||||
|
||||
static void defineHandle(nb::module_& toolsModule)
|
||||
{
|
||||
nb::class_<PyVBMHandle>(toolsModule, "VoxelBlockManagerHandle",
|
||||
"Owns the firstLeafID / jumpMap metadata buffers backing a "
|
||||
"VoxelBlockManager. Constructed by nanovdb.tools.buildVoxelBlockManager.")
|
||||
.def(nb::init<>(),
|
||||
"Construct an empty VoxelBlockManagerHandle with no buffers.")
|
||||
.def("blockCount", &PyVBMHandle::blockCount,
|
||||
"Number of voxel blocks managed by this handle.")
|
||||
.def("firstOffset", &PyVBMHandle::firstOffset,
|
||||
"Sequential voxel index of the first active voxel covered "
|
||||
"by this handle (1 by default when the handle covers the "
|
||||
"full grid).")
|
||||
.def("lastOffset", &PyVBMHandle::lastOffset,
|
||||
"Sequential voxel index of the last active voxel covered "
|
||||
"by this handle.")
|
||||
.def("reset", &PyVBMHandle::reset,
|
||||
"Release this handle's buffers and reset it to the empty state.")
|
||||
.def_prop_ro("log2_block_width", [](const PyVBMHandle& h) { return h.log2BlockWidth; },
|
||||
"The log2_block_width this handle was built with. The jumpMap "
|
||||
"and decodeBlock outputs derive their shapes from this value.")
|
||||
.def_prop_ro("block_width", &PyVBMHandle::blockWidth,
|
||||
"BlockWidth = 1 << log2_block_width (64, 128, 256, or 512).")
|
||||
.def_prop_ro("jump_map_length", &PyVBMHandle::jumpMapLength,
|
||||
"JumpMapLength = BlockWidth / 64 (1, 2, 4, or 8).")
|
||||
.def(
|
||||
"__bool__",
|
||||
[](const PyVBMHandle& h) { return h.blockCount() > 0; },
|
||||
nb::is_operator())
|
||||
// Zero-copy view of the (blockCount,) firstLeafID array.
|
||||
.def("firstLeafID",
|
||||
[](nb::handle py_self) -> nb::object {
|
||||
auto& h = nb::cast<PyVBMHandle&>(py_self);
|
||||
size_t shape[1] = {static_cast<size_t>(h.blockCount())};
|
||||
// A default-constructed or reset() handle has a null
|
||||
// hostFirstLeafID(); we still return an empty (0,) ndarray
|
||||
// so callers don't have to branch on a None sentinel. The
|
||||
// dummy non-null pointer (the handle itself) keeps nanobind
|
||||
// happy; nothing is read since the leading shape is 0.
|
||||
uint32_t* raw = h.handle.hostFirstLeafID();
|
||||
void* data = (raw != nullptr) ? static_cast<void*>(raw)
|
||||
: static_cast<void*>(&h);
|
||||
return nb::cast(
|
||||
nb::ndarray<nb::numpy, uint32_t, nb::ndim<1>,
|
||||
nb::c_contig, nb::device::cpu>(
|
||||
data, size_t(1), shape, py_self),
|
||||
nb::rv_policy::reference);
|
||||
},
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a zero-copy (blockCount,) uint32 NumPy view of the "
|
||||
"firstLeafID array. Returns an empty (0,) array on a "
|
||||
"default-constructed or reset() handle. The view keeps this "
|
||||
"handle alive.")
|
||||
// jumpMap is uint64_t[blockCount * JumpMapLength]. JumpMapLength is
|
||||
// determined by the log2_block_width recorded on the handle, not by
|
||||
// the caller — that way the returned view always covers exactly the
|
||||
// allocated buffer, with no risk of OOB reads.
|
||||
.def("jumpMap",
|
||||
[](nb::handle py_self) -> nb::object {
|
||||
auto& h = nb::cast<PyVBMHandle&>(py_self);
|
||||
size_t shape[2] = {static_cast<size_t>(h.blockCount()),
|
||||
static_cast<size_t>(h.jumpMapLength())};
|
||||
// Same null-buffer guard as firstLeafID(): a
|
||||
// default-constructed / reset() handle has a null
|
||||
// hostJumpMap(); return an empty (0, jump_map_length)
|
||||
// ndarray rather than passing nullptr to nanobind.
|
||||
uint64_t* raw = h.handle.hostJumpMap();
|
||||
void* data = (raw != nullptr) ? static_cast<void*>(raw)
|
||||
: static_cast<void*>(&h);
|
||||
return nb::cast(
|
||||
nb::ndarray<nb::numpy, uint64_t, nb::ndim<2>,
|
||||
nb::c_contig, nb::device::cpu>(
|
||||
data, size_t(2), shape, py_self),
|
||||
nb::rv_policy::reference);
|
||||
},
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return a zero-copy (blockCount, jump_map_length) uint64 NumPy "
|
||||
"view of the jumpMap. The shape is determined by the "
|
||||
"log2_block_width the handle was built with. Returns an empty "
|
||||
"(0, jump_map_length) array on a default-constructed or reset() "
|
||||
"handle. The view keeps this handle alive.")
|
||||
// Decode the inverse maps for a single block of this VBM. The
|
||||
// log2_block_width is taken from the handle, so the caller cannot
|
||||
// request a width that doesn't match what was built.
|
||||
.def("decodeBlock",
|
||||
[](PyVBMHandle& self,
|
||||
nb::handle py_grid,
|
||||
uint64_t block_index) -> nb::object {
|
||||
const auto* grid = castOnIndexGrid(py_grid,
|
||||
"VoxelBlockManagerHandle.decodeBlock");
|
||||
if (block_index >= self.blockCount()) {
|
||||
throw nb::index_error(
|
||||
"VoxelBlockManagerHandle.decodeBlock(block_index): "
|
||||
"block_index out of range [0, blockCount).");
|
||||
}
|
||||
// Defensive: NanoVDB's buildVoxelBlockManager doesn't always
|
||||
// initialize firstLeafID for blocks where no leaf starts at
|
||||
// a block boundary AND no leaf's iteration sweep reaches
|
||||
// them (e.g. when the source grid is tile-compressed, so
|
||||
// some sequential offsets correspond to tile values rather
|
||||
// than leaf voxels). The slot is then uninitialized memory;
|
||||
// passing it into decodeInverseMaps would lead to an OOB
|
||||
// read of tree.getFirstNode<0>()[garbage]. Catch the case
|
||||
// and raise rather than segfault.
|
||||
const uint32_t firstLeafID =
|
||||
self.handle.hostFirstLeafID()[block_index];
|
||||
const uint32_t nLeaves = grid->tree().nodeCount(0);
|
||||
if (firstLeafID >= nLeaves) {
|
||||
throw nb::value_error(
|
||||
"VoxelBlockManagerHandle.decodeBlock: the VBM's "
|
||||
"firstLeafID for this block was not initialized by "
|
||||
"buildVoxelBlockManager (the underlying algorithm "
|
||||
"doesn't cover blocks that no leaf reaches via its "
|
||||
"iteration). This typically happens on OnIndex "
|
||||
"grids built from tile-compressed source grids; "
|
||||
"until the issue is fixed upstream the workaround "
|
||||
"is to build the source grid voxel-by-voxel with "
|
||||
"build::Grid so it stays uncompressed.");
|
||||
}
|
||||
return dispatchLog2BlockWidth(self.log2BlockWidth, [&](auto W) {
|
||||
constexpr int LBW = decltype(W)::value;
|
||||
constexpr int BlockWidth = 1 << LBW;
|
||||
constexpr int JumpMapLength =
|
||||
VoxelBlockManagerBase<LBW>::JumpMapLength;
|
||||
const uint64_t blockFirstOffset =
|
||||
self.firstOffset() + block_index * BlockWidth;
|
||||
return pyDecodeInverseMapsImpl<LBW>(
|
||||
*grid,
|
||||
firstLeafID,
|
||||
self.handle.hostJumpMap() + block_index * JumpMapLength,
|
||||
blockFirstOffset);
|
||||
});
|
||||
},
|
||||
"grid"_a, "block_index"_a,
|
||||
"Decode the inverse maps for the block_index-th block of this "
|
||||
"VBM. Returns (leaf_index, voxel_offset) uint32 / uint16 NumPy "
|
||||
"arrays of length BlockWidth = 1<<log2_block_width, using the "
|
||||
"log2_block_width the handle was built with.");
|
||||
}
|
||||
|
||||
// ------------------- buildVoxelBlockManager binding -----------------------
|
||||
|
||||
static void defineBuild(nb::module_& toolsModule)
|
||||
{
|
||||
toolsModule.def("buildVoxelBlockManager",
|
||||
[](nb::handle py_grid,
|
||||
int log2_block_width,
|
||||
uint64_t first_offset,
|
||||
uint64_t last_offset,
|
||||
uint64_t n_blocks) -> PyVBMHandle {
|
||||
const auto* grid = castOnIndexGrid(py_grid, "buildVoxelBlockManager");
|
||||
// The C++ implementation only NANOVDB_ASSERTs these preconditions,
|
||||
// which makes them no-ops in release builds. Validate them here
|
||||
// so Python callers get a clear error instead of UB / abort.
|
||||
if (!grid->isSequential()) {
|
||||
throw nb::value_error(
|
||||
"buildVoxelBlockManager: grid must satisfy "
|
||||
"grid.isSequential() (fixed-size, breadth-first node "
|
||||
"layout). NanoVDB grids constructed via "
|
||||
"tools.createOnIndexGrid satisfy this by default.");
|
||||
}
|
||||
return dispatchLog2BlockWidth(log2_block_width, [&](auto W) {
|
||||
constexpr int LBW = decltype(W)::value;
|
||||
using Base = VoxelBlockManagerBase<LBW>;
|
||||
constexpr uint64_t BlockWidth = Base::BlockWidth;
|
||||
constexpr uint64_t JumpMapLength = Base::JumpMapLength;
|
||||
// first_offset must be 1 (mod BlockWidth). The single-arg
|
||||
// C++ helper would normalize a zero input to 1; we do the
|
||||
// same here so the in-place builder below sees a valid
|
||||
// value. Validate the nonzero case ourselves.
|
||||
if (first_offset != 0 &&
|
||||
((first_offset - 1) & (BlockWidth - 1)) != 0) {
|
||||
throw nb::value_error(
|
||||
"buildVoxelBlockManager: first_offset must satisfy "
|
||||
"first_offset == 1 (mod BlockWidth). Pass 0 (the "
|
||||
"default) to let the implementation use 1.");
|
||||
}
|
||||
if (first_offset == 0) first_offset = 1;
|
||||
if (last_offset == 0) last_offset = grid->activeVoxelCount();
|
||||
if (last_offset < first_offset) return PyVBMHandle();
|
||||
// Capacity must hold at least ceil((last - first + 1) /
|
||||
// BlockWidth) blocks; otherwise the handle's lastOffset
|
||||
// would advertise more coverage than blockCount allows
|
||||
// and decodeBlock would silently truncate. The formula
|
||||
// below equals the ceil() above when BlockWidth is a
|
||||
// power of two.
|
||||
const uint64_t minBlocks =
|
||||
(last_offset - first_offset + BlockWidth) >> LBW;
|
||||
if (n_blocks != 0 && n_blocks < minBlocks) {
|
||||
std::string msg(
|
||||
"buildVoxelBlockManager: n_blocks must be at "
|
||||
"least ceil((last_offset - first_offset + 1) / "
|
||||
"BlockWidth) = ");
|
||||
msg += std::to_string(minBlocks);
|
||||
msg += ". Pass 0 (the default) to use the minimum "
|
||||
"required capacity.";
|
||||
throw nb::value_error(msg.c_str());
|
||||
}
|
||||
if (n_blocks == 0) n_blocks = minBlocks;
|
||||
// Allocate the metadata buffers ourselves so we can
|
||||
// pre-initialize firstLeafID with a sentinel value before
|
||||
// the in-place builder runs. The C++ allocating overload
|
||||
// calls HostBuffer::create() which returns uninitialized
|
||||
// memory; blocks that the algorithm doesn't touch would
|
||||
// then retain arbitrary values, and our decodeBlock guard
|
||||
// (firstLeafID >= nLeaves) might miss any garbage value
|
||||
// that happens to be < nLeaves. By prefilling with nLeaves
|
||||
// up front, every untouched slot deterministically trips
|
||||
// the guard.
|
||||
auto firstLeafIDBuf = HostBuffer::create(
|
||||
n_blocks * sizeof(uint32_t));
|
||||
auto jumpMapBuf = HostBuffer::create(
|
||||
n_blocks * JumpMapLength * sizeof(uint64_t));
|
||||
const uint32_t nLeaves = grid->tree().nodeCount(0);
|
||||
{
|
||||
uint32_t* slots = static_cast<uint32_t*>(
|
||||
firstLeafIDBuf.data());
|
||||
for (uint64_t i = 0; i < n_blocks; ++i) {
|
||||
slots[i] = nLeaves;
|
||||
}
|
||||
}
|
||||
VoxelBlockManagerHandle<HostBuffer> handle(
|
||||
std::move(firstLeafIDBuf), std::move(jumpMapBuf),
|
||||
n_blocks, first_offset, last_offset);
|
||||
// In-place builder zeros the jumpMap itself and only
|
||||
// touches firstLeafID slots it actually visits. Release the
|
||||
// GIL around it — it's pure C++ (touches no Python objects)
|
||||
// and may parallelize internally via util::forEach.
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
buildVoxelBlockManager<LBW, HostBuffer>(grid, handle);
|
||||
}
|
||||
return PyVBMHandle(std::move(handle), LBW);
|
||||
});
|
||||
},
|
||||
"grid"_a,
|
||||
"log2_block_width"_a = 6,
|
||||
"first_offset"_a = 0,
|
||||
"last_offset"_a = 0,
|
||||
"n_blocks"_a = 0,
|
||||
"Build a host-side VoxelBlockManager from an OnIndexGrid. "
|
||||
"log2_block_width selects the per-block active-voxel count "
|
||||
"(6=64, 7=128, 8=256, 9=512). Pass 0 for first_offset / "
|
||||
"last_offset / n_blocks to use the full grid (first active "
|
||||
"voxel through grid.activeVoxelCount(), minimum block count). "
|
||||
"first_offset, if nonzero, must satisfy first_offset == 1 "
|
||||
"(mod BlockWidth).");
|
||||
}
|
||||
|
||||
// --------------------- decodeInverseMaps binding --------------------------
|
||||
|
||||
static void defineDecode(nb::module_& toolsModule)
|
||||
{
|
||||
toolsModule.def("decodeInverseMaps",
|
||||
[](nb::handle py_grid,
|
||||
uint32_t first_leaf_id,
|
||||
nb::ndarray<const uint64_t, nb::ndim<1>,
|
||||
nb::c_contig, nb::device::cpu> jump_map,
|
||||
uint64_t block_first_offset,
|
||||
int log2_block_width) -> nb::object {
|
||||
const auto* grid = castOnIndexGrid(py_grid, "decodeInverseMaps");
|
||||
// The C++ helper indexes tree.getFirstNode<0>()[first_leaf_id]
|
||||
// without a bounds check, so a stray first_leaf_id leads to an
|
||||
// OOB read. Validate up front. (We also require isSequential();
|
||||
// getFirstNode only makes sense on a sequential tree.)
|
||||
if (!grid->isSequential()) {
|
||||
throw nb::value_error(
|
||||
"decodeInverseMaps: grid must satisfy "
|
||||
"grid.isSequential().");
|
||||
}
|
||||
const uint32_t nLeaves = grid->tree().nodeCount(0);
|
||||
if (first_leaf_id >= nLeaves) {
|
||||
throw nb::index_error(
|
||||
"decodeInverseMaps: first_leaf_id out of range "
|
||||
"[0, grid.tree().nodeCount(0)).");
|
||||
}
|
||||
return dispatchLog2BlockWidth(log2_block_width, [&](auto W) {
|
||||
constexpr int LBW = decltype(W)::value;
|
||||
constexpr int JumpMapLength =
|
||||
VoxelBlockManagerBase<LBW>::JumpMapLength;
|
||||
if (jump_map.shape(0) != JumpMapLength) {
|
||||
std::string msg("decodeInverseMaps: jump_map must have "
|
||||
"length JumpMapLength = ");
|
||||
msg += std::to_string(JumpMapLength);
|
||||
msg += " for log2_block_width=";
|
||||
msg += std::to_string(LBW);
|
||||
throw nb::value_error(msg.c_str());
|
||||
}
|
||||
return pyDecodeInverseMapsImpl<LBW>(
|
||||
*grid, first_leaf_id, jump_map.data(),
|
||||
block_first_offset);
|
||||
});
|
||||
},
|
||||
"grid"_a,
|
||||
"first_leaf_id"_a,
|
||||
"jump_map"_a,
|
||||
"block_first_offset"_a,
|
||||
"log2_block_width"_a = 6,
|
||||
"Decode the inverse maps for a single voxel block of an OnIndexGrid. "
|
||||
"Returns a (leaf_index, voxel_offset) tuple of fresh NumPy arrays of "
|
||||
"length BlockWidth = 1<<log2_block_width. jump_map must have length "
|
||||
"BlockWidth/64. first_leaf_id must be in [0, grid.tree().nodeCount(0)).");
|
||||
}
|
||||
|
||||
// ----- createOnIndexGrid test-scaffold factory ----------------------------
|
||||
//
|
||||
// Narrow source-coverage factory used by the VoxelBlockManager unit tests.
|
||||
// New code should prefer tools.createNanoGridOnIndex (in PyCreateNanoGrid.cc)
|
||||
// which accepts a wider source set.
|
||||
|
||||
template<typename SrcBuildT>
|
||||
static nb::object tryCreateOnIndexGrid(nb::handle py_grid,
|
||||
uint32_t channels,
|
||||
bool include_stats,
|
||||
bool include_tiles,
|
||||
int verbose)
|
||||
{
|
||||
using SrcGridT = NanoGrid<SrcBuildT>;
|
||||
if (!nb::isinstance<SrcGridT>(py_grid)) {
|
||||
return nb::object();
|
||||
}
|
||||
const SrcGridT& src = nb::cast<const SrcGridT&>(py_grid);
|
||||
return nb::cast(
|
||||
tools::createNanoGrid<SrcGridT, ValueOnIndex, HostBuffer>(
|
||||
src, channels, include_stats, include_tiles, verbose));
|
||||
}
|
||||
|
||||
static void defineCreateOnIndexGrid(nb::module_& toolsModule)
|
||||
{
|
||||
toolsModule.def("createOnIndexGrid",
|
||||
[](nb::handle py_grid,
|
||||
uint32_t channels,
|
||||
bool include_stats,
|
||||
bool include_tiles,
|
||||
int verbose) -> nb::object {
|
||||
// Try every source BuildT we accept.
|
||||
if (auto r = tryCreateOnIndexGrid<float>(
|
||||
py_grid, channels, include_stats, include_tiles, verbose);
|
||||
r.is_valid()) return r;
|
||||
if (auto r = tryCreateOnIndexGrid<double>(
|
||||
py_grid, channels, include_stats, include_tiles, verbose);
|
||||
r.is_valid()) return r;
|
||||
if (auto r = tryCreateOnIndexGrid<int32_t>(
|
||||
py_grid, channels, include_stats, include_tiles, verbose);
|
||||
r.is_valid()) return r;
|
||||
if (auto r = tryCreateOnIndexGrid<Vec3f>(
|
||||
py_grid, channels, include_stats, include_tiles, verbose);
|
||||
r.is_valid()) return r;
|
||||
throw nb::type_error(
|
||||
"createOnIndexGrid: source grid must be a FloatGrid, "
|
||||
"DoubleGrid, Int32Grid, or Vec3fGrid (other source BuildTs "
|
||||
"are not yet bound).");
|
||||
},
|
||||
"src_grid"_a,
|
||||
"channels"_a = 0u,
|
||||
"include_stats"_a = true,
|
||||
"include_tiles"_a = true,
|
||||
"verbose"_a = 0,
|
||||
"Convert a source grid into a NanoGrid<ValueOnIndex> "
|
||||
"(OnIndexGrid). Accepts FloatGrid / DoubleGrid / Int32Grid / "
|
||||
"Vec3fGrid. This is a narrow helper kept alongside "
|
||||
"buildVoxelBlockManager; for general index conversion (broader "
|
||||
"source coverage, blind-data channels) prefer "
|
||||
"nanovdb.tools.createNanoGridOnIndex.");
|
||||
}
|
||||
|
||||
void defineVoxelBlockManagerModule(nb::module_& toolsModule)
|
||||
{
|
||||
defineHandle(toolsModule);
|
||||
defineBuild(toolsModule);
|
||||
defineDecode(toolsModule);
|
||||
defineCreateOnIndexGrid(toolsModule);
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Contributors to the OpenVDB Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#ifndef NANOVDB_PYVOXELBLOCKMANAGER_HAS_BEEN_INCLUDED
|
||||
#define NANOVDB_PYVOXELBLOCKMANAGER_HAS_BEEN_INCLUDED
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
/// @brief Bind VoxelBlockManagerHandle<HostBuffer>,
|
||||
/// tools.buildVoxelBlockManager, tools.decodeInverseMaps, and the
|
||||
/// test-scaffold tools.createOnIndexGrid factory under the given
|
||||
/// Python submodule (expected to be the existing nanovdb.tools).
|
||||
void defineVoxelBlockManagerModule(nb::module_& toolsModule);
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
#endif
|
||||
@@ -1,9 +1,26 @@
|
||||
# Copyright Contributors to the OpenVDB Project
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
import sys
|
||||
if sys.platform == "win32":
|
||||
import os
|
||||
openvdb_dll_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, 'openvdb', 'lib')
|
||||
os.add_dll_directory(directory)
|
||||
|
||||
from .lib.nanovdb import *
|
||||
if sys.platform == "win32":
|
||||
_openvdb_dll_directory = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), os.pardir, "openvdb", "lib"
|
||||
)
|
||||
if os.path.isdir(_openvdb_dll_directory):
|
||||
os.add_dll_directory(_openvdb_dll_directory)
|
||||
|
||||
|
||||
def get_include():
|
||||
"""Return the absolute path to the bundled NanoVDB C/C++ include directory.
|
||||
|
||||
Use this from a downstream Python extension's build system so the extension
|
||||
compiles against the same NanoVDB headers the installed wheel was built with::
|
||||
|
||||
import nanovdb
|
||||
ext_kwargs = dict(include_dirs=[nanovdb.get_include()])
|
||||
"""
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "include")
|
||||
|
||||
|
||||
from .lib.nanovdb import * # noqa: E402,F401,F403
|
||||
|
||||
@@ -13,7 +13,10 @@ namespace pynanovdb {
|
||||
|
||||
void defineDeviceBuffer(nb::module_& m)
|
||||
{
|
||||
nb::class_<cuda::DeviceBuffer>(m, "DeviceBuffer");
|
||||
nb::class_<cuda::DeviceBuffer>(m, "DeviceBuffer",
|
||||
"CUDA device-side buffer used to back a DeviceGridHandle. Holds a "
|
||||
"host mirror and a device pointer; deviceUpload / deviceDownload on "
|
||||
"the handle move bytes between the two.");
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <nanobind/ndarray.h>
|
||||
|
||||
#include <nanovdb/cuda/DeviceBuffer.h>
|
||||
#include <nanovdb/NanoVDB.h>
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace nb::literals;
|
||||
@@ -13,6 +14,48 @@ using namespace nanovdb;
|
||||
|
||||
namespace pynanovdb {
|
||||
|
||||
// Device-side polymorphic deviceGrid(n) — same dispatch shape as
|
||||
// pyHostGrid<BufferT> in PyGridHandle.h, but returns the device pointer.
|
||||
// gridType(n) is read from the host-side GridData header (the handle keeps
|
||||
// a host mirror), so this works whether or not the grid has been uploaded.
|
||||
// Returns None if the device-side grid is null (i.e. no deviceUpload yet)
|
||||
// or the BuildT is not Python-visible.
|
||||
static nb::object pyDeviceGrid(nb::handle py_handle, uint32_t n)
|
||||
{
|
||||
using BufferT = nanovdb::cuda::DeviceBuffer;
|
||||
auto& handle = nb::cast<GridHandle<BufferT>&>(py_handle);
|
||||
if (n >= handle.gridCount()) return nb::none();
|
||||
switch (handle.gridType(n)) {
|
||||
#define NANOVDB_PY_FOR_EACH_SCALAR_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template deviceGrid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_VECTOR_BUILDT(T, Suffix, AccessorName, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template deviceGrid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_POINT_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template deviceGrid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#define NANOVDB_PY_FOR_EACH_READONLY_BUILDT(T, Suffix, GridTypeEnum) \
|
||||
case nanovdb::GridType::GridTypeEnum: { \
|
||||
auto* grid = handle.template deviceGrid<T>(n); \
|
||||
return grid ? nb::cast(grid, nb::rv_policy::reference, py_handle) \
|
||||
: nb::none(); \
|
||||
}
|
||||
#include "../BuildTypes.def"
|
||||
default:
|
||||
return nb::none();
|
||||
}
|
||||
}
|
||||
|
||||
void defineDeviceGridHandle(nb::module_& m)
|
||||
{
|
||||
using BufferT = nanovdb::cuda::DeviceBuffer;
|
||||
@@ -27,19 +70,31 @@ void defineDeviceGridHandle(nb::module_& m)
|
||||
new (&handle) GridHandle<BufferT>(std::move(buffer));
|
||||
},
|
||||
"cpu_t"_a.noconvert(),
|
||||
"cuda_t"_a.noconvert())
|
||||
.def("deviceFloatGrid", nb::overload_cast<uint32_t>(&GridHandle<BufferT>::template deviceGrid<float>), "n"_a = 0, nb::rv_policy::reference_internal)
|
||||
.def("deviceDoubleGrid", nb::overload_cast<uint32_t>(&GridHandle<BufferT>::template deviceGrid<double>), "n"_a = 0, nb::rv_policy::reference_internal)
|
||||
.def("deviceInt32Grid", nb::overload_cast<uint32_t>(&GridHandle<BufferT>::template deviceGrid<int32_t>), "n"_a = 0, nb::rv_policy::reference_internal)
|
||||
.def("deviceVec3fGrid", nb::overload_cast<uint32_t>(&GridHandle<BufferT>::template deviceGrid<Vec3f>), "n"_a = 0, nb::rv_policy::reference_internal)
|
||||
.def("deviceRGBA8Grid",
|
||||
nb::overload_cast<uint32_t>(&GridHandle<BufferT>::template deviceGrid<math::Rgba8>),
|
||||
"n"_a = 0,
|
||||
nb::rv_policy::reference_internal)
|
||||
"cuda_t"_a.noconvert(),
|
||||
"Construct a DeviceGridHandle that wraps an existing pair of "
|
||||
"host and device uint32 arrays of equal length.")
|
||||
.def("deviceGrid", &pyDeviceGrid, "n"_a = 0,
|
||||
nb::keep_alive<0, 1>(),
|
||||
"Return the n-th device-resident grid as a typed Grid subclass "
|
||||
"selected by gridType(n), or None if the BuildT is not bound in "
|
||||
"Python or the device copy has not been uploaded yet. The "
|
||||
"returned grid keeps this handle alive.")
|
||||
.def(
|
||||
"deviceUpload", [](GridHandle<BufferT>& handle, bool sync) { handle.deviceUpload(nullptr, sync); }, "sync"_a = true)
|
||||
"deviceUpload", [](GridHandle<BufferT>& handle, bool sync) { handle.deviceUpload(nullptr, sync); }, "sync"_a = true,
|
||||
"Copy the host-side buffer to the device. If sync is True the "
|
||||
"call blocks until the transfer completes.")
|
||||
.def(
|
||||
"deviceDownload", [](GridHandle<BufferT>& handle, bool sync) { handle.deviceDownload(nullptr, sync); }, "sync"_a = true);
|
||||
"deviceDownload", [](GridHandle<BufferT>& handle, bool sync) { handle.deviceDownload(nullptr, sync); }, "sync"_a = true,
|
||||
"Copy the device-side buffer back to the host. If sync is True "
|
||||
"the call blocks until the transfer completes.");
|
||||
// NOTE: defineGridHandleUtilities<BufferT> intentionally NOT called for
|
||||
// DeviceBuffer. Registering nanovdb.splitGrids / nanovdb.mergeGrids as a
|
||||
// second overload taking a DeviceGridHandle list conflicts with the host
|
||||
// overload because both signatures take nb::list, and nanobind's
|
||||
// overload resolution can't disambiguate by element type — it picks one
|
||||
// and the inner cast fails with std::bad_cast. A properly typed device
|
||||
// variant (with its own name, or strongly-typed std::vector<HandleT>
|
||||
// args via nanobind/stl/vector.h) can land later if it's needed.
|
||||
}
|
||||
|
||||
} // namespace pynanovdb
|
||||
|
||||
@@ -50,7 +50,8 @@ template<typename BuildT> void definePointsToGrid(nb::module_& m, const char* na
|
||||
auto handle = converter.getHandle(points, tensor.shape(0));
|
||||
return handle;
|
||||
},
|
||||
"tensor"_a);
|
||||
"tensor"_a,
|
||||
"Rasterize the given (N, 3) int32 device tensor of points into a fresh GridHandle.");
|
||||
}
|
||||
|
||||
template void definePointsToGrid<nanovdb::math::Rgba8>(nb::module_&, const char*);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# NanoVDB Python Examples
|
||||
|
||||
Runnable `.py` scripts demonstrating the NanoVDB Python bindings.
|
||||
Each script is self-contained, builds its own input data, and prints
|
||||
a small summary to stdout.
|
||||
|
||||
Run any example with:
|
||||
|
||||
```bash
|
||||
python load_inspect.py
|
||||
```
|
||||
|
||||
When working from the source tree, the `nanovdb` module needs to be
|
||||
on `PYTHONPATH` (e.g. via the build output directory):
|
||||
|
||||
```bash
|
||||
cd <build>/nanovdb/nanovdb/python
|
||||
PYTHONPATH=. python /path/to/<example>.py
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
| Script | What it shows |
|
||||
| --- | --- |
|
||||
| [`load_inspect.py`](load_inspect.py) | Polymorphic `handle.grid(n)` access, `GridMetaData` type-erased introspection, mixed-type handles via `mergeGrids`. |
|
||||
| [`build_grid.py`](build_grid.py) | Voxel-by-voxel construction with `nanovdb.tools.build.FloatGrid`, the cached `ValueAccessor`, the thread-safe `WriteAccessor`, and `.to_nanovdb()` to bake into a host `GridHandle`. |
|
||||
| [`bulk_leaf_numpy.py`](bulk_leaf_numpy.py) | Zero-copy `(N_leaves, 512)` NumPy view of every leaf's mValues via `grid.leaf_values()`. Includes a global-stats reduction and an in-place mutation that propagates back into the grid. Requires NumPy. |
|
||||
| [`quantize.py`](quantize.py) | Quantize a `NanoGrid<float>` through `nanovdb.tools.createNanoGridFp{4,8,16,N}`. Shows fixed-width quantization with dithering and variable-width `FpN` with both `AbsDiff` and `RelDiff` oracles. |
|
||||
| [`validate.py`](validate.py) | `nanovdb.tools.validateGrid` / `validateGrids`, `checkGrid`, `isValid`, and the `evalChecksum` / `validateChecksum` / `updateChecksum` round-trip. |
|
||||
| [`io_roundtrip.py`](io_roundtrip.py) | `nanovdb.io` write/read round-trip over five primitives: `writeGrids` with codec fallback, `readGridMetaData`, `hasGrid`, `readGrid` by name, `splitGrids`, and zero-copy point positions via `getBlindData`. Port of `ex_write_nanovdb_grids` + `ex_read_nanovdb_sphere_accessor`. |
|
||||
| [`make_funny_nanovdb.py`](make_funny_nanovdb.py) | Functor-based construction: `tools.createFloatGrid(background, name, gridClass, func, bbox)` evaluates a Python callback at every voxel. Port of `ex_make_funny_nanovdb` on a reduced domain. |
|
||||
| [`make_typed_grids.py`](make_typed_grids.py) | One solid sphere per value type via `tools.build.{Float,Double,Int16,Int32,Int64,UInt32,Vec3f}Grid`, written to a single file and re-read with polymorphic `handle.grid()` dispatch. Port of `ex_make_typed_grids`. |
|
||||
| [`raytrace_level_set.py`](raytrace_level_set.py) | CPU sphere-traced level-set render to PGM using `worldToIndex`, the trilinear sampler, `zeroCrossing()`, and normalized `gradient()` shading. Port of `ex_raytrace_level_set` (host path). |
|
||||
| [`raytrace_fog_volume.py`](raytrace_fog_volume.py) | CPU transmittance ray-march of a fog volume to PGM using a `ReadAccessor` and `Coord.Floor` — the accessor-based sampling idiom. Port of `ex_raytrace_fog_volume` (host path). |
|
||||
| [`collide_level_set.py`](collide_level_set.py) | Particles colliding with a level set: `worldToIndexF`, `tree.isActive` narrow-band test, accessor distance reads, and `sampler.gradient()` collision normals. Port of `ex_collide_level_set` (host path). |
|
||||
| [`index_grid_channels.py`](index_grid_channels.py) | `tools.createNanoGridOnIndex(src, channels=1)`, `grid.valueCount()`, coordinate reads through `createChannelAccessor`, and blind-data authoring with `tools.CreateNanoGrid.addBlindData` + the writable `getBlindData` view. Extends the host half of `ex_index_grid_cuda`. Requires NumPy for the authoring section. |
|
||||
| [`node_manager.py`](node_manager.py) | Linearized node iteration with `createNodeManager`: per-level counts, `leaf(i)` / `lower(i)` access, node origins, masks, and stats. Port of the host half of `ex_nodemanager_cuda`. |
|
||||
| [`openvdb_interop.py`](openvdb_interop.py) | `tools.openToNanoVDB` / `nanoToOpenVDB` round-trip with accessor comparison on both sides. Self-skips unless built with `NANOVDB_USE_OPENVDB` and `openvdb` is importable. Port of `ex_openvdb_to_nanovdb_accessor`. |
|
||||
|
||||
Scripts that produce files write them to a fresh temporary directory
|
||||
and print its path, so the source tree stays clean. The whole set is
|
||||
smoke-tested by [`../test/TestExamples.py`](../test/TestExamples.py)
|
||||
(ctest name `pytest_nanovdb_examples`) when the module is configured
|
||||
with `NANOVDB_BUILD_PYTHON_UNITTESTS=ON`.
|
||||
|
||||
For full API signatures and per-argument docstrings, use Python's
|
||||
`help()` on any symbol — e.g. `help(nanovdb.tools.createNanoGridFpN)`.
|
||||
The bindings ship `.pyi` type stubs for IDE / type-checker support.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright Contributors to the OpenVDB Project
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Construct a NanoVDB grid voxel-by-voxel in pure Python.
|
||||
|
||||
nanovdb.tools.build.<Suffix>Grid is the mutable CPU grid builder
|
||||
that mirrors nanovdb::tools::build::Grid<T>. This example shows the
|
||||
three construction loops you'll typically reach for (setValue
|
||||
directly, the cached ValueAccessor, and the thread-safe
|
||||
WriteAccessor), then bakes each build grid into a host NanoGrid via
|
||||
.to_nanovdb() and reads it back through the regular polymorphic
|
||||
handle.grid() API.
|
||||
|
||||
Run with: python build_grid.py
|
||||
"""
|
||||
import nanovdb
|
||||
|
||||
|
||||
def fill_with_setValue():
|
||||
"""Simplest path: setValue() directly on the build grid."""
|
||||
g = nanovdb.tools.build.FloatGrid(
|
||||
background=0.0, name="setValue_demo",
|
||||
gridClass=nanovdb.GridClass.FogVolume)
|
||||
# Plant five active voxels along the x axis.
|
||||
for i in range(5):
|
||||
g.setValue(nanovdb.math.Coord(i, 0, 0), float(i + 1))
|
||||
return g
|
||||
|
||||
|
||||
def fill_with_accessor():
|
||||
"""Cached path: getAccessor() avoids re-walking the tree for
|
||||
each write when consecutive coordinates share a leaf."""
|
||||
g = nanovdb.tools.build.FloatGrid(background=0.0, name="accessor_demo")
|
||||
acc = g.getAccessor()
|
||||
# The accessor caches the last leaf / lower / upper node, so a
|
||||
# burst of writes in a 16^3 neighborhood only walks the tree once.
|
||||
for x in range(8):
|
||||
for y in range(8):
|
||||
for z in range(8):
|
||||
if x + y + z == 7:
|
||||
acc.setValue(nanovdb.math.Coord(x, y, z),
|
||||
float(x * 64 + y * 8 + z))
|
||||
return g
|
||||
|
||||
|
||||
def fill_with_write_accessor():
|
||||
"""Thread-safe path: getWriteAccessor() buffers writes into a
|
||||
private root and merges them into the parent on destruction or
|
||||
on an explicit .merge() call. Useful when fanning out to multiple
|
||||
threads — one WriteAccessor per thread, no shared mutable state."""
|
||||
g = nanovdb.tools.build.FloatGrid(background=0.0, name="write_accessor_demo")
|
||||
wa = g.getWriteAccessor()
|
||||
wa.setValue(nanovdb.math.Coord(50, 50, 50), 9.0)
|
||||
# Before merge, the parent doesn't see the change yet.
|
||||
assert g.getValue(nanovdb.math.Coord(50, 50, 50)) == 0.0
|
||||
wa.merge()
|
||||
assert g.getValue(nanovdb.math.Coord(50, 50, 50)) == 9.0
|
||||
return g
|
||||
|
||||
|
||||
def main():
|
||||
for builder in (fill_with_setValue, fill_with_accessor,
|
||||
fill_with_write_accessor):
|
||||
g = builder()
|
||||
print(f"=== {g.getName()} ===")
|
||||
print(f" nodeCount (leaf, lower, upper) = {g.nodeCount()}")
|
||||
|
||||
# Bake into a host NanoGrid<float>.
|
||||
handle = g.to_nanovdb(sMode=nanovdb.tools.StatsMode.All)
|
||||
ng = handle.grid()
|
||||
print(f" baked NanoGrid: type={ng.gridType()}, "
|
||||
f"active={ng.activeVoxelCount()}, "
|
||||
f"worldBBox={ng.worldBBox()}")
|
||||
|
||||
# The build grid is left untouched — we can bake again.
|
||||
handle2 = g.to_nanovdb()
|
||||
assert handle2.grid().activeVoxelCount() == ng.activeVoxelCount()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright Contributors to the OpenVDB Project
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Bulk per-leaf value access as a zero-copy NumPy array.
|
||||
|
||||
grid.leaf_values() is the highest-bandwidth path from NanoVDB into
|
||||
NumPy. It returns an (N_leaves, 512) view of every leaf's mValues
|
||||
without copying — modify it, slice it, feed it into a PyTorch tensor,
|
||||
hash it for cache lookup, whatever you need.
|
||||
|
||||
Run with: python bulk_leaf_numpy.py
|
||||
"""
|
||||
import nanovdb
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
print("This example requires numpy. Install it with: pip install numpy")
|
||||
return
|
||||
|
||||
# Build a fog volume sphere with stats so the leaves have meaningful
|
||||
# min/max attached (just for the printing below — not required by
|
||||
# leaf_values itself).
|
||||
handle = nanovdb.tools.createFogVolumeSphere(
|
||||
radius=20.0, name="bulk_demo")
|
||||
grid = handle.grid()
|
||||
print(f"Grid: {grid.gridType()}, active voxels = {grid.activeVoxelCount()}, "
|
||||
f"leaves = {grid.tree().nodeCount(0)}")
|
||||
|
||||
# leaf_values() is the zero-copy view. Modifying it modifies the grid.
|
||||
bulk = grid.leaf_values()
|
||||
# np.asarray adds a NumPy wrapper but doesn't copy.
|
||||
arr = np.asarray(bulk)
|
||||
print(f"leaf_values: shape={arr.shape}, dtype={arr.dtype}, "
|
||||
f"backed by grid memory (no copy).")
|
||||
|
||||
# Global statistics across every voxel in every leaf, computed in C.
|
||||
# 0.0 voxels (background) are excluded by using a mask.
|
||||
nonzero = arr[arr != 0.0]
|
||||
print(f" non-background voxels = {nonzero.size}")
|
||||
print(f" min = {nonzero.min()}, max = {nonzero.max()}, "
|
||||
f"mean = {nonzero.mean()}")
|
||||
|
||||
# Per-leaf reductions: each row of `arr` is one leaf's 512 voxels.
|
||||
per_leaf_max = arr.max(axis=1)
|
||||
print(f" per-leaf max (first 5): {per_leaf_max[:5]}")
|
||||
|
||||
# Zero-copy means writes propagate. Zero out the first leaf's values
|
||||
# and read one back through the regular accessor to confirm the grid
|
||||
# actually changed. (The active *mask* is unchanged — we wrote into
|
||||
# mValues only — so activeVoxelCount() stays the same.)
|
||||
arr[0] = 0.0
|
||||
leaf = grid.tree().getFirstLeaf()
|
||||
if leaf is not None:
|
||||
first_value_after = leaf.getFirstValue()
|
||||
print(f" zeroed first leaf's values in place: "
|
||||
f"leaf.getFirstValue() = {first_value_after}, "
|
||||
f"activeVoxelCount unchanged: {grid.activeVoxelCount()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user