From db4c5c3b9848cd79988468019156c5b0e59b3d81 Mon Sep 17 00:00:00 2001 From: Jefferson Amstutz Date: Mon, 30 Jan 2023 20:38:09 -0600 Subject: [PATCH] initial implementation of ANARI rendering support --- .gitignore | 2 + .gitlab-ci.yml | 2 +- .gitlab/ci/config/initial_config.cmake | 3 + .../ci/docker/ubuntu2004/kokkos/Dockerfile | 22 + .gitlab/ci/ubuntu2004.yml | 3 +- CMake/VTKmConfig.cmake.in | 12 + CMakeLists.txt | 8 + data/baseline/interop/anari/glyphs.png | 3 + data/baseline/interop/anari/isosurface.png | 3 + data/baseline/interop/anari/points.png | 3 + .../interop/anari/scene-empty-mappers.png | 3 + data/baseline/interop/anari/scene.png | 3 + data/baseline/interop/anari/volume.png | 3 + vtkm/interop/anari/ANARIActor.cxx | 128 ++++ vtkm/interop/anari/ANARIActor.h | 108 ++++ vtkm/interop/anari/ANARIMapper.cxx | 201 ++++++ vtkm/interop/anari/ANARIMapper.h | 139 ++++ vtkm/interop/anari/ANARIMapperGlyphs.cxx | 283 ++++++++ vtkm/interop/anari/ANARIMapperGlyphs.h | 118 ++++ vtkm/interop/anari/ANARIMapperPoints.cxx | 407 ++++++++++++ vtkm/interop/anari/ANARIMapperPoints.h | 137 ++++ vtkm/interop/anari/ANARIMapperTriangles.cxx | 611 ++++++++++++++++++ vtkm/interop/anari/ANARIMapperTriangles.h | 145 +++++ vtkm/interop/anari/ANARIMapperVolume.cxx | 211 ++++++ vtkm/interop/anari/ANARIMapperVolume.h | 108 ++++ vtkm/interop/anari/ANARIScene.cxx | 155 +++++ vtkm/interop/anari/ANARIScene.h | 176 +++++ vtkm/interop/anari/CMakeLists.txt | 47 ++ vtkm/interop/anari/VtkmANARITypes.cxx | 26 + vtkm/interop/anari/VtkmANARITypes.h | 43 ++ vtkm/interop/anari/testing/ANARITestCommon.h | 149 +++++ vtkm/interop/anari/testing/CMakeLists.txt | 18 + .../testing/UnitTestANARIMapperGlyphs.cxx | 77 +++ .../testing/UnitTestANARIMapperPoints.cxx | 84 +++ .../testing/UnitTestANARIMapperTriangles.cxx | 85 +++ .../testing/UnitTestANARIMapperVolume.cxx | 74 +++ .../anari/testing/UnitTestANARIScene.cxx | 110 ++++ vtkm/interop/anari/vtkm.module | 14 + 38 files changed, 3722 insertions(+), 2 deletions(-) create mode 100644 data/baseline/interop/anari/glyphs.png create mode 100644 data/baseline/interop/anari/isosurface.png create mode 100644 data/baseline/interop/anari/points.png create mode 100644 data/baseline/interop/anari/scene-empty-mappers.png create mode 100644 data/baseline/interop/anari/scene.png create mode 100644 data/baseline/interop/anari/volume.png create mode 100644 vtkm/interop/anari/ANARIActor.cxx create mode 100644 vtkm/interop/anari/ANARIActor.h create mode 100644 vtkm/interop/anari/ANARIMapper.cxx create mode 100644 vtkm/interop/anari/ANARIMapper.h create mode 100644 vtkm/interop/anari/ANARIMapperGlyphs.cxx create mode 100644 vtkm/interop/anari/ANARIMapperGlyphs.h create mode 100644 vtkm/interop/anari/ANARIMapperPoints.cxx create mode 100644 vtkm/interop/anari/ANARIMapperPoints.h create mode 100644 vtkm/interop/anari/ANARIMapperTriangles.cxx create mode 100644 vtkm/interop/anari/ANARIMapperTriangles.h create mode 100644 vtkm/interop/anari/ANARIMapperVolume.cxx create mode 100644 vtkm/interop/anari/ANARIMapperVolume.h create mode 100644 vtkm/interop/anari/ANARIScene.cxx create mode 100644 vtkm/interop/anari/ANARIScene.h create mode 100644 vtkm/interop/anari/CMakeLists.txt create mode 100644 vtkm/interop/anari/VtkmANARITypes.cxx create mode 100644 vtkm/interop/anari/VtkmANARITypes.h create mode 100644 vtkm/interop/anari/testing/ANARITestCommon.h create mode 100644 vtkm/interop/anari/testing/CMakeLists.txt create mode 100644 vtkm/interop/anari/testing/UnitTestANARIMapperGlyphs.cxx create mode 100644 vtkm/interop/anari/testing/UnitTestANARIMapperPoints.cxx create mode 100644 vtkm/interop/anari/testing/UnitTestANARIMapperTriangles.cxx create mode 100644 vtkm/interop/anari/testing/UnitTestANARIMapperVolume.cxx create mode 100644 vtkm/interop/anari/testing/UnitTestANARIScene.cxx create mode 100644 vtkm/interop/anari/vtkm.module diff --git a/.gitignore b/.gitignore index e43b0f988..867636b06 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .DS_Store +*vscode +.anari_deps diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8be2f709d..ebc70efe0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -65,7 +65,7 @@ - .docker_image .ubuntu2004_kokkos: &ubuntu2004_kokkos - image: "kitware/vtkm:ci-ubuntu2004_kokkos-20230705" + image: "kitware/vtkm:ci-ubuntu2004_kokkos-20230829" extends: - .docker_image diff --git a/.gitlab/ci/config/initial_config.cmake b/.gitlab/ci/config/initial_config.cmake index ab82d6835..923f51159 100644 --- a/.gitlab/ci/config/initial_config.cmake +++ b/.gitlab/ci/config/initial_config.cmake @@ -53,6 +53,9 @@ foreach(option IN LISTS options) elseif(no_rendering STREQUAL option) set(VTKm_ENABLE_RENDERING "OFF" CACHE STRING "") + elseif(anari STREQUAL option) + set(VTKm_ENABLE_ANARI "ON" CACHE STRING "") + elseif(no_testing STREQUAL option) set(VTKm_ENABLE_TESTING "OFF" CACHE STRING "") set(VTKm_ENABLE_TESTING_LIBRARY "OFF" CACHE STRING "") diff --git a/.gitlab/ci/docker/ubuntu2004/kokkos/Dockerfile b/.gitlab/ci/docker/ubuntu2004/kokkos/Dockerfile index 37d28f1c7..ec880dfd2 100644 --- a/.gitlab/ci/docker/ubuntu2004/kokkos/Dockerfile +++ b/.gitlab/ci/docker/ubuntu2004/kokkos/Dockerfile @@ -52,3 +52,25 @@ RUN mkdir -p /opt/kokkos/build && \ cmake -GNinja -DCMAKE_INSTALL_PREFIX=/opt/kokkos -DCMAKE_CXX_FLAGS=-fPIC -DKokkos_ENABLE_SERIAL=ON ../kokkos-$KOKKOS_VERSION &&\ ninja all && \ ninja install + +# Build and install ANARI SDK +WORKDIR /opt/anari/src +ARG ANARI_VERSION=0.7.1 +RUN curl -L https://github.com/KhronosGroup/ANARI-SDK/archive/refs/tags/v$ANARI_VERSION.tar.gz | tar xzv && \ + cmake -GNinja \ + -S ANARI-SDK-$ANARI_VERSION \ + -B build \ + -DBUILD_CTS=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_HELIDE_DEVICE=ON \ + -DBUILD_REMOTE_DEVICE=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_TESTING=OFF \ + -DBUILD_VIEWER=OFF \ + -DCMAKE_INSTALL_PREFIX=/opt/anari \ + -DINSTALL_VIEWER_LIBRARY=OFF && \ + cmake --build build && \ + cmake --install build && \ + rm -rf * + +WORKDIR /root diff --git a/.gitlab/ci/ubuntu2004.yml b/.gitlab/ci/ubuntu2004.yml index c6ff59fe0..9523f5634 100644 --- a/.gitlab/ci/ubuntu2004.yml +++ b/.gitlab/ci/ubuntu2004.yml @@ -22,7 +22,8 @@ build:ubuntu2004_kokkos: - .run_automatically variables: CMAKE_BUILD_TYPE: RelWithDebInfo - VTKM_SETTINGS: "kokkos+shared+64bit_floats" + CMAKE_PREFIX_PATH: "/opt/anari" + VTKM_SETTINGS: "kokkos+shared+64bit_floats+rendering+anari" test:ubuntu2004_kokkos: tags: diff --git a/CMake/VTKmConfig.cmake.in b/CMake/VTKmConfig.cmake.in index c7d945191..2f15d5db3 100644 --- a/CMake/VTKmConfig.cmake.in +++ b/CMake/VTKmConfig.cmake.in @@ -72,6 +72,7 @@ set(VTKm_ENABLE_OPENMP "@VTKm_ENABLE_OPENMP@") set(VTKm_ENABLE_TBB "@VTKm_ENABLE_TBB@") set(VTKm_ENABLE_LOGGING "@VTKm_ENABLE_LOGGING@") set(VTKm_ENABLE_RENDERING "@VTKm_ENABLE_RENDERING@") +set(VTKm_ENABLE_ANARI "@VTKm_ENABLE_ANARI@") set(VTKm_ENABLE_GL_CONTEXT "@VTKm_ENABLE_GL_CONTEXT@") set(VTKm_ENABLE_OSMESA_CONTEXT "@VTKm_ENABLE_OSMESA_CONTEXT@") set(VTKm_ENABLE_EGL_CONTEXT "@VTKm_ENABLE_EGL_CONTEXT@") @@ -93,6 +94,7 @@ endif() include(CMakeFindDependencyMacro) set(CMAKE_MODULE_PATH_save_vtkm "${CMAKE_MODULE_PATH}") +set(PACKAGE_PREFIX_DIR_save_vtkm "${PACKAGE_PREFIX_DIR}") list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_LIST_DIR}") if (VTKm_ENABLE_TBB) @@ -103,6 +105,16 @@ if (VTKm_ENABLE_TBB) endif() endif() +if (VTKm_ENABLE_ANARI) + find_dependency(anari) + if (NOT anari_FOUND) + set(VTKm_FOUND 0) + list(APPEND VTKm_NOT_FOUND_REASON "ANARI not found: ${anari_NOT_FOUND_MESSAGE}") + endif() +endif() + +set(PACKAGE_PREFIX_DIR ${PACKAGE_PREFIX_DIR_save_vtkm}) + # Load the library exports, but only if not compiling VTK-m itself set_and_check(VTKm_CONFIG_DIR "@PACKAGE_VTKm_INSTALL_CONFIG_DIR@") set(VTKM_FROM_INSTALL_DIR FALSE) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b767481b..d0d76b79a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,13 @@ cmake_dependent_option(VTKm_ENABLE_TESTING_LIBRARY "Enable VTKm Testing Library" OFF "NOT VTKm_ENABLE_TESTING;NOT VTKm_ENABLE_BENCHMARKS" ON) mark_as_advanced(VTKm_ENABLE_TESTING_LIBRARY) +# The ANARI interop library uses a bit of code in vtkm_rendering, so this option +# currently requires vtkm_rendering to be built. Eventually this dependency +# should go away as vtkm_anari doesn't require applications to use anything from +# vtkm_rendering directly. +cmake_dependent_option(VTKm_ENABLE_ANARI "Enable ANARI interop support" + OFF "VTKm_ENABLE_RENDERING" OFF) + # We may want to make finer controls on whether libraries/modules get built. # VTK uses the concept of groups for its modules vtkm_option(VTKm_BUILD_ALL_LIBRARIES @@ -216,6 +223,7 @@ vtkm_module_force_group(Testing DISABLE_VALUE "DONT_WANT" ) vtkm_module_force_group(Benchmarking ENABLE_OPTION VTKm_ENABLE_BENCHMARKS) +vtkm_module_force_group(ANARI ENABLE_OPTION VTKm_ENABLE_ANARI) # The tutorial requires several common filters. This logic might need to # become more complicated (or less compliated if we decide to always diff --git a/data/baseline/interop/anari/glyphs.png b/data/baseline/interop/anari/glyphs.png new file mode 100644 index 000000000..5a8862a27 --- /dev/null +++ b/data/baseline/interop/anari/glyphs.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12f5095042fc2bd800d0a556e529103f8e008f10ec112ca0a1acb5b49b283dd7 +size 367720 diff --git a/data/baseline/interop/anari/isosurface.png b/data/baseline/interop/anari/isosurface.png new file mode 100644 index 000000000..58f5c732b --- /dev/null +++ b/data/baseline/interop/anari/isosurface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d1ff70038dc33f60ea4f002900b6137bc6efb60ca118472b1c4459cc3b67270 +size 28689 diff --git a/data/baseline/interop/anari/points.png b/data/baseline/interop/anari/points.png new file mode 100644 index 000000000..59d6358d6 --- /dev/null +++ b/data/baseline/interop/anari/points.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be65f08cf247683224eb8454fc7bfec846b9226fe0c823ac9740c8b06a3b6baf +size 23043 diff --git a/data/baseline/interop/anari/scene-empty-mappers.png b/data/baseline/interop/anari/scene-empty-mappers.png new file mode 100644 index 000000000..2a415ae67 --- /dev/null +++ b/data/baseline/interop/anari/scene-empty-mappers.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17f87d2e7ea22f0a336a782f89e02e2a3dabb778db236045f1e06dc3a4db433f +size 203 diff --git a/data/baseline/interop/anari/scene.png b/data/baseline/interop/anari/scene.png new file mode 100644 index 000000000..2179c03c1 --- /dev/null +++ b/data/baseline/interop/anari/scene.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:567eee6acf70a06c58d42afed6c1fa93761ae1a556846c5a9296ef9fd57f98f6 +size 306635 diff --git a/data/baseline/interop/anari/volume.png b/data/baseline/interop/anari/volume.png new file mode 100644 index 000000000..ff40cb7e9 --- /dev/null +++ b/data/baseline/interop/anari/volume.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69d625670cdbf38490b7ba2a72d18c5ac57387ca85d5e34d6008ba67d80395f7 +size 152115 diff --git a/vtkm/interop/anari/ANARIActor.cxx b/vtkm/interop/anari/ANARIActor.cxx new file mode 100644 index 000000000..19aa761e0 --- /dev/null +++ b/vtkm/interop/anari/ANARIActor.cxx @@ -0,0 +1,128 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +const char* AnariMaterialInputString(vtkm::IdComponent p) +{ + switch (p) + { + case 0: + default: + return "attribute0"; + case 1: + return "attribute1"; + case 2: + return "attribute2"; + case 3: + return "attribute3"; + } + + return "attribute0"; +} + +ANARIActor::ANARIActor(const vtkm::cont::UnknownCellSet& cells, + const vtkm::cont::CoordinateSystem& coordinates, + const vtkm::cont::Field& field0, + const vtkm::cont::Field& field1, + const vtkm::cont::Field& field2, + const vtkm::cont::Field& field3) +{ + this->Data->Cells = cells; + this->Data->Coordinates = coordinates; + this->Data->Fields[0] = field0; + this->Data->Fields[1] = field1; + this->Data->Fields[2] = field2; + this->Data->Fields[3] = field3; +} + +ANARIActor::ANARIActor(const vtkm::cont::UnknownCellSet& cells, + const vtkm::cont::CoordinateSystem& coordinates, + const FieldSet& f) + : ANARIActor(cells, coordinates, f[0], f[1], f[2], f[3]) +{ +} + +ANARIActor::ANARIActor(const vtkm::cont::DataSet& dataset, + const std::string& field0, + const std::string& field1, + const std::string& field2, + const std::string& field3) +{ + this->Data->Cells = dataset.GetCellSet(); + if (dataset.GetNumberOfCoordinateSystems() > 0) + this->Data->Coordinates = dataset.GetCoordinateSystem(); + this->Data->Fields[0] = field0.empty() ? vtkm::cont::Field{} : dataset.GetField(field0); + this->Data->Fields[1] = field1.empty() ? vtkm::cont::Field{} : dataset.GetField(field1); + this->Data->Fields[2] = field2.empty() ? vtkm::cont::Field{} : dataset.GetField(field2); + this->Data->Fields[3] = field3.empty() ? vtkm::cont::Field{} : dataset.GetField(field3); +} + +const vtkm::cont::UnknownCellSet& ANARIActor::GetCellSet() const +{ + return this->Data->Cells; +} + +const vtkm::cont::CoordinateSystem& ANARIActor::GetCoordinateSystem() const +{ + return this->Data->Coordinates; +} + +const vtkm::cont::Field& ANARIActor::GetField(vtkm::IdComponent idx) const +{ + return this->Data->Fields[idx < 0 ? GetPrimaryFieldIndex() : idx]; +} + +FieldSet ANARIActor::GetFieldSet() const +{ + return this->Data->Fields; +} + +void ANARIActor::SetPrimaryFieldIndex(vtkm::IdComponent idx) +{ + this->Data->PrimaryField = idx; +} + +vtkm::IdComponent ANARIActor::GetPrimaryFieldIndex() const +{ + return this->Data->PrimaryField; +} + +vtkm::cont::DataSet ANARIActor::MakeDataSet(bool includeFields) const +{ + vtkm::cont::DataSet dataset; + dataset.SetCellSet(GetCellSet()); + dataset.AddCoordinateSystem(GetCoordinateSystem()); + if (!includeFields) + return dataset; + + auto addField = [&](const vtkm::cont::Field& field) { + if (field.GetNumberOfValues() > 0) + dataset.AddField(field); + }; + + addField(this->Data->Fields[0]); + addField(this->Data->Fields[1]); + addField(this->Data->Fields[2]); + addField(this->Data->Fields[3]); + + return dataset; +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIActor.h b/vtkm/interop/anari/ANARIActor.h new file mode 100644 index 000000000..d0cf2f47f --- /dev/null +++ b/vtkm/interop/anari/ANARIActor.h @@ -0,0 +1,108 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIActor_h +#define vtk_m_interop_anari_ANARIActor_h + +// vtk-m +#include +#include +#include +#include +#include +// std +#include +#include + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// \brief Convenience type used to represent all the fields in an `ANARIActor`. +/// +using FieldSet = std::array; + +/// \brief Returns the appropriate ANARI attribute string based on field index. +/// +const char* AnariMaterialInputString(vtkm::IdComponent p); + +/// \brief Collects cells, coords, and 0-4 fields for ANARI mappers to consume. +/// +/// `ANARIActor` represents a selected set of cells, coordinates, and fields for +/// `ANARIMapper` based mappers to map onto ANARI objects. This class also +/// maintains which field is the "main" field, which almost always is the field +/// which is used to color the geometry or volume. +/// +/// Mappers creating geometry will generally add all fields as attribute arrays +/// if possible, letting applications use more than one field as material inputs +/// or data to be color mapped by samplers. +/// +struct VTKM_ANARI_EXPORT ANARIActor +{ + ANARIActor() = default; + + /// @brief Main constructor taking cells, coordinates, and up to 4 fields. + /// + ANARIActor(const vtkm::cont::UnknownCellSet& cells, + const vtkm::cont::CoordinateSystem& coordinates, + const vtkm::cont::Field& field0 = {}, + const vtkm::cont::Field& field1 = {}, + const vtkm::cont::Field& field2 = {}, + const vtkm::cont::Field& field3 = {}); + + /// @brief Convenience constructor when an entire FieldSet already exists. + /// + ANARIActor(const vtkm::cont::UnknownCellSet& cells, + const vtkm::cont::CoordinateSystem& coordinates, + const FieldSet& fieldset); + + /// @brief Convenience constructor using a dataset + named fields. + /// + ANARIActor(const vtkm::cont::DataSet& dataset, + const std::string& field0 = "", + const std::string& field1 = "", + const std::string& field2 = "", + const std::string& field3 = ""); + + const vtkm::cont::UnknownCellSet& GetCellSet() const; + const vtkm::cont::CoordinateSystem& GetCoordinateSystem() const; + const vtkm::cont::Field& GetField(vtkm::IdComponent idx = -1) const; + + FieldSet GetFieldSet() const; + + void SetPrimaryFieldIndex(vtkm::IdComponent idx); + vtkm::IdComponent GetPrimaryFieldIndex() const; + + /// @brief Utility to reconstitute a DataSet from the items in the actor. + /// + vtkm::cont::DataSet MakeDataSet(bool includeFields = false) const; + +private: + struct ActorData + { + vtkm::cont::UnknownCellSet Cells; + vtkm::cont::CoordinateSystem Coordinates; + FieldSet Fields; + vtkm::IdComponent PrimaryField{ 0 }; + }; + + std::shared_ptr Data = std::make_shared(); +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIMapper.cxx b/vtkm/interop/anari/ANARIMapper.cxx new file mode 100644 index 000000000..dcc9830b2 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapper.cxx @@ -0,0 +1,201 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +ANARIMapper::ANARIMapper(anari_cpp::Device device, + const ANARIActor& actor, + const std::string& name, + const vtkm::cont::ColorTable& colorTable) + : Actor(actor) + , ColorTable(colorTable) + , Name(name) +{ + this->Handles = std::make_shared(); + this->Handles->Device = device; + anari_cpp::retain(device, device); +} + +anari_cpp::Device ANARIMapper::GetDevice() const +{ + return this->Handles->Device; +} + +const ANARIActor& ANARIMapper::GetActor() const +{ + return this->Actor; +} + +const char* ANARIMapper::GetName() const +{ + return this->Name.c_str(); +} + +void ANARIMapper::SetActor(const ANARIActor& actor) +{ + this->Actor = actor; +} + +void ANARIMapper::SetMapFieldAsAttribute(bool enabled) +{ + this->MapFieldAsAttribute = enabled; +} + +bool ANARIMapper::GetMapFieldAsAttribute() const +{ + return this->MapFieldAsAttribute; +} + +const vtkm::cont::ColorTable& ANARIMapper::GetColorTable() const +{ + return this->ColorTable; +} + +void ANARIMapper::SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays) +{ + auto d = this->GetDevice(); + if (releaseArrays) + { + anari_cpp::release(d, color); + anari_cpp::release(d, opacity); + } +} + +void ANARIMapper::SetANARIColorMapValueRange(const vtkm::Vec2f_32&) +{ + // no-op +} + +void ANARIMapper::SetANARIColorMapOpacityScale(vtkm::Float32) +{ + // no-op +} + +void ANARIMapper::SetName(const char* name) +{ + this->Name = name; +} + +void ANARIMapper::SetColorTable(const vtkm::cont::ColorTable& colorTable) +{ + this->ColorTable = colorTable; +} + +anari_cpp::Geometry ANARIMapper::GetANARIGeometry() +{ + return nullptr; +} + +anari_cpp::SpatialField ANARIMapper::GetANARISpatialField() +{ + return nullptr; +} + +anari_cpp::Surface ANARIMapper::GetANARISurface() +{ + return nullptr; +} + +anari_cpp::Volume ANARIMapper::GetANARIVolume() +{ + return nullptr; +} + +anari_cpp::Group ANARIMapper::GetANARIGroup() +{ + if (!this->Handles->Group) + { + auto d = this->GetDevice(); + this->Handles->Group = anari_cpp::newObject(d); + this->RefreshGroup(); + } + + return this->Handles->Group; +} + +anari_cpp::Instance ANARIMapper::GetANARIInstance() +{ + if (!this->Handles->Instance) + { + auto d = this->GetDevice(); + this->Handles->Instance = anari_cpp::newObject(d, "transform"); + auto group = this->GetANARIGroup(); + anari_cpp::setParameter(d, this->Handles->Instance, "group", group); + anari_cpp::setParameter(d, this->Handles->Instance, "name", MakeObjectName("instance")); + anari_cpp::commitParameters(d, this->Handles->Instance); + } + + return this->Handles->Instance; +} + +bool ANARIMapper::GroupIsEmpty() const +{ + return !this->Valid; +} + +std::string ANARIMapper::MakeObjectName(const char* suffix) const +{ + std::string name = this->GetName(); + name += '.'; + name += suffix; + return name; +} + +void ANARIMapper::RefreshGroup() +{ + if (!this->Handles->Group) + return; + + auto d = this->GetDevice(); + + anari_cpp::unsetParameter(d, this->Handles->Group, "surface"); + anari_cpp::unsetParameter(d, this->Handles->Group, "volume"); + + auto surface = this->GetANARISurface(); + auto volume = this->GetANARIVolume(); + + if (!this->GroupIsEmpty()) + { + if (surface) + anari_cpp::setParameterArray1D(d, this->Handles->Group, "surface", &surface, 1); + + if (volume) + anari_cpp::setParameterArray1D(d, this->Handles->Group, "volume", &volume, 1); + + anari_cpp::setParameter(d, this->Handles->Group, "name", MakeObjectName("group")); + } + + anari_cpp::commitParameters(d, this->Handles->Group); +} + +vtkm::cont::ColorTable& ANARIMapper::GetColorTable() +{ + return this->ColorTable; +} + +ANARIMapper::ANARIHandles::~ANARIHandles() +{ + anari_cpp::release(this->Device, this->Group); + anari_cpp::release(this->Device, this->Instance); + anari_cpp::release(this->Device, this->Device); +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIMapper.h b/vtkm/interop/anari/ANARIMapper.h new file mode 100644 index 000000000..85df117de --- /dev/null +++ b/vtkm/interop/anari/ANARIMapper.h @@ -0,0 +1,139 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIMapper_h +#define vtk_m_interop_anari_ANARIMapper_h + +// vtk-m +#include +#include + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +inline void NoopANARIDeleter(const void*, const void*) {} + +/// @brief This is the base class used for all ANARI mappers. +/// +/// This class implements shared functionality of all ANARI mappers. All ANARI +/// object handle lifetimes are tied to the lifetime of the mapper, including +/// the device. Applications are not intended to ever release handles received +/// from the mapper, unless they manually retain the handle. Additionally, +/// ANARIMappers will update surface or volume objects if changes occur, such as +/// changes to the color map or ANARIActor. +struct VTKM_ANARI_EXPORT ANARIMapper +{ + ANARIMapper(anari_cpp::Device device, + const ANARIActor& actor = {}, + const std::string& name = "", + const vtkm::cont::ColorTable& colorTable = vtkm::cont::ColorTable::Preset::Default); + virtual ~ANARIMapper() = default; + + anari_cpp::Device GetDevice() const; + const ANARIActor& GetActor() const; + const char* GetName() const; + const vtkm::cont::ColorTable& GetColorTable() const; + + void SetName(const char* name); + void SetColorTable(const vtkm::cont::ColorTable& colorTable); + + /// @brief Set the current actor on this mapper. + /// + /// This sets the actor used to create the geometry. When the actor is changed + /// the mapper will update all the corresponding ANARI objects accordingly. + /// This will not cause new ANARI geometry handles to be made, rather the + /// existing handles will be updated to reflect the new actor's data. + virtual void SetActor(const ANARIActor& actor); + + /// @brief Set whether fields from `ANARIActor` should end up as geometry attributes. + /// + /// When this is disabled, the mapper will skip creating the data arrays + /// associated with fields for when applications only want the raw geometry. + /// This defaults to being enabled. + virtual void SetMapFieldAsAttribute(bool enabled); + bool GetMapFieldAsAttribute() const; + + /// @brief Set color map arrays using raw ANARI array handles. + /// @param color Color array used for color mapping. + /// @param opacity (unused/deprecated, will remove on future ANARI version) + /// @param releaseArrays If true this function will release the hanldes passed in. + virtual void SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays = true); + + /// @brief Set the value range (domain) for the color map. + /// + virtual void SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange); + + /// @brief Set a scale factor for opacity (typically used for volumes). + /// + virtual void SetANARIColorMapOpacityScale(vtkm::Float32 opacityScale); + + /// @brief Get the corresponding ANARIGeometry handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + virtual anari_cpp::Geometry GetANARIGeometry(); + + /// @brief Get the corresponding ANARISpatialField handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + virtual anari_cpp::SpatialField GetANARISpatialField(); + + /// @brief Get the corresponding ANARISurface handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + virtual anari_cpp::Surface GetANARISurface(); + + /// @brief Get the corresponding ANARIVolume handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + virtual anari_cpp::Volume GetANARIVolume(); + + anari_cpp::Group GetANARIGroup(); + anari_cpp::Instance GetANARIInstance(); + + bool GroupIsEmpty() const; + +protected: + std::string MakeObjectName(const char* suffix) const; + void RefreshGroup(); + + vtkm::cont::ColorTable& GetColorTable(); + + bool Valid{ false }; + bool Current{ false }; + +private: + struct ANARIHandles + { + anari_cpp::Device Device{ nullptr }; + anari_cpp::Group Group{ nullptr }; + anari_cpp::Instance Instance{ nullptr }; + ~ANARIHandles(); + }; + + std::shared_ptr Handles; + ANARIActor Actor; + vtkm::cont::ColorTable ColorTable; + std::string Name; + bool MapFieldAsAttribute{ true }; +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIMapperGlyphs.cxx b/vtkm/interop/anari/ANARIMapperGlyphs.cxx new file mode 100644 index 000000000..136dcec0c --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperGlyphs.cxx @@ -0,0 +1,283 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtk-m +#include "vtkm/rendering/raytracing/SphereExtractor.h" +#include +#include +#include +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +// Worklets /////////////////////////////////////////////////////////////////// + +class GeneratePointGlyphs : public vtkm::worklet::WorkletMapField +{ +public: + vtkm::Float32 SizeFactor{ 0.f }; + bool Offset{ false }; + + VTKM_CONT + GeneratePointGlyphs(float size = 1.f, bool offset = false) + : SizeFactor(size) + , Offset(offset) + { + } + + using ControlSignature = void(FieldIn, WholeArrayIn, WholeArrayOut, WholeArrayOut); + using ExecutionSignature = void(InputIndex, _1, _2, _3, _4); + + template + VTKM_EXEC void operator()(const vtkm::Id idx, + const InGradientType gradient, + const InPointPortalType& points, + OutVertexPortalType& vertices, + OutRadiusPortalType& radii) const + { + auto ng = vtkm::Normal(static_cast(gradient)); + auto pt = points.Get(idx); + auto v0 = pt + ng * this->SizeFactor; + auto v1 = pt + ng * -this->SizeFactor; + if (this->Offset) + { + vertices.Set(4 * idx + 0, pt); + vertices.Set(4 * idx + 1, v1); + vertices.Set(4 * idx + 2, v1); + vertices.Set(4 * idx + 3, v1 - (this->SizeFactor * ng)); + } + else + { + vertices.Set(4 * idx + 0, v0); + vertices.Set(4 * idx + 1, pt); + vertices.Set(4 * idx + 2, pt); + vertices.Set(4 * idx + 3, v1); + } + radii.Set(4 * idx + 0, this->SizeFactor / 8); + radii.Set(4 * idx + 1, this->SizeFactor / 8); + radii.Set(4 * idx + 2, this->SizeFactor / 4); + radii.Set(4 * idx + 3, 0.f); + } +}; + +// Helper functions /////////////////////////////////////////////////////////// + +static GlyphArrays MakeGlyphs(vtkm::cont::Field gradients, + vtkm::cont::UnknownCellSet cells, + vtkm::cont::CoordinateSystem coords, + float glyphSize, + bool offset) +{ + const auto numGlyphs = gradients.GetNumberOfValues(); + + GlyphArrays retval; + + retval.Vertices.Allocate(numGlyphs * 4); + retval.Radii.Allocate(numGlyphs * 4); + + GeneratePointGlyphs worklet(glyphSize, offset); + vtkm::worklet::DispatcherMapField dispatch(worklet); + + if (gradients.IsPointField()) + dispatch.Invoke(gradients, coords, retval.Vertices, retval.Radii); + else + { + vtkm::cont::DataSet centersInput; + centersInput.AddCoordinateSystem(coords); + centersInput.SetCellSet(cells); + + vtkm::filter::field_conversion::CellAverage filter; + filter.SetUseCoordinateSystemAsField(true); + filter.SetOutputFieldName("Centers"); + auto centersOutput = filter.Execute(centersInput); + + auto resolveField = [&](const auto& concreteField) { + dispatch.Invoke(gradients, concreteField, retval.Vertices, retval.Radii); + }; + centersOutput.GetField("Centers") + .GetData() + .CastAndCallForTypesWithFloatFallback>(resolveField); + } + + return retval; +} + +// ANARIMapperGlyphs definitions ////////////////////////////////////////////// + +ANARIMapperGlyphs::ANARIMapperGlyphs(anari_cpp::Device device, + const ANARIActor& actor, + const char* name, + const vtkm::cont::ColorTable& colorTable) + : ANARIMapper(device, actor, name, colorTable) +{ + this->Handles = std::make_shared(); + this->Handles->Device = device; + anari_cpp::retain(device, device); +} + +ANARIMapperGlyphs::~ANARIMapperGlyphs() +{ + // ensure ANARI handles are released before host memory goes away + this->Handles.reset(); +} + +void ANARIMapperGlyphs::SetActor(const ANARIActor& actor) +{ + this->ANARIMapper::SetActor(actor); + this->ConstructArrays(true); +} + +void ANARIMapperGlyphs::SetOffsetGlyphs(bool enabled) +{ + this->Offset = enabled; +} + +anari_cpp::Geometry ANARIMapperGlyphs::GetANARIGeometry() +{ + if (this->Handles->Geometry) + return this->Handles->Geometry; + + auto d = this->GetDevice(); + this->Handles->Geometry = anari_cpp::newObject(d, "cone"); + this->ConstructArrays(); + this->UpdateGeometry(); + return this->Handles->Geometry; +} + +anari_cpp::Surface ANARIMapperGlyphs::GetANARISurface() +{ + if (this->Handles->Surface) + return this->Handles->Surface; + + auto d = this->GetDevice(); + + if (!this->Handles->Material) + { + this->Handles->Material = anari_cpp::newObject(d, "matte"); + anari_cpp::setParameter(d, this->Handles->Material, "name", this->MakeObjectName("material")); + } + + anari_cpp::commitParameters(d, this->Handles->Material); + + this->Handles->Surface = anari_cpp::newObject(d); + anari_cpp::setParameter(d, this->Handles->Surface, "name", this->MakeObjectName("surface")); + anari_cpp::setParameter(d, this->Handles->Surface, "geometry", this->GetANARIGeometry()); + anari_cpp::setParameter(d, this->Handles->Surface, "material", this->Handles->Material); + anari_cpp::commitParameters(d, this->Handles->Surface); + return this->Handles->Surface; +} + +void ANARIMapperGlyphs::ConstructArrays(bool regenerate) +{ + if (regenerate) + this->Current = false; + + if (this->Current) + return; + + this->Current = true; + this->Valid = false; + + this->Handles->ReleaseArrays(); + + const auto& actor = this->GetActor(); + const auto& coords = actor.GetCoordinateSystem(); + const auto& cells = actor.GetCellSet(); + const auto& field = actor.GetField(); + + auto numGlyphs = field.GetNumberOfValues(); + + if (numGlyphs == 0) + { + this->RefreshGroup(); + return; + } + + vtkm::Bounds coordBounds = coords.GetBounds(); + vtkm::Float64 lx = coordBounds.X.Length(); + vtkm::Float64 ly = coordBounds.Y.Length(); + vtkm::Float64 lz = coordBounds.Z.Length(); + vtkm::Float64 mag = vtkm::Sqrt(lx * lx + ly * ly + lz * lz); + constexpr vtkm::Float64 heuristic = 300.; + auto glyphSize = static_cast(mag / heuristic); + + auto arrays = MakeGlyphs(field, cells, coords, glyphSize, Offset); + + auto* v = (vtkm::Vec3f_32*)arrays.Vertices.GetBuffers()[0].ReadPointerHost(*arrays.Token); + auto* r = (float*)arrays.Radii.GetBuffers()[0].ReadPointerHost(*arrays.Token); + + auto d = this->GetDevice(); + this->Handles->Parameters.Vertex.Position = + anari_cpp::newArray1D(d, v, NoopANARIDeleter, nullptr, arrays.Vertices.GetNumberOfValues()); + this->Handles->Parameters.Vertex.Radius = + anari_cpp::newArray1D(d, r, NoopANARIDeleter, nullptr, arrays.Radii.GetNumberOfValues()); + this->Handles->Parameters.NumPrimitives = numGlyphs; + + this->UpdateGeometry(); + + this->Arrays = arrays; + this->Valid = true; + + this->RefreshGroup(); +} + +void ANARIMapperGlyphs::UpdateGeometry() +{ + if (!this->Handles->Geometry) + return; + + auto d = this->GetDevice(); + + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.position"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.radius"); + + anari_cpp::setParameter(d, this->Handles->Geometry, "name", this->MakeObjectName("geometry")); + + if (this->Handles->Parameters.Vertex.Position) + { + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.position", this->Handles->Parameters.Vertex.Position); + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.radius", this->Handles->Parameters.Vertex.Radius); + anari_cpp::setParameter(d, this->Handles->Geometry, "caps", "both"); + } + + anari_cpp::commitParameters(d, this->Handles->Geometry); +} + +ANARIMapperGlyphs::ANARIHandles::~ANARIHandles() +{ + this->ReleaseArrays(); + anari_cpp::release(this->Device, this->Surface); + anari_cpp::release(this->Device, this->Material); + anari_cpp::release(this->Device, this->Geometry); + anari_cpp::release(this->Device, this->Device); +} + +void ANARIMapperGlyphs::ANARIHandles::ReleaseArrays() +{ + anari_cpp::release(this->Device, this->Parameters.Vertex.Position); + anari_cpp::release(this->Device, this->Parameters.Vertex.Radius); + this->Parameters.Vertex.Position = nullptr; + this->Parameters.Vertex.Radius = nullptr; +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIMapperGlyphs.h b/vtkm/interop/anari/ANARIMapperGlyphs.h new file mode 100644 index 000000000..67c88f49d --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperGlyphs.h @@ -0,0 +1,118 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIMapperGlyphs_h +#define vtk_m_interop_anari_ANARIMapperGlyphs_h + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// @brief Raw ANARI arrays and parameter values set on the `ANARIGeometry`. +/// +struct GlyphsParameters +{ + struct VertexData + { + anari_cpp::Array1D Position{ nullptr }; + anari_cpp::Array1D Radius{ nullptr }; + } Vertex{}; + + unsigned int NumPrimitives{ 0 }; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper. +/// +struct GlyphArrays +{ + vtkm::cont::ArrayHandle Vertices; + vtkm::cont::ArrayHandle Radii; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief Mapper which turns vector data into arrow glyphs. +/// +/// This mapper creates ANARI `cone` geometry for the primary field in the +/// provided `ANARIActor`. +struct VTKM_ANARI_EXPORT ANARIMapperGlyphs : public ANARIMapper +{ + /// @brief Constructor + /// + ANARIMapperGlyphs( + anari_cpp::Device device, + const ANARIActor& actor = {}, + const char* name = "", + const vtkm::cont::ColorTable& colorTable = vtkm::cont::ColorTable::Preset::Default); + + /// @brief Destructor + /// + ~ANARIMapperGlyphs() override; + + /// @brief Set the current actor on this mapper. + /// + /// This sets the actor used to create the geometry. When the actor is changed + /// the mapper will update all the corresponding ANARI objects accordingly. + /// This will not cause new ANARI geometry handles to be made, rather the + /// existing handles will be updated to reflect the new actor's data. + void SetActor(const ANARIActor& actor) override; + + /// @brief Offset the glyph in the direction of the vector at each point. + /// + /// This will cause the mapper to offset the glyph, making the arrow appear to + /// be coming out of the point instead of going through it. This is useful for + /// visualizing things like surface normals on a mesh. + void SetOffsetGlyphs(bool enabled); + + /// @brief Get the corresponding ANARIGeometry handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + anari_cpp::Geometry GetANARIGeometry() override; + + /// @brief Get the corresponding ANARISurface handle from this mapper. + /// + /// NOTE: This handle is not retained, so applications should not release it. + anari_cpp::Surface GetANARISurface() override; + +private: + /// @brief Do the work to construct the basic ANARI arrays for the ANARIGeometry. + /// @param regenerate Force the position/radius arrays are regenerated. + /// + void ConstructArrays(bool regenerate = false); + /// @brief Update ANARIGeometry object with the latest data from the actor. + void UpdateGeometry(); + + /// @brief Container of all relevant ANARI scene object handles. + struct ANARIHandles + { + anari_cpp::Device Device{ nullptr }; + anari_cpp::Geometry Geometry{ nullptr }; + anari_cpp::Material Material{ nullptr }; + anari_cpp::Surface Surface{ nullptr }; + GlyphsParameters Parameters; + ~ANARIHandles(); + void ReleaseArrays(); + }; + + std::shared_ptr Handles; + + bool Offset{ false }; + GlyphArrays Arrays; +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIMapperPoints.cxx b/vtkm/interop/anari/ANARIMapperPoints.cxx new file mode 100644 index 000000000..a126f8865 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperPoints.cxx @@ -0,0 +1,407 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtk-m +#include +#include +#include +#include +// anari +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +// Worklets /////////////////////////////////////////////////////////////////// + +class ExtractPointPositions : public vtkm::worklet::WorkletMapField +{ +public: + VTKM_CONT + ExtractPointPositions() = default; + + using ControlSignature = void(FieldIn, // [in] index + WholeArrayIn, // [in] point + WholeArrayOut // [out] point + ); + using ExecutionSignature = void(InputIndex, + _1, // [in] index + _2, // [in] point + _3 // [out] points + ); + + template + VTKM_EXEC void operator()(const vtkm::Id out_idx, + const vtkm::Id in_idx, + const InPointPortalType& points, + OutPointPortalType& outP) const + { + outP.Set(out_idx, static_cast(points.Get(in_idx))); + } +}; + +// Helper functions /////////////////////////////////////////////////////////// + +static PointsFieldArrays UnpackFields(FieldSet fields) +{ + PointsFieldArrays retval; + + using AttributeHandleT = decltype(retval.Field1); + + auto makeFieldArray = [](auto field, auto& numComps) -> AttributeHandleT { + if (field.GetNumberOfValues() == 0) + return {}; + + auto fieldData = field.GetData(); + numComps = fieldData.GetNumberOfComponentsFlat(); + if (numComps >= 1 && numComps <= 4) + { + vtkm::cont::ArrayHandleRuntimeVec outData(numComps); + vtkm::cont::ArrayCopyShallowIfPossible(fieldData, outData); + return outData; + } + + return {}; + }; + + retval.Field1 = makeFieldArray(fields[0], retval.NumberOfField1Components); + retval.Field2 = makeFieldArray(fields[1], retval.NumberOfField2Components); + retval.Field3 = makeFieldArray(fields[2], retval.NumberOfField3Components); + retval.Field4 = makeFieldArray(fields[3], retval.NumberOfField4Components); + + return retval; +} + +static PointsArrays UnpackPoints(vtkm::cont::ArrayHandle points, + vtkm::cont::CoordinateSystem coords) +{ + PointsArrays retval; + + const auto numPoints = points.GetNumberOfValues(); + retval.Vertices.Allocate(numPoints); + vtkm::worklet::DispatcherMapField().Invoke( + points, coords, retval.Vertices); + + return retval; +} + +// ANARIMapperPoints definitions ////////////////////////////////////////////// + +ANARIMapperPoints::ANARIMapperPoints(anari_cpp::Device device, + const ANARIActor& actor, + const std::string& name, + const vtkm::cont::ColorTable& colorTable) + : ANARIMapper(device, actor, name, colorTable) +{ + this->Handles = std::make_shared(); + this->Handles->Device = device; + auto& attributes = this->Handles->Parameters.Vertex.Attribute; + std::fill(attributes.begin(), attributes.end(), nullptr); + anari_cpp::retain(device, device); +} + +ANARIMapperPoints::~ANARIMapperPoints() +{ + // ensure ANARI handles are released before host memory goes away + this->Handles.reset(); +} + +void ANARIMapperPoints::SetActor(const ANARIActor& actor) +{ + this->ANARIMapper::SetActor(actor); + this->ConstructArrays(true); + this->UpdateMaterial(); +} + +void ANARIMapperPoints::SetMapFieldAsAttribute(bool enabled) +{ + this->ANARIMapper::SetMapFieldAsAttribute(enabled); + this->UpdateGeometry(); + this->UpdateMaterial(); +} + +void ANARIMapperPoints::SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays) +{ + this->GetANARISurface(); + auto s = this->Handles->Sampler; + if (s) + { + auto d = this->GetDevice(); + anari_cpp::setParameter(d, s, "image", color); + anari_cpp::commitParameters(d, s); + } + this->ANARIMapper::SetANARIColorMap(color, opacity, releaseArrays); +} + +void ANARIMapperPoints::SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) +{ + this->GetANARISurface(); + auto s = this->Handles->Sampler; + if (s) + { + auto d = this->GetDevice(); + auto scale = + anari_cpp::scaling_matrix(anari_cpp::float3(1.f / (valueRange[1] - valueRange[0]))); + auto translation = anari_cpp::translation_matrix(anari_cpp::float3(-valueRange[0], 0, 0)); + anari_cpp::setParameter(d, s, "inTransform", anari_cpp::mul(scale, translation)); + anari_cpp::commitParameters(d, s); + } +} + +anari_cpp::Geometry ANARIMapperPoints::GetANARIGeometry() +{ + if (this->Handles->Geometry) + return this->Handles->Geometry; + + auto d = this->GetDevice(); + this->Handles->Geometry = anari_cpp::newObject(d, "sphere"); + this->ConstructArrays(); + this->UpdateGeometry(); + + return this->Handles->Geometry; +} + +anari_cpp::Surface ANARIMapperPoints::GetANARISurface() +{ + if (this->Handles->Surface) + return this->Handles->Surface; + + auto d = this->GetDevice(); + + this->Handles->Surface = anari_cpp::newObject(d); + + if (!this->Handles->Material) + { + this->Handles->Material = anari_cpp::newObject(d, "matte"); + anari_cpp::setParameter(d, this->Handles->Material, "name", this->MakeObjectName("material")); + } + + auto s = anari_cpp::newObject(d, "image1D"); + this->Handles->Sampler = s; + auto colorArray = anari_cpp::newArray1D(d, ANARI_FLOAT32_VEC4, 3); + auto* colors = anari_cpp::map(d, colorArray); + colors[0] = vtkm::Vec4f_32(1.f, 0.f, 0.f, 0.f); + colors[1] = vtkm::Vec4f_32(0.f, 1.f, 0.f, 0.5f); + colors[2] = vtkm::Vec4f_32(0.f, 0.f, 1.f, 1.f); + anari_cpp::unmap(d, colorArray); + anari_cpp::setAndReleaseParameter(d, s, "image", colorArray); + anari_cpp::setParameter(d, s, "filter", "linear"); + anari_cpp::setParameter(d, s, "wrapMode", "clampToEdge"); + anari_cpp::setParameter(d, s, "name", this->MakeObjectName("colormap")); + anari_cpp::commitParameters(d, s); + + this->SetANARIColorMapValueRange(vtkm::Vec2f_32(0.f, 10.f)); + + this->UpdateMaterial(); + + anari_cpp::setParameter(d, this->Handles->Surface, "name", this->MakeObjectName("surface")); + anari_cpp::setParameter(d, this->Handles->Surface, "geometry", this->GetANARIGeometry()); + anari_cpp::setParameter(d, this->Handles->Surface, "material", this->Handles->Material); + anari_cpp::commitParameters(d, this->Handles->Surface); + + return this->Handles->Surface; +} + +void ANARIMapperPoints::ConstructArrays(bool regenerate) +{ + if (regenerate) + this->Current = false; + + if (this->Current) + return; + + this->Current = true; + this->Valid = false; + + this->Handles->ReleaseArrays(); + + const auto& actor = this->GetActor(); + const auto& coords = actor.GetCoordinateSystem(); + + if (coords.GetNumberOfPoints() == 0) + { + this->RefreshGroup(); + return; + } + + vtkm::Bounds coordBounds = coords.GetBounds(); + // set a default radius + vtkm::Float64 lx = coordBounds.X.Length(); + vtkm::Float64 ly = coordBounds.Y.Length(); + vtkm::Float64 lz = coordBounds.Z.Length(); + vtkm::Float64 mag = vtkm::Sqrt(lx * lx + ly * ly + lz * lz); + // same as used in vtk ospray + constexpr vtkm::Float64 heuristic = 500.; + auto baseRadius = static_cast(mag / heuristic); + + vtkm::rendering::raytracing::SphereExtractor sphereExtractor; + + sphereExtractor.ExtractCoordinates(coords, baseRadius); + + auto numPoints = sphereExtractor.GetNumberOfSpheres(); + this->Handles->Parameters.NumPrimitives = static_cast(numPoints); + + if (numPoints == 0) + { + this->RefreshGroup(); + return; + } + + this->PrimaryField = actor.GetPrimaryFieldIndex(); + + auto pts = sphereExtractor.GetPointIds(); + + auto arrays = UnpackPoints(pts, coords); + auto fieldArrays = UnpackFields(actor.GetFieldSet()); + + arrays.Radii = sphereExtractor.GetRadii(); + auto* p = (vtkm::Vec3f_32*)arrays.Vertices.GetBuffers()[0].ReadPointerHost(*arrays.Token); + auto* r = (float*)arrays.Radii.GetBuffers()[0].ReadPointerHost(*arrays.Token); + + auto d = this->GetDevice(); + this->Handles->Parameters.Vertex.Position = + anari_cpp::newArray1D(d, p, NoopANARIDeleter, nullptr, numPoints); + this->Handles->Parameters.Vertex.Radius = + anari_cpp::newArray1D(d, r, NoopANARIDeleter, nullptr, numPoints); + + auto createANARIArray = [](auto device, auto fieldArray, auto& token) -> anari_cpp::Array1D { + const auto nv = fieldArray.GetNumberOfValues(); + if (nv == 0) + return nullptr; + + anari_cpp::DataType type = ANARI_FLOAT32 + fieldArray.GetNumberOfComponents() - 1; + const auto& buffers = fieldArray.GetBuffers(); + auto* a = buffers[0].ReadPointerHost(*token); + if (!a && buffers.size() > 1) + a = buffers[1].ReadPointerHost(*token); + + return a ? anariNewArray1D(device, a, NoopANARIDeleter, nullptr, type, nv) : nullptr; + }; + + this->Handles->Parameters.Vertex.Attribute[0] = + createANARIArray(d, fieldArrays.Field1, fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[1] = + createANARIArray(d, fieldArrays.Field2, fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[2] = + createANARIArray(d, fieldArrays.Field3, fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[3] = + createANARIArray(d, fieldArrays.Field4, fieldArrays.Token); + + this->UpdateGeometry(); + this->UpdateMaterial(); + + this->Arrays = arrays; + this->FieldArrays = fieldArrays; + this->Valid = true; + + this->RefreshGroup(); +} + +void ANARIMapperPoints::UpdateGeometry() +{ + if (!this->Handles->Geometry) + return; + + auto d = this->GetDevice(); + + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.position"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.radius"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute0"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute1"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute2"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute3"); + + anari_cpp::setParameter(d, this->Handles->Geometry, "name", this->MakeObjectName("geometry")); + + if (this->Handles->Parameters.Vertex.Position) + { + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.position", this->Handles->Parameters.Vertex.Position); + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.radius", this->Handles->Parameters.Vertex.Radius); + if (this->GetMapFieldAsAttribute()) + { + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute0", + this->Handles->Parameters.Vertex.Attribute[0]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute1", + this->Handles->Parameters.Vertex.Attribute[1]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute2", + this->Handles->Parameters.Vertex.Attribute[2]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute3", + this->Handles->Parameters.Vertex.Attribute[3]); + } + } + + anari_cpp::commitParameters(d, this->Handles->Geometry); +} + +void ANARIMapperPoints::UpdateMaterial() +{ + if (!this->Handles->Material) + return; + + auto d = this->GetDevice(); + auto s = this->Handles->Sampler; + auto a = this->Handles->Parameters.Vertex.Attribute[PrimaryField]; + if (s && a && this->GetMapFieldAsAttribute()) + { + anari_cpp::setParameter(d, s, "inAttribute", AnariMaterialInputString(PrimaryField)); + anari_cpp::commitParameters(d, s); + anari_cpp::setParameter(d, this->Handles->Material, "color", s); + } + else + anari_cpp::setParameter(d, this->Handles->Material, "color", vtkm::Vec3f_32(1.f)); + + anari_cpp::commitParameters(d, this->Handles->Material); +} + +ANARIMapperPoints::ANARIHandles::~ANARIHandles() +{ + this->ReleaseArrays(); + anari_cpp::release(this->Device, this->Surface); + anari_cpp::release(this->Device, this->Material); + anari_cpp::release(this->Device, this->Sampler); + anari_cpp::release(this->Device, this->Geometry); + anari_cpp::release(this->Device, this->Device); +} + +void ANARIMapperPoints::ANARIHandles::ReleaseArrays() +{ + anari_cpp::release(this->Device, this->Parameters.Vertex.Position); + anari_cpp::release(this->Device, this->Parameters.Vertex.Radius); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[0]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[1]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[2]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[3]); + this->Parameters.Vertex.Position = nullptr; + this->Parameters.Vertex.Radius = nullptr; + this->Parameters.Vertex.Attribute[0] = nullptr; + this->Parameters.Vertex.Attribute[1] = nullptr; + this->Parameters.Vertex.Attribute[2] = nullptr; + this->Parameters.Vertex.Attribute[3] = nullptr; +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIMapperPoints.h b/vtkm/interop/anari/ANARIMapperPoints.h new file mode 100644 index 000000000..d979ab939 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperPoints.h @@ -0,0 +1,137 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIMapperPoints_h +#define vtk_m_interop_anari_ANARIMapperPoints_h + +#include +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// @brief Raw ANARI arrays and parameter values set on the `ANARIGeometry`. +/// +struct PointsParameters +{ + struct VertexData + { + anari_cpp::Array1D Position{ nullptr }; + anari_cpp::Array1D Radius{ nullptr }; + std::array Attribute; + } Vertex{}; + + unsigned int NumPrimitives{ 0 }; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper. +/// +struct PointsArrays +{ + vtkm::cont::ArrayHandle Vertices; + vtkm::cont::ArrayHandle Radii; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper for field attributes. +/// +struct PointsFieldArrays +{ + vtkm::cont::ArrayHandleRuntimeVec Field1; + int NumberOfField1Components{ 1 }; + vtkm::cont::ArrayHandleRuntimeVec Field2; + int NumberOfField2Components{ 1 }; + vtkm::cont::ArrayHandleRuntimeVec Field3; + int NumberOfField3Components{ 1 }; + vtkm::cont::ArrayHandleRuntimeVec Field4; + int NumberOfField4Components{ 1 }; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief Mapper which turns each point into ANARI `sphere` geometry. +/// +/// NOTE: This mapper will color map values that are 1/2/3/4 component Float32 +/// fields, otherwise they will be ignored. +struct VTKM_ANARI_EXPORT ANARIMapperPoints : public ANARIMapper +{ + /// @brief Constructor + /// + ANARIMapperPoints( + anari_cpp::Device device, + const ANARIActor& actor = {}, + const std::string& name = "", + const vtkm::cont::ColorTable& colorTable = vtkm::cont::ColorTable::Preset::Default); + + /// @brief Destructor + /// + ~ANARIMapperPoints() override; + + /// @brief Set the current actor on this mapper. + /// + /// See `ANARIMapper` for more detail. + void SetActor(const ANARIActor& actor) override; + + /// @brief Set whether fields from `ANARIActor` should end up as geometry attributes. + /// + /// See `ANARIMapper` for more detail. + void SetMapFieldAsAttribute(bool enabled) override; + + /// @brief Set color map arrays using raw ANARI array handles. + /// + /// See `ANARIMapper` for more detail. + void SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays = true) override; + + /// @brief Set the value range (domain) for the color map. + /// + void SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) override; + + anari_cpp::Geometry GetANARIGeometry() override; + anari_cpp::Surface GetANARISurface() override; + +private: + /// @brief Do the work to construct the basic ANARI arrays for the ANARIGeometry. + /// @param regenerate Force the position/radius arrays are regenerated. + /// + void ConstructArrays(bool regenerate = false); + /// @brief Update ANARIGeometry object with the latest data from the actor. + void UpdateGeometry(); + /// @brief Update ANARIMaterial object with the latest data from the actor. + void UpdateMaterial(); + + /// @brief Container of all relevant ANARI scene object handles. + struct ANARIHandles + { + anari_cpp::Device Device{ nullptr }; + anari_cpp::Geometry Geometry{ nullptr }; + anari_cpp::Sampler Sampler{ nullptr }; + anari_cpp::Material Material{ nullptr }; + anari_cpp::Surface Surface{ nullptr }; + PointsParameters Parameters; + ~ANARIHandles(); + void ReleaseArrays(); + }; + + std::shared_ptr Handles; + vtkm::IdComponent PrimaryField{ 0 }; + PointsArrays Arrays; + PointsFieldArrays FieldArrays; +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIMapperTriangles.cxx b/vtkm/interop/anari/ANARIMapperTriangles.cxx new file mode 100644 index 000000000..55fe77e96 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperTriangles.cxx @@ -0,0 +1,611 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtk-m +#include +#include +#include +#include +#include +// std +#include +// anari +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +// Worklets /////////////////////////////////////////////////////////////////// + +class ExtractTriangleFields : public vtkm::worklet::WorkletMapField +{ +public: + bool PopulateField1{ false }; + bool PopulateField2{ false }; + bool PopulateField3{ false }; + bool PopulateField4{ false }; + vtkm::Range Field1Range; + vtkm::Range Field2Range; + vtkm::Range Field3Range; + vtkm::Range Field4Range; + + VTKM_CONT + ExtractTriangleFields(bool emptyField1, + bool emptyField2, + bool emptyField3, + bool emptyField4, + vtkm::Range field1Range, + vtkm::Range field2Range, + vtkm::Range field3Range, + vtkm::Range field4Range) + : PopulateField1(!emptyField1) + , PopulateField2(!emptyField2) + , PopulateField3(!emptyField3) + , PopulateField4(!emptyField4) + , Field1Range(field1Range) + , Field2Range(field2Range) + , Field3Range(field3Range) + , Field4Range(field4Range) + { + } + + using ControlSignature = void(FieldIn, + WholeArrayIn, // [in] field1 + WholeArrayIn, // [in] field2 + WholeArrayIn, // [in] field3 + WholeArrayIn, // [in] field4 + WholeArrayOut, // [out] field1 + WholeArrayOut, // [out] field2 + WholeArrayOut, // [out] field3 + WholeArrayOut // [out] field4 + ); + using ExecutionSignature = void(InputIndex, + _1, // [in] indices + _2, // [in] field1 + _3, // [in] field2 + _4, // [in] field3 + _5, // [in] field4 + _6, // [out] field1 + _7, // [out] field2 + _8, // [out] field3 + _9 // [out] field4 + ); + + template + VTKM_EXEC void operator()(const vtkm::Id idx, + const vtkm::Id4 indices, + const FieldPortalType& field1, + const FieldPortalType& field2, + const FieldPortalType& field3, + const FieldPortalType& field4, + OutFieldPortalType& outF1, + OutFieldPortalType& outF2, + OutFieldPortalType& outF3, + OutFieldPortalType& outF4) const + { + const auto i0 = indices[1]; + const auto i1 = indices[2]; + const auto i2 = indices[3]; + if (this->PopulateField1) + { + outF1.Set(3 * idx + 0, static_cast(field1.Get(i0))); + outF1.Set(3 * idx + 1, static_cast(field1.Get(i1))); + outF1.Set(3 * idx + 2, static_cast(field1.Get(i2))); + } + if (this->PopulateField2) + { + outF2.Set(3 * idx + 0, static_cast(field2.Get(i0))); + outF2.Set(3 * idx + 1, static_cast(field2.Get(i1))); + outF2.Set(3 * idx + 2, static_cast(field2.Get(i2))); + } + if (this->PopulateField3) + { + outF3.Set(3 * idx + 0, static_cast(field3.Get(i0))); + outF3.Set(3 * idx + 1, static_cast(field3.Get(i1))); + outF3.Set(3 * idx + 2, static_cast(field3.Get(i2))); + } + if (this->PopulateField4) + { + outF4.Set(3 * idx + 0, static_cast(field4.Get(i0))); + outF4.Set(3 * idx + 1, static_cast(field4.Get(i1))); + outF4.Set(3 * idx + 2, static_cast(field4.Get(i2))); + } + } +}; + +class ExtractTriangleVerticesAndNormals : public vtkm::worklet::WorkletMapField +{ +public: + bool ExtractNormals{ false }; + + VTKM_CONT + ExtractTriangleVerticesAndNormals(bool withNormals) + : ExtractNormals(withNormals) + { + } + + using ControlSignature = void(FieldIn, + WholeArrayIn, // [in] points + WholeArrayIn, // [in] normals + WholeArrayOut, // [out] points + WholeArrayOut // [out] normals + ); + using ExecutionSignature = void(InputIndex, + _1, // [in] indices + _2, // [in] points + _3, // [in] normals + _4, // [out] points + _5 // [out] normals + ); + + template + VTKM_EXEC void operator()(const vtkm::Id idx, + const vtkm::Id4 indices, + const PointPortalType& points, + const NormalPortalType& normals, + OutPointsPortalType& outP, + OutNormalsPortalType& outN) const + { + const auto i0 = indices[1]; + const auto i1 = indices[2]; + const auto i2 = indices[3]; + outP.Set(3 * idx + 0, static_cast(points.Get(i0))); + outP.Set(3 * idx + 1, static_cast(points.Get(i1))); + outP.Set(3 * idx + 2, static_cast(points.Get(i2))); + if (this->ExtractNormals) + { + outN.Set(3 * idx + 0, static_cast(normals.Get(i0))); + outN.Set(3 * idx + 1, static_cast(normals.Get(i1))); + outN.Set(3 * idx + 2, static_cast(normals.Get(i2))); + } + } +}; + +// Helper functions /////////////////////////////////////////////////////////// + +static TriangleFieldArrays UnpackFields(vtkm::cont::ArrayHandle tris, + FieldSet fields, + vtkm::Range range) +{ + TriangleFieldArrays retval; + + const auto numTris = tris.GetNumberOfValues(); + + using AttributeHandleT = decltype(retval.Field1); + + auto isFieldEmpty = [](const vtkm::cont::Field& f) -> bool { + return f.GetNumberOfValues() == 0 || f.GetData().GetNumberOfComponentsFlat() != 1 || + !f.GetData().CanConvert(); + }; + + const bool emptyField1 = isFieldEmpty(fields[0]); + const bool emptyField2 = isFieldEmpty(fields[1]); + const bool emptyField3 = isFieldEmpty(fields[2]); + const bool emptyField4 = isFieldEmpty(fields[3]); + + auto field1 = + emptyField1 ? AttributeHandleT{} : fields[0].GetData().AsArrayHandle(); + auto field2 = + emptyField2 ? AttributeHandleT{} : fields[1].GetData().AsArrayHandle(); + auto field3 = + emptyField3 ? AttributeHandleT{} : fields[2].GetData().AsArrayHandle(); + auto field4 = + emptyField4 ? AttributeHandleT{} : fields[3].GetData().AsArrayHandle(); + + vtkm::Range field1Range = range; + vtkm::Range field2Range = range; + vtkm::Range field3Range = range; + vtkm::Range field4Range = range; + + if (!emptyField1) + { + retval.Field1.Allocate(numTris * 3); + } + if (!emptyField2) + { + retval.Field2.Allocate(numTris * 3); + } + if (!emptyField3) + { + retval.Field3.Allocate(numTris * 3); + } + if (!emptyField4) + { + retval.Field4.Allocate(numTris * 3); + } + + ExtractTriangleFields fieldsWorklet(emptyField1, + emptyField2, + emptyField3, + emptyField4, + field1Range, + field2Range, + field3Range, + field4Range); + vtkm::worklet::DispatcherMapField(fieldsWorklet) + .Invoke(tris, + field1, + field2, + field3, + field4, + retval.Field1, + retval.Field2, + retval.Field3, + retval.Field4); + + return retval; +} + +static TriangleArrays UnpackTriangles(vtkm::cont::ArrayHandle tris, + vtkm::cont::CoordinateSystem coords, + vtkm::cont::ArrayHandle normals) +{ + TriangleArrays retval; + + const auto numTris = tris.GetNumberOfValues(); + + bool extractNormals = normals.GetNumberOfValues() != 0; + + retval.Vertices.Allocate(numTris * 3); + if (extractNormals) + retval.Normals.Allocate(numTris * 3); + + ExtractTriangleVerticesAndNormals worklet(extractNormals); + vtkm::worklet::DispatcherMapField(worklet).Invoke( + tris, coords, normals, retval.Vertices, retval.Normals); + + return retval; +} + +// ANARIMapperTriangles definitions /////////////////////////////////////////// + +ANARIMapperTriangles::ANARIMapperTriangles(anari_cpp::Device device, + const ANARIActor& actor, + const std::string& name, + const vtkm::cont::ColorTable& colorTable) + : ANARIMapper(device, actor, name, colorTable) +{ + this->Handles = std::make_shared(); + this->Handles->Device = device; + auto& attributes = this->Handles->Parameters.Vertex.Attribute; + std::fill(attributes.begin(), attributes.end(), nullptr); + anari_cpp::retain(device, device); +} + +ANARIMapperTriangles::~ANARIMapperTriangles() +{ + // ensure ANARI handles are released before host memory goes away + this->Handles.reset(); +} + +void ANARIMapperTriangles::SetActor(const ANARIActor& actor) +{ + this->ANARIMapper::SetActor(actor); + this->ConstructArrays(true); + this->UpdateMaterial(); +} + +void ANARIMapperTriangles::SetMapFieldAsAttribute(bool enabled) +{ + this->ANARIMapper::SetMapFieldAsAttribute(enabled); + this->UpdateGeometry(); + this->UpdateMaterial(); +} + +void ANARIMapperTriangles::SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays) +{ + this->GetANARISurface(); + auto d = this->GetDevice(); + auto s = this->Handles->Sampler; + anari_cpp::setParameter(d, s, "image", color); + anari_cpp::commitParameters(d, s); + this->ANARIMapper::SetANARIColorMap(color, opacity, releaseArrays); +} + +void ANARIMapperTriangles::SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) +{ + this->GetANARISurface(); + auto s = this->Handles->Sampler; + auto d = this->GetDevice(); + auto scale = anari_cpp::scaling_matrix(anari_cpp::float3(1.f / (valueRange[1] - valueRange[0]))); + auto translation = anari_cpp::translation_matrix(anari_cpp::float3(-valueRange[0], 0, 0)); + anari_cpp::setParameter(d, s, "inTransform", anari_cpp::mul(scale, translation)); + anari_cpp::commitParameters(d, s); +} + +void ANARIMapperTriangles::SetCalculateNormals(bool enabled) +{ + this->CalculateNormals = enabled; +} + +anari_cpp::Geometry ANARIMapperTriangles::GetANARIGeometry() +{ + if (this->Handles->Geometry) + return this->Handles->Geometry; + + auto d = this->GetDevice(); + this->Handles->Geometry = anari_cpp::newObject(d, "triangle"); + this->ConstructArrays(); + this->UpdateGeometry(); + return this->Handles->Geometry; +} + +anari_cpp::Surface ANARIMapperTriangles::GetANARISurface() +{ + if (this->Handles->Surface) + return this->Handles->Surface; + + auto d = this->GetDevice(); + + this->Handles->Surface = anari_cpp::newObject(d); + + if (!this->Handles->Material) + { + this->Handles->Material = anari_cpp::newObject(d, "matte"); + anari_cpp::setParameter(d, this->Handles->Material, "name", this->MakeObjectName("material")); + } + + auto s = anari_cpp::newObject(d, "image1D"); + this->Handles->Sampler = s; + auto colorArray = anari_cpp::newArray1D(d, ANARI_FLOAT32_VEC4, 3); + auto* colors = anari_cpp::map(d, colorArray); + colors[0] = vtkm::Vec4f_32(1.f, 0.f, 0.f, 0.f); + colors[1] = vtkm::Vec4f_32(0.f, 1.f, 0.f, 0.5f); + colors[2] = vtkm::Vec4f_32(0.f, 0.f, 1.f, 1.f); + anari_cpp::unmap(d, colorArray); + anari_cpp::setAndReleaseParameter(d, s, "image", colorArray); + anari_cpp::setParameter(d, s, "filter", "linear"); + anari_cpp::setParameter(d, s, "wrapMode", "clampToEdge"); + anari_cpp::setParameter(d, s, "name", this->MakeObjectName("colormap")); + anari_cpp::commitParameters(d, s); + + this->SetANARIColorMapValueRange(vtkm::Vec2f_32(0.f, 10.f)); + + this->UpdateMaterial(); + + anari_cpp::setParameter(d, this->Handles->Surface, "name", this->MakeObjectName("surface")); + anari_cpp::setParameter(d, this->Handles->Surface, "geometry", this->GetANARIGeometry()); + anari_cpp::setParameter(d, this->Handles->Surface, "material", this->Handles->Material); + anari_cpp::commitParameters(d, this->Handles->Surface); + + return this->Handles->Surface; +} + +bool ANARIMapperTriangles::NeedToGenerateData() const +{ + const bool haveNormals = this->Handles->Parameters.Vertex.Normal != nullptr; + const bool needNormals = this->CalculateNormals && !haveNormals; + return !this->Current || needNormals; +} + +void ANARIMapperTriangles::ConstructArrays(bool regenerate) +{ + if (regenerate) + this->Current = false; + + if (!regenerate && !this->NeedToGenerateData()) + return; + + this->Current = true; + this->Valid = false; + + this->Handles->ReleaseArrays(); + + const auto& actor = this->GetActor(); + const auto& cells = actor.GetCellSet(); + + if (cells.GetNumberOfCells() == 0) + { + this->RefreshGroup(); + return; + } + + vtkm::rendering::raytracing::TriangleExtractor triExtractor; + triExtractor.ExtractCells(cells); + + if (triExtractor.GetNumberOfTriangles() == 0) + { + this->RefreshGroup(); + return; + } + + vtkm::cont::ArrayHandle inNormals; + + if (this->CalculateNormals) + { + vtkm::filter::vector_analysis::SurfaceNormals normalsFilter; + normalsFilter.SetOutputFieldName("Normals"); + auto dataset = normalsFilter.Execute(actor.MakeDataSet()); + auto field = dataset.GetField("Normals"); + auto fieldArray = field.GetData(); + vtkm::cont::ArrayCopyShallowIfPossible(fieldArray, inNormals); + } + + auto tris = triExtractor.GetTriangles(); + + auto arrays = UnpackTriangles(tris, actor.GetCoordinateSystem(), inNormals); + auto fieldArrays = UnpackFields(tris, actor.GetFieldSet(), this->GetColorTable().GetRange()); + + this->PrimaryField = actor.GetPrimaryFieldIndex(); + + auto numVerts = arrays.Vertices.GetNumberOfValues(); + + auto* v = (vtkm::Vec3f_32*)arrays.Vertices.GetBuffers()[0].ReadPointerHost(*arrays.Token); + auto* n = (vtkm::Vec3f_32*)arrays.Normals.GetBuffers()[0].ReadPointerHost(*arrays.Token); + + auto d = this->GetDevice(); + this->Handles->Parameters.NumPrimitives = numVerts / 3; + this->Handles->Parameters.Vertex.Position = + anari_cpp::newArray1D(d, v, NoopANARIDeleter, nullptr, numVerts); + + if (fieldArrays.Field1.GetNumberOfValues() != 0) + { + auto* a = (float*)fieldArrays.Field1.GetBuffers()[0].ReadPointerHost(*fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[0] = + anari_cpp::newArray1D(d, a, NoopANARIDeleter, nullptr, numVerts); + } + if (fieldArrays.Field2.GetNumberOfValues() != 0) + { + auto* a = (float*)fieldArrays.Field2.GetBuffers()[0].ReadPointerHost(*fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[1] = + anari_cpp::newArray1D(d, a, NoopANARIDeleter, nullptr, numVerts); + } + if (fieldArrays.Field3.GetNumberOfValues() != 0) + { + auto* a = (float*)fieldArrays.Field3.GetBuffers()[0].ReadPointerHost(*fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[2] = + anari_cpp::newArray1D(d, a, NoopANARIDeleter, nullptr, numVerts); + } + if (fieldArrays.Field4.GetNumberOfValues() != 0) + { + auto* a = (float*)fieldArrays.Field4.GetBuffers()[0].ReadPointerHost(*fieldArrays.Token); + this->Handles->Parameters.Vertex.Attribute[3] = + anari_cpp::newArray1D(d, a, NoopANARIDeleter, nullptr, numVerts); + } + if (this->CalculateNormals) + { + this->Handles->Parameters.Vertex.Normal = + anari_cpp::newArray1D(d, n, NoopANARIDeleter, nullptr, arrays.Normals.GetNumberOfValues()); + } + + // NOTE: usd device requires indices, but shouldn't + { + auto indexArray = + anari_cpp::newArray1D(d, ANARI_UINT32_VEC3, this->Handles->Parameters.NumPrimitives); + auto* begin = anari_cpp::map(d, indexArray); + auto* end = begin + numVerts; + std::iota(begin, end, 0); + anari_cpp::unmap(d, indexArray); + this->Handles->Parameters.Primitive.Index = indexArray; + } + + this->UpdateGeometry(); + this->UpdateMaterial(); + + this->Arrays = arrays; + this->FieldArrays = fieldArrays; + this->Valid = true; + + this->RefreshGroup(); +} + +void ANARIMapperTriangles::UpdateGeometry() +{ + if (!this->Handles->Geometry) + return; + + auto d = this->GetDevice(); + + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.position"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute0"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute1"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute2"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.attribute3"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "vertex.normal"); + anari_cpp::unsetParameter(d, this->Handles->Geometry, "primitive.index"); + + anari_cpp::setParameter(d, this->Handles->Geometry, "name", this->MakeObjectName("geometry")); + + if (this->Handles->Parameters.Vertex.Position) + { + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.position", this->Handles->Parameters.Vertex.Position); + if (this->GetMapFieldAsAttribute()) + { + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute0", + this->Handles->Parameters.Vertex.Attribute[0]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute1", + this->Handles->Parameters.Vertex.Attribute[1]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute2", + this->Handles->Parameters.Vertex.Attribute[2]); + anari_cpp::setParameter(d, + this->Handles->Geometry, + "vertex.attribute3", + this->Handles->Parameters.Vertex.Attribute[3]); + } + + if (CalculateNormals) + { + anari_cpp::setParameter( + d, this->Handles->Geometry, "vertex.normal", this->Handles->Parameters.Vertex.Normal); + } + anari_cpp::setParameter( + d, this->Handles->Geometry, "primitive.index", this->Handles->Parameters.Primitive.Index); + } + + anari_cpp::commitParameters(d, this->Handles->Geometry); +} + +void ANARIMapperTriangles::UpdateMaterial() +{ + if (!this->Handles->Material) + return; + + auto d = this->GetDevice(); + auto s = this->Handles->Sampler; + auto a = this->Handles->Parameters.Vertex.Attribute[PrimaryField]; + if (s && a && this->GetMapFieldAsAttribute()) + { + anari_cpp::setParameter(d, s, "inAttribute", AnariMaterialInputString(PrimaryField)); + anari_cpp::commitParameters(d, s); + anari_cpp::setParameter(d, this->Handles->Material, "color", s); + } + else + anari_cpp::setParameter(d, this->Handles->Material, "color", vtkm::Vec3f_32(1.f)); + + anari_cpp::commitParameters(d, this->Handles->Material); +} + +ANARIMapperTriangles::ANARIHandles::~ANARIHandles() +{ + this->ReleaseArrays(); + anari_cpp::release(this->Device, this->Surface); + anari_cpp::release(this->Device, this->Material); + anari_cpp::release(this->Device, this->Sampler); + anari_cpp::release(this->Device, this->Geometry); + anari_cpp::release(this->Device, this->Device); +} + +void ANARIMapperTriangles::ANARIHandles::ReleaseArrays() +{ + anari_cpp::release(this->Device, this->Parameters.Vertex.Position); + anari_cpp::release(this->Device, this->Parameters.Vertex.Normal); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[0]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[1]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[2]); + anari_cpp::release(this->Device, this->Parameters.Vertex.Attribute[3]); + anari_cpp::release(this->Device, this->Parameters.Primitive.Index); + this->Parameters.Vertex.Position = nullptr; + this->Parameters.Vertex.Normal = nullptr; + this->Parameters.Vertex.Attribute[0] = nullptr; + this->Parameters.Vertex.Attribute[1] = nullptr; + this->Parameters.Vertex.Attribute[2] = nullptr; + this->Parameters.Vertex.Attribute[3] = nullptr; + this->Parameters.Primitive.Index = nullptr; +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIMapperTriangles.h b/vtkm/interop/anari/ANARIMapperTriangles.h new file mode 100644 index 000000000..722b512d0 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperTriangles.h @@ -0,0 +1,145 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIMapperTriangles_h +#define vtk_m_interop_anari_ANARIMapperTriangles_h + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// @brief Raw ANARI arrays and parameter values set on the `ANARIGeometry`. +/// +struct TrianglesParameters +{ + struct VertexData + { + anari_cpp::Array1D Position{ nullptr }; + anari_cpp::Array1D Normal{ nullptr }; + std::array Attribute; + } Vertex{}; + + struct PrimitiveData + { + anari_cpp::Array1D Index{ nullptr }; + } Primitive{}; + + unsigned int NumPrimitives{ 0 }; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper. +/// +struct TriangleArrays +{ + vtkm::cont::ArrayHandle Vertices; + vtkm::cont::ArrayHandle Normals; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper for field attributes. +/// +struct TriangleFieldArrays +{ + vtkm::cont::ArrayHandle Field1; + vtkm::cont::ArrayHandle Field2; + vtkm::cont::ArrayHandle Field3; + vtkm::cont::ArrayHandle Field4; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief Mapper which triangulates cells into ANARI `triangle` geometry. +/// +/// NOTE: This mapper will only color map values that are 1-component Float32 +/// fields, otherwise they will be ignored. This restriction will allow 2-4 +/// component fields in the future. +struct VTKM_ANARI_EXPORT ANARIMapperTriangles : public ANARIMapper +{ + /// @brief Constructor + /// + ANARIMapperTriangles( + anari_cpp::Device device, + const ANARIActor& actor = {}, + const std::string& name = "", + const vtkm::cont::ColorTable& colorTable = vtkm::cont::ColorTable::Preset::Default); + + /// @brief Destructor + /// + ~ANARIMapperTriangles() override; + + /// @brief Set the current actor on this mapper. + /// + /// See `ANARIMapper` for more detail. + void SetActor(const ANARIActor& actor) override; + + /// @brief Set whether fields from `ANARIActor` should end up as geometry attributes. + /// + /// See `ANARIMapper` for more detail. + void SetMapFieldAsAttribute(bool enabled) override; + + /// @brief Set color map arrays using raw ANARI array handles. + /// + /// See `ANARIMapper` for more detail. + void SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays = true) override; + + /// @brief Set the value range (domain) for the color map. + /// + void SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) override; + + /// @brief Set whether `vertex.normal` data should also be calculated when triangulating geometry. + /// + void SetCalculateNormals(bool enabled); + + anari_cpp::Geometry GetANARIGeometry() override; + anari_cpp::Surface GetANARISurface() override; + +private: + bool NeedToGenerateData() const; + /// @brief Do the work to construct the basic ANARI arrays for the ANARIGeometry. + /// @param regenerate Force the position/radius arrays are regenerated. + /// + void ConstructArrays(bool regenerate = false); + /// @brief Update ANARIGeometry object with the latest data from the actor. + void UpdateGeometry(); + /// @brief Update ANARIMaterial object with the latest data from the actor. + void UpdateMaterial(); + + /// @brief Container of all relevant ANARI scene object handles. + struct ANARIHandles + { + anari_cpp::Device Device{ nullptr }; + anari_cpp::Geometry Geometry{ nullptr }; + anari_cpp::Sampler Sampler{ nullptr }; + anari_cpp::Material Material{ nullptr }; + anari_cpp::Surface Surface{ nullptr }; + TrianglesParameters Parameters; + ~ANARIHandles(); + void ReleaseArrays(); + }; + + std::shared_ptr Handles; + + bool CalculateNormals{ false }; + vtkm::IdComponent PrimaryField{ 0 }; + TriangleArrays Arrays; + TriangleFieldArrays FieldArrays; +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIMapperVolume.cxx b/vtkm/interop/anari/ANARIMapperVolume.cxx new file mode 100644 index 000000000..593674f84 --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperVolume.cxx @@ -0,0 +1,211 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +ANARIMapperVolume::ANARIMapperVolume(anari_cpp::Device device, + const ANARIActor& actor, + const std::string& name, + const vtkm::cont::ColorTable& colorTable) + : ANARIMapper(device, actor, name, colorTable) +{ + this->Handles = std::make_shared(); + this->Handles->Device = device; + anari_cpp::retain(device, device); +} + +ANARIMapperVolume::~ANARIMapperVolume() +{ + // ensure ANARI handles are released before host memory goes away + this->Handles.reset(); +} + +void ANARIMapperVolume::SetActor(const ANARIActor& actor) +{ + this->ANARIMapper::SetActor(actor); + this->ConstructArrays(true); +} + +void ANARIMapperVolume::SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays) +{ + auto d = this->GetDevice(); + auto v = this->GetANARIVolume(); + anari_cpp::setParameter(d, v, "color", color); + anari_cpp::setParameter(d, v, "opacity", opacity); + anari_cpp::commitParameters(d, v); + this->ANARIMapper::SetANARIColorMap(color, opacity, releaseArrays); +} + +void ANARIMapperVolume::SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) +{ + auto d = this->GetDevice(); + auto v = this->GetANARIVolume(); + anari_cpp::setParameter(d, v, "valueRange", ANARI_FLOAT32_BOX1, &valueRange); + anari_cpp::commitParameters(d, v); +} + +void ANARIMapperVolume::SetANARIColorMapOpacityScale(vtkm::Float32 opacityScale) +{ + auto d = this->GetDevice(); + auto v = this->GetANARIVolume(); + anari_cpp::setParameter(d, v, "densityScale", opacityScale); + anari_cpp::commitParameters(d, v); +} + +anari_cpp::SpatialField ANARIMapperVolume::GetANARISpatialField() +{ + if (this->Handles->SpatialField) + return this->Handles->SpatialField; + + this->Handles->SpatialField = + anari_cpp::newObject(this->GetDevice(), "structuredRegular"); + this->ConstructArrays(); + this->UpdateSpatialField(); + return this->Handles->SpatialField; +} + +anari_cpp::Volume ANARIMapperVolume::GetANARIVolume() +{ + if (this->Handles->Volume) + return this->Handles->Volume; + + auto d = this->GetDevice(); + + this->Handles->Volume = anari_cpp::newObject(d, "transferFunction1D"); + + auto colorArray = anari_cpp::newArray1D(d, ANARI_FLOAT32_VEC3, 3); + auto* colors = anari_cpp::map(d, colorArray); + colors[0] = vtkm::Vec3f_32(1.f, 0.f, 0.f); + colors[1] = vtkm::Vec3f_32(0.f, 1.f, 0.f); + colors[2] = vtkm::Vec3f_32(0.f, 0.f, 1.f); + anari_cpp::unmap(d, colorArray); + + auto opacityArray = anari_cpp::newArray1D(d, ANARI_FLOAT32, 2); + auto* opacities = anari_cpp::map(d, opacityArray); + opacities[0] = 0.f; + opacities[1] = 1.f; + anari_cpp::unmap(d, opacityArray); + + anari_cpp::setAndReleaseParameter(d, this->Handles->Volume, "color", colorArray); + anari_cpp::setAndReleaseParameter(d, this->Handles->Volume, "opacity", opacityArray); + anari_cpp::setParameter(d, this->Handles->Volume, "field", this->GetANARISpatialField()); + anari_cpp::setParameter(d, this->Handles->Volume, "name", this->MakeObjectName("volume")); + anari_cpp::commitParameters(d, this->Handles->Volume); + + SetANARIColorMapValueRange(vtkm::Vec2f_32(0.f, 10.f)); + + return this->Handles->Volume; +} + +void ANARIMapperVolume::ConstructArrays(bool regenerate) +{ + if (regenerate) + this->Current = false; + + if (this->Current) + return; + + this->Current = true; + this->Valid = false; + + const auto& actor = this->GetActor(); + const auto& coords = actor.GetCoordinateSystem(); + const auto& cells = actor.GetCellSet(); + const auto& fieldArray = actor.GetField().GetData(); + + const bool isStructured = cells.IsType>(); + const bool isFloat = fieldArray.IsType>(); + + this->Handles->ReleaseArrays(); + + if (isStructured && isFloat) + { + auto structuredCells = cells.AsCellSet>(); + auto pdims = structuredCells.GetPointDimensions(); + + VolumeArrays arrays; + + auto d = this->GetDevice(); + + arrays.Data = fieldArray.AsArrayHandle>(); + + auto* ptr = (float*)arrays.Data.GetBuffers()[0].ReadPointerHost(*arrays.Token); + + auto bounds = coords.GetBounds(); + vtkm::Vec3f_32 bLower(bounds.X.Min, bounds.Y.Min, bounds.Z.Min); + vtkm::Vec3f_32 bUpper(bounds.X.Max, bounds.Y.Max, bounds.Z.Max); + vtkm::Vec3f_32 size = bUpper - bLower; + + vtkm::Vec3ui_32 dims(pdims[0], pdims[1], pdims[2]); + auto spacing = size / (vtkm::Vec3f_32(dims) - 1.f); + + std::memcpy(this->Handles->Parameters.Dims, &dims, sizeof(dims)); + std::memcpy(this->Handles->Parameters.Origin, &bLower, sizeof(bLower)); + std::memcpy(this->Handles->Parameters.Spacing, &spacing, sizeof(spacing)); + this->Handles->Parameters.Data = + anari_cpp::newArray3D(d, ptr, NoopANARIDeleter, nullptr, dims[0], dims[1], dims[2]); + + this->Arrays = arrays; + this->Valid = true; + } + + this->UpdateSpatialField(); + this->RefreshGroup(); +} + +void ANARIMapperVolume::UpdateSpatialField() +{ + auto d = this->GetDevice(); + + anari_cpp::unsetParameter(d, this->Handles->SpatialField, "origin"); + anari_cpp::unsetParameter(d, this->Handles->SpatialField, "spacing"); + anari_cpp::unsetParameter(d, this->Handles->SpatialField, "data"); + + anari_cpp::setParameter( + d, this->Handles->SpatialField, "name", this->MakeObjectName("spatialField")); + + if (this->Handles->Parameters.Data) + { + anari_cpp::setParameter( + d, this->Handles->SpatialField, "origin", this->Handles->Parameters.Origin); + anari_cpp::setParameter( + d, this->Handles->SpatialField, "spacing", this->Handles->Parameters.Spacing); + anari_cpp::setParameter(d, this->Handles->SpatialField, "data", this->Handles->Parameters.Data); + } + + anari_cpp::commitParameters(d, this->Handles->SpatialField); +} + +ANARIMapperVolume::ANARIHandles::~ANARIHandles() +{ + this->ReleaseArrays(); + anari_cpp::release(this->Device, this->Volume); + anari_cpp::release(this->Device, this->SpatialField); + anari_cpp::release(this->Device, this->Device); +} + +void ANARIMapperVolume::ANARIHandles::ReleaseArrays() +{ + anari_cpp::release(this->Device, this->Parameters.Data); + this->Parameters.Data = nullptr; +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIMapperVolume.h b/vtkm/interop/anari/ANARIMapperVolume.h new file mode 100644 index 000000000..560f4225a --- /dev/null +++ b/vtkm/interop/anari/ANARIMapperVolume.h @@ -0,0 +1,108 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIMapperVolume_h +#define vtk_m_interop_anari_ANARIMapperVolume_h + +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// @brief Raw ANARI arrays and parameter values set on the `ANARISpatialField`. +/// +struct VolumeParameters +{ + anari_cpp::Array3D Data{ nullptr }; + int Dims[3]; + float Origin[3]; + float Spacing[3]; +}; + +/// @brief VTK-m data arrays underlying the `ANARIArray` handles created by the mapper. +/// +struct VolumeArrays +{ + vtkm::cont::ArrayHandle Data; + std::shared_ptr Token{ new vtkm::cont::Token }; +}; + +/// @brief Mapper which turns structured volumes into a single ANARI `transferFunction1D` volume. +/// +/// NOTE: This currently only supports Float32 scalar fields. In the future this +/// mapper will also support Uint8, Uint16, and Float64 scalar fields. +struct VTKM_ANARI_EXPORT ANARIMapperVolume : public ANARIMapper +{ + /// @brief Constructor + /// + ANARIMapperVolume( + anari_cpp::Device device, + const ANARIActor& actor = {}, + const std::string& name = "", + const vtkm::cont::ColorTable& colorTable = vtkm::cont::ColorTable::Preset::Default); + + /// @brief Destructor + /// + ~ANARIMapperVolume() override; + + /// @brief Set the current actor on this mapper. + /// + /// See `ANARIMapper` for more detail. + void SetActor(const ANARIActor& actor) override; + + /// @brief Set color map arrays using raw ANARI array handles. + /// + /// See `ANARIMapper` for more detail. + void SetANARIColorMap(anari_cpp::Array1D color, + anari_cpp::Array1D opacity, + bool releaseArrays = true) override; + /// @brief Set the value range (domain) for the color map. + /// + void SetANARIColorMapValueRange(const vtkm::Vec2f_32& valueRange) override; + + /// @brief Set a scale factor for opacity. + /// + void SetANARIColorMapOpacityScale(vtkm::Float32 opacityScale) override; + + anari_cpp::SpatialField GetANARISpatialField() override; + anari_cpp::Volume GetANARIVolume() override; + +private: + /// @brief Do the work to construct the basic ANARI arrays for the ANARIGeometry. + /// @param regenerate Force the position/radius arrays are regenerated. + /// + void ConstructArrays(bool regenerate = false); + /// @brief Update ANARISpatialField object with the latest data from the actor. + void UpdateSpatialField(); + + /// @brief Container of all relevant ANARI scene object handles. + struct ANARIHandles + { + anari_cpp::Device Device{ nullptr }; + anari_cpp::SpatialField SpatialField{ nullptr }; + anari_cpp::Volume Volume{ nullptr }; + VolumeParameters Parameters; + ~ANARIHandles(); + void ReleaseArrays(); + }; + + std::shared_ptr Handles; + VolumeArrays Arrays; +}; + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/ANARIScene.cxx b/vtkm/interop/anari/ANARIScene.cxx new file mode 100644 index 000000000..87c5c2d8e --- /dev/null +++ b/vtkm/interop/anari/ANARIScene.cxx @@ -0,0 +1,155 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include +// std +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +ANARIScene::ANARIScene(anari_cpp::Device device) + : Device(device) +{ + anari_cpp::retain(this->Device, this->Device); +} + +ANARIScene::~ANARIScene() +{ + anari_cpp::release(this->Device, this->World); + anari_cpp::release(this->Device, this->Device); +} + +vtkm::IdComponent ANARIScene::GetNumberOfMappers() const +{ + return static_cast(this->Mappers.size()); +} + +bool ANARIScene::HasMapperWithName(const char* _name) const +{ + std::string name = _name; + auto itr = std::find_if(this->Mappers.begin(), this->Mappers.end(), [&](auto& m) { + return m.Mapper->GetName() == name; + }); + return itr != this->Mappers.end(); +} + +vtkm::IdComponent ANARIScene::GetMapperIndexByName(const char* _name) +{ + std::string name = _name; + auto itr = std::find_if(this->Mappers.begin(), this->Mappers.end(), [&](auto& m) { + return m.Mapper->GetName() == name; + }); + return static_cast(std::distance(this->Mappers.begin(), itr)); +} + +ANARIMapper& ANARIScene::GetMapper(vtkm::IdComponent id) +{ + return *this->Mappers[id].Mapper; +} + +ANARIMapper& ANARIScene::GetMapper(const char* _name) +{ + std::string name = _name; + auto itr = std::find_if(this->Mappers.begin(), this->Mappers.end(), [&](auto& m) { + return m.Mapper->GetName() == name; + }); + return *itr->Mapper; +} + +bool ANARIScene::GetMapperVisible(vtkm::IdComponent id) const +{ + return this->Mappers[id].Show; +} + +void ANARIScene::SetMapperVisible(vtkm::IdComponent id, bool shown) +{ + auto& m = this->Mappers[id]; + if (m.Show != shown) + { + m.Show = shown; + this->UpdateWorld(); + } +} + +void ANARIScene::RemoveMapper(vtkm::IdComponent id) +{ + this->Mappers.erase(this->Mappers.begin() + id); + this->UpdateWorld(); +} + +void ANARIScene::RemoveMapper(const char* name) +{ + std::string n = name; + this->Mappers.erase(std::remove_if(this->Mappers.begin(), + this->Mappers.end(), + [&](auto& m) { return m.Mapper->GetName() == n; }), + this->Mappers.end()); + this->UpdateWorld(); +} + +void ANARIScene::RemoveAllMappers() +{ + this->Mappers.clear(); + this->UpdateWorld(); +} + +anari_cpp::Device ANARIScene::GetDevice() const +{ + return this->Device; +} + +anari_cpp::World ANARIScene::GetANARIWorld() +{ + if (!this->World) + { + auto d = this->GetDevice(); + this->World = anari_cpp::newObject(d); + anari_cpp::setParameter(d, this->World, "name", "scene"); + this->UpdateWorld(); + } + + return this->World; +} + +void ANARIScene::UpdateWorld() +{ + if (!this->World) + return; // nobody has asked for the world yet, so don't actually do anything + + auto d = this->GetDevice(); + + std::vector instances; + + for (auto& m : this->Mappers) + { + auto i = m.Mapper->GetANARIInstance(); + if (i && m.Show) + instances.push_back(i); + } + + if (!instances.empty()) + { + anari_cpp::setAndReleaseParameter( + d, this->World, "instance", anari_cpp::newArray1D(d, instances.data(), instances.size())); + } + else + anari_cpp::unsetParameter(d, this->World, "instance"); + + anari_cpp::commitParameters(d, this->World); +} + +} // namespace anari +} // namespace interop +} // namespace vtkm diff --git a/vtkm/interop/anari/ANARIScene.h b/vtkm/interop/anari/ANARIScene.h new file mode 100644 index 000000000..c4edb4457 --- /dev/null +++ b/vtkm/interop/anari/ANARIScene.h @@ -0,0 +1,176 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_ANARIScene_h +#define vtk_m_interop_anari_ANARIScene_h + +#include +// std +#include +#include + +namespace vtkm +{ +namespace interop +{ +namespace anari +{ + +/// @brief Object which manages a collection of mappers representing a single scene. +/// +/// This object is a container of named mappers which will automatically keep +/// an `ANARIWorld` up to date which contains any `ANARISurface` or +/// `ANARIVolume` objects coming from the contained mappers. While applications +/// are free to do this work themselves, it is very convenient and useful to +/// let `ANARIScene` do the work of keeping an `ANARIWorld` up to date for you. +/// +/// Mappers in `ANARIScene` can also be selectively hidden for quick scene +/// updates. A hidden mapper's geometry/volume are just skipped when updating +/// the list of object handles in the world. +/// +/// NOTE: This object will not create any lights in the scene, so the +/// `ANARIWorld` used by the application is expected to have application-managed +/// `ANARILight` objects added to it when desired. +/// +/// NOTE: Unlike `ANARIMapper` and `ANARIActor`, `ANARIScene` is not C++ +/// copyable or movable. +struct VTKM_ANARI_EXPORT ANARIScene +{ + /// @brief Constructor + /// + ANARIScene(anari_cpp::Device device); + + /// @brief Destructor + /// + ~ANARIScene(); + + ANARIScene(const ANARIScene&) = delete; + ANARIScene(ANARIScene&&) = delete; + ANARIScene& operator=(const ANARIScene&) = delete; + ANARIScene& operator=(ANARIScene&&) = delete; + + /// @brief Add a mapper to the scene. + /// @tparam ANARIMapperType Any subclass of `ANARIMapper`. + /// + /// NOTE: This will replace any mapper that has the same name. + template + ANARIMapperType& AddMapper(const ANARIMapperType& mapper, bool visible = true); + + /// @brief Add a mapper to the scene. + /// @tparam ANARIMapperType Any subclass of `ANARIMapper`. + /// + /// NOTE: Replace the i'th mapper with a new instance. + /// NOTE: It is undefined behavior to use this to put 2 or more mappers in the + /// scene with the same name. + template + void ReplaceMapper(const ANARIMapperType& newMapper, vtkm::IdComponent id, bool visible); + + /// @brief Get number of mappers in this scene. + /// + vtkm::IdComponent GetNumberOfMappers() const; + + /// @brief Ask whether a mapper has the passed in name or not. + /// + bool HasMapperWithName(const char* name) const; + + /// @brief Get the index to the mapper with the given name. + /// + vtkm::IdComponent GetMapperIndexByName(const char* name); + + /// @brief Get the associated mapper by index. + /// + ANARIMapper& GetMapper(vtkm::IdComponent id); + + /// @brief Get the associated mapper by name. + /// + ANARIMapper& GetMapper(const char* name); + + /// @brief Get the associated mapper by name. + /// + bool GetMapperVisible(vtkm::IdComponent id) const; + void SetMapperVisible(vtkm::IdComponent id, bool shown); + + /// @brief Remove mapper by index. + /// + void RemoveMapper(vtkm::IdComponent id); + + /// @brief Remove mapper by name. + /// + void RemoveMapper(const char* name); + + /// @brief Clear out this scene of all mappers. + void RemoveAllMappers(); + + /// @brief Get the `ANARIDevice` handle this scene is talking to. + /// + /// NOTE: This handle is not retained, so applications should not release it. + anari_cpp::Device GetDevice() const; + + /// @brief Get the `ANARIWorld` handle this scene is working on. + /// + /// NOTE: This handle is not retained, so applications should not release it. + anari_cpp::World GetANARIWorld(); + +private: + void UpdateWorld(); + + anari_cpp::Device Device{ nullptr }; + anari_cpp::World World{ nullptr }; + + struct SceneMapper + { + std::unique_ptr Mapper; + bool Show{ true }; + }; + + std::vector Mappers; +}; + +// Inlined definitions //////////////////////////////////////////////////////// + +template +inline ANARIMapperType& ANARIScene::AddMapper(const ANARIMapperType& mapper, bool visible) +{ + static_assert(std::is_base_of::value, + "Only ANARIMapper types can be added to ANARIScene"); + + auto* name = mapper.GetName(); + if (HasMapperWithName(name)) + { + auto idx = GetMapperIndexByName(name); + ReplaceMapper(mapper, idx, visible); + return (ANARIMapperType&)GetMapper(idx); + } + else + { + this->Mappers.push_back({ std::make_unique(mapper), visible }); + UpdateWorld(); + return (ANARIMapperType&)GetMapper(GetNumberOfMappers() - 1); + } +} + +template +inline void ANARIScene::ReplaceMapper(const ANARIMapperType& newMapper, + vtkm::IdComponent id, + bool visible) +{ + static_assert(std::is_base_of::value, + "Only ANARIMapper types can be added to ANARIScene"); + const bool wasVisible = GetMapperVisible(id); + Mappers[id] = { std::make_unique(newMapper), visible }; + if (wasVisible || visible) + UpdateWorld(); +} + +} // namespace anari +} // namespace interop +} // namespace vtkm + +#endif diff --git a/vtkm/interop/anari/CMakeLists.txt b/vtkm/interop/anari/CMakeLists.txt new file mode 100644 index 000000000..5cb2a8696 --- /dev/null +++ b/vtkm/interop/anari/CMakeLists.txt @@ -0,0 +1,47 @@ +##============================================================================ +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +##============================================================================ + +find_package(anari 0.7.0 REQUIRED) + +set(headers + ANARIActor.h + ANARIMapper.h + ANARIMapperGlyphs.h + ANARIMapperPoints.h + ANARIMapperTriangles.h + ANARIMapperVolume.h + ANARIScene.h + VtkmANARITypes.h + ) + +set(sources + ANARIActor.cxx + ANARIMapper.cxx + ANARIScene.cxx + VtkmANARITypes.cxx + ) + +set(device_sources + ANARIMapperGlyphs.cxx + ANARIMapperPoints.cxx + ANARIMapperTriangles.cxx + ANARIMapperVolume.cxx + ../../rendering/raytracing/SphereExtractor.cxx + ) + + +vtkm_library( + NAME vtkm_anari + SOURCES ${sources} + HEADERS ${headers} + DEVICE_SOURCES ${device_sources} + ) + +target_link_libraries(vtkm_anari PUBLIC anari::anari) diff --git a/vtkm/interop/anari/VtkmANARITypes.cxx b/vtkm/interop/anari/VtkmANARITypes.cxx new file mode 100644 index 000000000..e4cbd5e72 --- /dev/null +++ b/vtkm/interop/anari/VtkmANARITypes.cxx @@ -0,0 +1,26 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#include "VtkmANARITypes.h" + +namespace anari +{ + +ANARI_TYPEFOR_DEFINITION(vtkm::Vec2f_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec3f_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec4f_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec2i_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec3i_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec4i_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec2ui_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec3ui_32); +ANARI_TYPEFOR_DEFINITION(vtkm::Vec4ui_32); + +} // namespace anari diff --git a/vtkm/interop/anari/VtkmANARITypes.h b/vtkm/interop/anari/VtkmANARITypes.h new file mode 100644 index 000000000..1b194d412 --- /dev/null +++ b/vtkm/interop/anari/VtkmANARITypes.h @@ -0,0 +1,43 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_VtkmANARITypes_h +#define vtk_m_interop_anari_VtkmANARITypes_h + +// vtk-m +#include +// anari +#include +#if ANARI_SDK_VERSION_MINOR <= 3 +#include +#endif + +namespace anari_cpp = ::anari; + +namespace anari +{ + +/// These declarations let ANARI C++ bindings infer the correct `ANARIDataType` +/// enum value from VTK-m's C++ math types. This header should be included +/// before any code which needs this type inference to function. + +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec2f_32, ANARI_FLOAT32_VEC2); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec3f_32, ANARI_FLOAT32_VEC3); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec4f_32, ANARI_FLOAT32_VEC4); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec2i_32, ANARI_INT32_VEC2); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec3i_32, ANARI_INT32_VEC3); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec4i_32, ANARI_INT32_VEC4); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec2ui_32, ANARI_UINT32_VEC2); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec3ui_32, ANARI_UINT32_VEC3); +ANARI_TYPEFOR_SPECIALIZATION(vtkm::Vec4ui_32, ANARI_UINT32_VEC4); + +} // namespace anari + +#endif diff --git a/vtkm/interop/anari/testing/ANARITestCommon.h b/vtkm/interop/anari/testing/ANARITestCommon.h new file mode 100644 index 000000000..06ac00f7a --- /dev/null +++ b/vtkm/interop/anari/testing/ANARITestCommon.h @@ -0,0 +1,149 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +#ifndef vtk_m_interop_anari_testing_ANARITestCommon_h +#define vtk_m_interop_anari_testing_ANARITestCommon_h + +// vtk-m +#include +#include +#include +#include + +namespace +{ + +static void StatusFunc(const void* userData, + ANARIDevice /*device*/, + ANARIObject source, + ANARIDataType /*sourceType*/, + ANARIStatusSeverity severity, + ANARIStatusCode /*code*/, + const char* message) +{ + bool verbose = *(bool*)userData; + if (!verbose) + return; + + if (severity == ANARI_SEVERITY_FATAL_ERROR) + { + fprintf(stderr, "[FATAL][%p] %s\n", source, message); + } + else if (severity == ANARI_SEVERITY_ERROR) + { + fprintf(stderr, "[ERROR][%p] %s\n", source, message); + } + else if (severity == ANARI_SEVERITY_WARNING) + { + fprintf(stderr, "[WARN ][%p] %s\n", source, message); + } + else if (severity == ANARI_SEVERITY_PERFORMANCE_WARNING) + { + fprintf(stderr, "[PERF ][%p] %s\n", source, message); + } + else if (severity == ANARI_SEVERITY_INFO) + { + fprintf(stderr, "[INFO ][%p] %s\n", source, message); + } + else if (severity == ANARI_SEVERITY_DEBUG) + { + fprintf(stderr, "[DEBUG][%p] %s\n", source, message); + } +} + +static void setColorMap(anari_cpp::Device d, vtkm::interop::anari::ANARIMapper& mapper) +{ + auto colorArray = anari_cpp::newArray1D(d, ANARI_FLOAT32_VEC3, 3); + auto* colors = anari_cpp::map(d, colorArray); + colors[0] = vtkm::Vec3f_32(0.f, 0.f, 1.f); + colors[1] = vtkm::Vec3f_32(0.f, 1.f, 0.f); + colors[2] = vtkm::Vec3f_32(1.f, 0.f, 0.f); + anari_cpp::unmap(d, colorArray); + + auto opacityArray = anari_cpp::newArray1D(d, ANARI_FLOAT32, 2); + auto* opacities = anari_cpp::map(d, opacityArray); + opacities[0] = 0.f; + opacities[1] = 1.f; + anari_cpp::unmap(d, opacityArray); + + mapper.SetANARIColorMap(colorArray, opacityArray, true); + mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(0.f, 10.f)); + mapper.SetANARIColorMapOpacityScale(0.5f); +} + +static anari_cpp::Device loadANARIDevice() +{ + vtkm::testing::FloatingPointExceptionTrapDisable(); + auto* libraryName = std::getenv("VTKM_TEST_ANARI_LIBRARY"); + static bool verbose = std::getenv("VTKM_TEST_ANARI_VERBOSE") != nullptr; + auto lib = anari_cpp::loadLibrary(libraryName ? libraryName : "helide", StatusFunc, &verbose); + auto d = anari_cpp::newDevice(lib, "default"); + anari_cpp::unloadLibrary(lib); + return d; +} + +static void renderTestANARIImage(anari_cpp::Device d, + anari_cpp::World w, + vtkm::Vec3f_32 cam_pos, + vtkm::Vec3f_32 cam_dir, + vtkm::Vec3f_32 cam_up, + const std::string& imgName, + vtkm::Vec2ui_32 imgSize = vtkm::Vec2ui_32(1024, 768)) +{ + auto renderer = anari_cpp::newObject(d, "default"); + anari_cpp::setParameter(d, renderer, "background", vtkm::Vec4f_32(0.3f, 0.3f, 0.3f, 1.f)); + anari_cpp::setParameter(d, renderer, "pixelSamples", 64); + anari_cpp::commitParameters(d, renderer); + + auto camera = anari_cpp::newObject(d, "perspective"); + anari_cpp::setParameter(d, camera, "aspect", imgSize[0] / float(imgSize[1])); + anari_cpp::setParameter(d, camera, "position", cam_pos); + anari_cpp::setParameter(d, camera, "direction", cam_dir); + anari_cpp::setParameter(d, camera, "up", cam_up); + anari_cpp::commitParameters(d, camera); + + auto frame = anari_cpp::newObject(d); + anari_cpp::setParameter(d, frame, "size", imgSize); + anari_cpp::setParameter(d, frame, "channel.color", ANARI_FLOAT32_VEC4); + anari_cpp::setParameter(d, frame, "world", w); + anari_cpp::setParameter(d, frame, "camera", camera); + anari_cpp::setParameter(d, frame, "renderer", renderer); + anari_cpp::commitParameters(d, frame); + + anari_cpp::release(d, camera); + anari_cpp::release(d, renderer); + + anari_cpp::render(d, frame); + anari_cpp::wait(d, frame); + + const auto fb = anari_cpp::map(d, frame, "channel.color"); + + vtkm::cont::DataSetBuilderUniform builder; + vtkm::cont::DataSet image = builder.Create(vtkm::Id2(fb.width, fb.height)); + + // NOTE: We are only copying the pixel data into a VTK-m array for the + // purpose of using VTK-m's image comparison test code. Applications + // would not normally do this and instead just use the pixel data + // directly, such as displaying it in an interactive window. + + vtkm::cont::ArrayHandle colorArray = + vtkm::cont::make_ArrayHandle(fb.data, fb.width * fb.height, vtkm::CopyFlag::On); + + anari_cpp::unmap(d, frame, "channel.color"); + anari_cpp::release(d, frame); + + image.AddPointField("color", colorArray); + + VTKM_TEST_ASSERT(test_equal_images(image, imgName)); +} + +} // namespace + +#endif diff --git a/vtkm/interop/anari/testing/CMakeLists.txt b/vtkm/interop/anari/testing/CMakeLists.txt new file mode 100644 index 000000000..5f4d69d38 --- /dev/null +++ b/vtkm/interop/anari/testing/CMakeLists.txt @@ -0,0 +1,18 @@ +##============================================================================ +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +##============================================================================ + +vtkm_unit_tests( +SOURCES + UnitTestANARIMapperGlyphs.cxx + UnitTestANARIMapperPoints.cxx + UnitTestANARIMapperTriangles.cxx + UnitTestANARIMapperVolume.cxx + UnitTestANARIScene.cxx + ) diff --git a/vtkm/interop/anari/testing/UnitTestANARIMapperGlyphs.cxx b/vtkm/interop/anari/testing/UnitTestANARIMapperGlyphs.cxx new file mode 100644 index 000000000..f182124fc --- /dev/null +++ b/vtkm/interop/anari/testing/UnitTestANARIMapperGlyphs.cxx @@ -0,0 +1,77 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtkm::anari +#include +// vtk-m +#include +#include +#include +#include +// std +#include +#include + +#include "ANARITestCommon.h" + +namespace +{ + +void RenderTests() +{ + // Initialize ANARI ///////////////////////////////////////////////////////// + + auto d = loadANARIDevice(); + + // Create VTKm datasets ///////////////////////////////////////////////////// + + vtkm::source::Tangle source; + source.SetPointDimensions({ 32 }); + auto tangle = source.Execute(); + + vtkm::filter::vector_analysis::Gradient gradientFilter; + gradientFilter.SetActiveField("tangle"); + gradientFilter.SetOutputFieldName("Gradient"); + auto tangleGrad = gradientFilter.Execute(tangle); + + // Map data to ANARI objects //////////////////////////////////////////////// + + auto world = anari_cpp::newObject(d); + + vtkm::interop::anari::ANARIActor actor( + tangleGrad.GetCellSet(), tangleGrad.GetCoordinateSystem(), tangleGrad.GetField("Gradient")); + + vtkm::interop::anari::ANARIMapperGlyphs mGlyphs(d, actor); + + auto surface = mGlyphs.GetANARISurface(); + anari_cpp::setParameterArray1D(d, world, "surface", &surface, 1); + anari_cpp::commitParameters(d, world); + + // Render a frame /////////////////////////////////////////////////////////// + + renderTestANARIImage(d, + world, + vtkm::Vec3f_32(0.5f, 1.f, 0.6f), + vtkm::Vec3f_32(0.f, -1.f, 0.f), + vtkm::Vec3f_32(0.f, 0.f, 1.f), + "interop/anari/glyphs.png"); + + // Cleanup ////////////////////////////////////////////////////////////////// + + anari_cpp::release(d, world); + anari_cpp::release(d, d); +} + +} // namespace + +int UnitTestANARIMapperGlyphs(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(RenderTests, argc, argv); +} diff --git a/vtkm/interop/anari/testing/UnitTestANARIMapperPoints.cxx b/vtkm/interop/anari/testing/UnitTestANARIMapperPoints.cxx new file mode 100644 index 000000000..01ed6f39a --- /dev/null +++ b/vtkm/interop/anari/testing/UnitTestANARIMapperPoints.cxx @@ -0,0 +1,84 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtkm::anari +#include +// vtk-m +#include +#include +#include +#include +// std +#include +#include + +#include "ANARITestCommon.h" + +namespace +{ + +void RenderTests() +{ + // Initialize ANARI ///////////////////////////////////////////////////////// + + auto d = loadANARIDevice(); + + // Create VTKm datasets ///////////////////////////////////////////////////// + + vtkm::source::Tangle source; + source.SetPointDimensions({ 32 }); + auto tangle = source.Execute(); + + auto& tangle_field = tangle.GetField("tangle"); + vtkm::Range range; + tangle_field.GetRange(&range); + const auto isovalue = range.Center(); + + vtkm::filter::contour::Contour contourFilter; + contourFilter.SetIsoValue(isovalue); + contourFilter.SetActiveField("tangle"); + auto tangleIso = contourFilter.Execute(tangle); + + // Map data to ANARI objects //////////////////////////////////////////////// + + auto world = anari_cpp::newObject(d); + + vtkm::interop::anari::ANARIActor actor( + tangleIso.GetCellSet(), tangleIso.GetCoordinateSystem(), tangleIso.GetField("tangle")); + + vtkm::interop::anari::ANARIMapperPoints mIso(d, actor); + setColorMap(d, mIso); + + auto surface = mIso.GetANARISurface(); + anari_cpp::setParameterArray1D(d, world, "surface", &surface, 1); + + anari_cpp::commitParameters(d, world); + + // Render a frame /////////////////////////////////////////////////////////// + + renderTestANARIImage(d, + world, + vtkm::Vec3f_32(-0.05, 1.43, 1.87), + vtkm::Vec3f_32(0.32, -0.53, -0.79), + vtkm::Vec3f_32(-0.20, -0.85, 0.49), + "interop/anari/points.png"); + + // Cleanup ////////////////////////////////////////////////////////////////// + + anari_cpp::release(d, world); + anari_cpp::release(d, d); +} + +} // namespace + +int UnitTestANARIMapperPoints(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(RenderTests, argc, argv); +} diff --git a/vtkm/interop/anari/testing/UnitTestANARIMapperTriangles.cxx b/vtkm/interop/anari/testing/UnitTestANARIMapperTriangles.cxx new file mode 100644 index 000000000..f3df570c0 --- /dev/null +++ b/vtkm/interop/anari/testing/UnitTestANARIMapperTriangles.cxx @@ -0,0 +1,85 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtkm::anari +#include +// vtk-m +#include +#include +#include +#include +// std +#include +#include + +#include "ANARITestCommon.h" + +namespace +{ + +void RenderTests() +{ + // Initialize ANARI ///////////////////////////////////////////////////////// + + auto d = loadANARIDevice(); + + // Create VTKm datasets ///////////////////////////////////////////////////// + + vtkm::source::Tangle source; + source.SetPointDimensions({ 32 }); + auto tangle = source.Execute(); + + auto& tangle_field = tangle.GetField("tangle"); + vtkm::Range range; + tangle_field.GetRange(&range); + const auto isovalue = range.Center(); + + vtkm::filter::contour::Contour contourFilter; + contourFilter.SetIsoValue(isovalue); + contourFilter.SetActiveField("tangle"); + auto tangleIso = contourFilter.Execute(tangle); + + // Map data to ANARI objects //////////////////////////////////////////////// + + auto world = anari_cpp::newObject(d); + + vtkm::interop::anari::ANARIActor actor( + tangleIso.GetCellSet(), tangleIso.GetCoordinateSystem(), tangleIso.GetField("tangle")); + + vtkm::interop::anari::ANARIMapperTriangles mIso(d, actor); + mIso.SetCalculateNormals(true); + setColorMap(d, mIso); + + auto surface = mIso.GetANARISurface(); + anari_cpp::setParameterArray1D(d, world, "surface", &surface, 1); + + anari_cpp::commitParameters(d, world); + + // Render a frame /////////////////////////////////////////////////////////// + + renderTestANARIImage(d, + world, + vtkm::Vec3f_32(-0.05, 1.43, 1.87), + vtkm::Vec3f_32(0.32, -0.53, -0.79), + vtkm::Vec3f_32(-0.20, -0.85, 0.49), + "interop/anari/isosurface.png"); + + // Cleanup ////////////////////////////////////////////////////////////////// + + anari_cpp::release(d, world); + anari_cpp::release(d, d); +} + +} // namespace + +int UnitTestANARIMapperTriangles(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(RenderTests, argc, argv); +} diff --git a/vtkm/interop/anari/testing/UnitTestANARIMapperVolume.cxx b/vtkm/interop/anari/testing/UnitTestANARIMapperVolume.cxx new file mode 100644 index 000000000..ec9863eef --- /dev/null +++ b/vtkm/interop/anari/testing/UnitTestANARIMapperVolume.cxx @@ -0,0 +1,74 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtkm::anari +#include +// vtk-m +#include +#include +#include +#include +// std +#include +#include + +#include "ANARITestCommon.h" + +namespace +{ + +void RenderTests() +{ + // Initialize ANARI ///////////////////////////////////////////////////////// + + auto d = loadANARIDevice(); + + // Create VTKm datasets ///////////////////////////////////////////////////// + + vtkm::source::Tangle source; + source.SetPointDimensions({ 32 }); + auto tangle = source.Execute(); + + // Map data to ANARI objects //////////////////////////////////////////////// + + auto world = anari_cpp::newObject(d); + + vtkm::interop::anari::ANARIActor actor( + tangle.GetCellSet(), tangle.GetCoordinateSystem(), tangle.GetField("tangle")); + + vtkm::interop::anari::ANARIMapperVolume mVol(d, actor); + setColorMap(d, mVol); + + auto volume = mVol.GetANARIVolume(); + anari_cpp::setParameterArray1D(d, world, "volume", &volume, 1); + + anari_cpp::commitParameters(d, world); + + // Render a frame /////////////////////////////////////////////////////////// + + renderTestANARIImage(d, + world, + vtkm::Vec3f_32(-0.05, 1.43, 1.87), + vtkm::Vec3f_32(0.32, -0.53, -0.79), + vtkm::Vec3f_32(-0.20, -0.85, 0.49), + "interop/anari/volume.png"); + + // Cleanup ////////////////////////////////////////////////////////////////// + + anari_cpp::release(d, world); + anari_cpp::release(d, d); +} + +} // namespace + +int UnitTestANARIMapperVolume(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(RenderTests, argc, argv); +} diff --git a/vtkm/interop/anari/testing/UnitTestANARIScene.cxx b/vtkm/interop/anari/testing/UnitTestANARIScene.cxx new file mode 100644 index 000000000..3959a4bab --- /dev/null +++ b/vtkm/interop/anari/testing/UnitTestANARIScene.cxx @@ -0,0 +1,110 @@ +//============================================================================ +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//============================================================================ + +// vtkm::anari +#include +#include +#include +#include +// vtk-m +#include +#include +#include +#include +#include +// std +#include +#include + +#include "ANARITestCommon.h" + +namespace +{ + +void RenderTests() +{ + // Initialize ANARI ///////////////////////////////////////////////////////// + + auto d = loadANARIDevice(); + + // Create VTKm datasets ///////////////////////////////////////////////////// + + vtkm::source::Tangle source; + source.SetPointDimensions({ 32 }); + auto tangle = source.Execute(); + + auto& tangle_field = tangle.GetField("tangle"); + vtkm::Range range; + tangle_field.GetRange(&range); + const auto isovalue = range.Center(); + + vtkm::filter::contour::Contour contourFilter; + contourFilter.SetIsoValue(isovalue); + contourFilter.SetActiveField(tangle_field.GetName()); + auto tangleIso = contourFilter.Execute(tangle); + + vtkm::filter::vector_analysis::Gradient gradientFilter; + gradientFilter.SetActiveField(tangle_field.GetName()); + gradientFilter.SetOutputFieldName("Gradient"); + auto tangleGrad = gradientFilter.Execute(tangle); + + // Map data to ANARI objects //////////////////////////////////////////////// + + vtkm::interop::anari::ANARIScene scene(d); + + auto& mVol = scene.AddMapper(vtkm::interop::anari::ANARIMapperVolume(d)); + mVol.SetName("volume"); + + auto& mIso = scene.AddMapper(vtkm::interop::anari::ANARIMapperTriangles(d)); + mIso.SetName("isosurface"); + mIso.SetCalculateNormals(true); + + auto& mGrad = scene.AddMapper(vtkm::interop::anari::ANARIMapperGlyphs(d)); + mGrad.SetName("gradient"); + + // Render a frame /////////////////////////////////////////////////////////// + + renderTestANARIImage(d, + scene.GetANARIWorld(), + vtkm::Vec3f_32(-0.05, 1.43, 1.87), + vtkm::Vec3f_32(0.32, -0.53, -0.79), + vtkm::Vec3f_32(-0.20, -0.85, 0.49), + "interop/anari/scene-empty-mappers.png"); + + // Render a frame /////////////////////////////////////////////////////////// + + mVol.SetActor({ tangle.GetCellSet(), tangle.GetCoordinateSystem(), tangle.GetField("tangle") }); + mIso.SetActor( + { tangleIso.GetCellSet(), tangleIso.GetCoordinateSystem(), tangleIso.GetField("tangle") }); + mGrad.SetActor( + { tangleGrad.GetCellSet(), tangleGrad.GetCoordinateSystem(), tangleGrad.GetField("Gradient") }); + + setColorMap(d, mVol); + setColorMap(d, mIso); + setColorMap(d, mGrad); + + renderTestANARIImage(d, + scene.GetANARIWorld(), + vtkm::Vec3f_32(-0.05, 1.43, 1.87), + vtkm::Vec3f_32(0.32, -0.53, -0.79), + vtkm::Vec3f_32(-0.20, -0.85, 0.49), + "interop/anari/scene.png"); + + // Cleanup ////////////////////////////////////////////////////////////////// + + anari_cpp::release(d, d); +} + +} // namespace + +int UnitTestANARIScene(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(RenderTests, argc, argv); +} diff --git a/vtkm/interop/anari/vtkm.module b/vtkm/interop/anari/vtkm.module new file mode 100644 index 000000000..0c6b8de08 --- /dev/null +++ b/vtkm/interop/anari/vtkm.module @@ -0,0 +1,14 @@ +NAME + vtkm_anari +GROUPS + ANARI +DEPENDS + vtkm_filter_field_conversion + vtkm_filter_vector_analysis + vtkm_rendering + vtkm_worklet +TEST_DEPENDS + vtkm_filter_vector_analysis + vtkm_filter_contour + vtkm_rendering_testing + vtkm_source