diff --git a/scripts/build_ascent/build_ascent.sh b/scripts/build_ascent/build_ascent.sh index 4eef315bf..1b9f5233e 100755 --- a/scripts/build_ascent/build_ascent.sh +++ b/scripts/build_ascent/build_ascent.sh @@ -49,6 +49,7 @@ build_raja="${build_raja:=true}" build_umpire="${build_umpire:=true}" build_mfem="${build_mfem:=true}" build_catalyst="${build_catalyst:=false}" +build_anari="${build_anari:=false}" # ascent options build_ascent="${build_ascent:=true}" @@ -558,10 +559,49 @@ fi # build_kokkos fi # if enable_hip + +################ +# anari +################ +anari_version=0.10.0 +anari_src_dir=$(ospath ${source_dir}/ANARI-SDK-${anari_version}) +anari_build_dir=$(ospath ${build_dir}/anari-v${anari_version}) +anari_install_dir=$(ospath ${install_dir}/anari-v${anari_version}/) +anari_tarball=$(ospath ${source_dir}/anari-v${anari_version}.tar.gz) + +# build only if install doesn't exist +if [ ! -d ${anari_install_dir} ]; then +if ${build_anari}; then +if [ ! -d ${anari_src_dir} ]; then + echo "**** Downloading ${anari_tarball}" + curl -L https://github.com/KhronosGroup/ANARI-SDK/archive/refs/tags/v${anari_version}/anari-v${anari_version}.tar.gz -o ${anari_tarball} + tar ${tar_extra_args} -xzf ${anari_tarball} -C ${source_dir} +fi + +echo "**** Configuring anari ${anari_version}" +cmake -S ${anari_src_dir} -B ${anari_build_dir} ${cmake_compiler_settings} \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=${enable_verbose}\ + -DCMAKE_BUILD_TYPE=${build_config} \ + -DBUILD_VIEWER=OFF \ + -DBUILD_CTS=OFF \ + -Danari_BUILD_TESTING=OFF \ + -DCMAKE_INSTALL_PREFIX=${anari_install_dir} \ + +echo "**** Building anari ${anari_version}" +cmake --build ${anari_build_dir} --config ${build_config} -j${build_jobs} +echo "**** Installing anari ${anari_version}" +cmake --install ${anari_build_dir} --config ${build_config} + +fi +else + echo "**** Skipping anari build, install found at: ${anari_install_dir}" +fi # build_anari + + ################ # VTK-m ################ -vtkm_version=v2.1.0 +vtkm_version=v2.2.0 vtkm_src_dir=$(ospath ${source_dir}/vtk-m-${vtkm_version}) vtkm_build_dir=$(ospath ${build_dir}/vtk-m-${vtkm_version}) vtkm_install_dir=$(ospath ${install_dir}/vtk-m-${vtkm_version}/) @@ -575,11 +615,6 @@ if [ ! -d ${vtkm_src_dir} ]; then curl -L https://gitlab.kitware.com/vtk/vtk-m/-/archive/${vtkm_version}/vtk-m-${vtkm_version}.tar.gz -o ${vtkm_tarball} tar ${tar_extra_args} -xzf ${vtkm_tarball} -C ${source_dir} - # apply vtk-m patch - cd ${vtkm_src_dir} - patch -p1 < ${script_dir}/2023_12_06_vtkm-mr3160-rocthrust-fix.patch - patch -p1 < ${script_dir}/2024_05_03_vtkm-mr3215-ext-geom-fix.patch - patch -p1 < ${script_dir}/2024_07_02_vtkm-mr3246-raysubset_bugfix.patch cd ${root_dir} fi @@ -603,6 +638,13 @@ if [[ "$enable_mpicc" == "ON" ]]; then vtkm_extra_cmake_args="${vtkm_extra_cmake_args} -DMPI_CXX_COMPILER=${mpicxx_exe}" fi +if ${build_anari}; then + vtkm_extra_cmake_args="${vtkm_extra_cmake_args} -DVTKm_ENABLE_ANARI=ON" + vtkm_extra_cmake_args="${vtkm_extra_cmake_args} -DANARI_DIR=$anari_install_dir}" + vtkm_extra_cmake_args="${vtkm_extra_cmake_args} -DCMAKE_PREFIX_PATH=${anari_install_dir}" +fi + + echo "**** Configuring VTK-m ${vtkm_version}" cmake -S ${vtkm_src_dir} -B ${vtkm_build_dir} ${cmake_compiler_settings} \ -DCMAKE_VERBOSE_MAKEFILE:BOOL=${enable_verbose}\ @@ -885,17 +927,17 @@ fi # build_catalyst # Ascent ################ # if we are in an ascent checkout, use existing source -ascent_checkout_dir=$(ospath ${script_dir}/../../src) -ascent_checkout_dir=$(abs_path ${ascent_checkout_dir}) -echo ${ascent_checkout_dir} -if [ -d ${ascent_checkout_dir} ]; then - ascent_version=checkout - ascent_src_dir=$(abs_path ${ascent_checkout_dir}) - echo "**** Using existing Ascent source repo checkout: ${ascent_src_dir}" -else - ascent_version=develop - ascent_src_dir=$(ospath ${source_dir}/ascent/src) -fi +#ascent_checkout_dir=$(ospath ${script_dir}/../../src) +#ascent_checkout_dir=$(abs_path ${ascent_checkout_dir}) +#echo ${ascent_checkout_dir} +#if [ -d ${ascent_checkout_dir} ]; then +# ascent_version=checkout +# ascent_src_dir=$(abs_path ${ascent_checkout_dir}) +# echo "**** Using existing Ascent source repo checkout: ${ascent_src_dir}" +#else +ascent_version=develop_anari +ascent_src_dir=$(ospath ${source_dir}/ascent/src) +#fi # otherwise use ascent develop ascent_build_dir=$(ospath ${build_dir}/ascent-${ascent_version}/) @@ -970,6 +1012,11 @@ if ${build_catalyst}; then echo 'set(CATALYST_DIR ' ${catalyst_cmake_dir} ' CACHE PATH "")' >> ${root_dir}/ascent-config.cmake fi +if ${build_anari}; then + echo 'set(ENABLE_ANARI ON CACHE BOOL "")' >> ${root_dir}/ascent-config.cmake + echo 'set(ANARI_DIR ' ${anari_install_dir}' CACHE PATH "")' >> ${root_dir}/ascent-config.cmake +fi + if [[ "$enable_cuda" == "ON" ]]; then echo 'set(ENABLE_CUDA ON CACHE BOOL "")' >> ${root_dir}/ascent-config.cmake echo 'set(CMAKE_CUDA_ARCHITECTURES ' ${CUDA_ARCH} ' CACHE PATH "")' >> ${root_dir}/ascent-config.cmake diff --git a/scripts/build_ascent/build_ascent_anari.sh b/scripts/build_ascent/build_ascent_anari.sh new file mode 100755 index 000000000..4c82509e6 --- /dev/null +++ b/scripts/build_ascent/build_ascent_anari.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +############################################################################## +# Demonstrates how to manually build Ascent and its dependencies, including: +# +# hdf5, conduit, vtk-m, mfem, raja, anari and umpire +# +# usage example: +# env enable_mpi=ON enable_openmp=ON ./build_ascent.sh +# +# +# Assumes: +# - cmake is in your path +# - selected compilers (including nvcc) are in your path or set via env vars +# - [when enabled] MPI and Python (+numpy and mpi4py), are in your path +# +############################################################################## +set -eu -o pipefail + +# 2024-10-14 ANARI support is handled by our unified script +env build_anari=true ./build_ascent.sh diff --git a/scripts/build_ascent/build_ascent_cuda_polaris.sh b/scripts/build_ascent/build_ascent_cuda_polaris.sh index 72b9278fd..6acf1dc8d 100755 --- a/scripts/build_ascent/build_ascent_cuda_polaris.sh +++ b/scripts/build_ascent/build_ascent_cuda_polaris.sh @@ -9,8 +9,16 @@ module load cudatoolkit-standalone module load craype-x86-milan module load cray-python +# export http_proxy="http://proxy-01.pub.alcf.anl.gov:3128" +# export https_proxy="http://proxy-01.pub.alcf.anl.gov:3128" +# export ftp_proxy="http://proxy-01.pub.alcf.anl.gov:3128" +# source /home/qiwu/projects/diva_superbuild/setup-env.sh + export CC=$(which cc) export CXX=$(which CC) export FTN=$(which ftn) -env enable_python=ON enable_mpi=ON enable_fortran=ON raja_enable_vectorization=OFF enable_tests=OFF ./build_ascent_cuda.sh +# env enable_mpi=ON enable_fortran=ON raja_enable_vectorization=OFF enable_tests=OFF ./build_ascent_cuda.sh +# env enable_mpi=ON enable_fortran=ON raja_enable_vectorization=OFF enable_tests=OFF build_ascent=false ./build_ascent_cuda.sh +# env enable_python=ON enable_mpi=ON enable_fortran=ON raja_enable_vectorization=OFF enable_tests=OFF ./build_ascent_cuda.sh +env enable_python=ON enable_mpi=ON enable_fortran=ON raja_enable_vectorization=OFF enable_tests=OFF build_ascent=false ./build_ascent_cuda.sh diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 87c377a8e..d0a382614 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,12 +45,14 @@ option(DRAY_ENABLE_STATS "Enable stats" ON) option(DRAY_USE_DOUBLE_PRECISION "Build Devil Ray with double precision" OFF) # VTK-h Specific Options - option(VTKH_ENABLE_FILTER_CONTOUR_TREE "Build VTK-h contour tree support" ON) # Cuda Specific Options option(ENABLE_CUDA_DEBUG_CPU_ONLY "Enable CUDA CPU debugging" OFF) +# support ANARI rendering backend +option(ENABLE_ANARI "Build ANARI Support" OFF) + ##################################################### # Note: Third party libs like MFEM, FIDES, etc are # enabled when you provide MFEM_DIR, etc diff --git a/src/cmake/Setup3rdParty.cmake b/src/cmake/Setup3rdParty.cmake index f7aebc1a0..cdb71768d 100644 --- a/src/cmake/Setup3rdParty.cmake +++ b/src/cmake/Setup3rdParty.cmake @@ -140,6 +140,16 @@ if (GENTEN_DIR) include(cmake/thirdparty/SetupGenTen.cmake) endif() +################################ +# Setup Catalyst +################################ if (CATALYST_DIR) include(cmake/thirdparty/SetupCatalyst.cmake) endif() + +################################ +# Setup ANARI +################################ +if (ANARI_DIR) + include(cmake/thirdparty/SetupANARI.cmake) +endif() diff --git a/src/cmake/thirdparty/SetupANARI.cmake b/src/cmake/thirdparty/SetupANARI.cmake new file mode 100644 index 000000000..ec3563fa5 --- /dev/null +++ b/src/cmake/thirdparty/SetupANARI.cmake @@ -0,0 +1,35 @@ +############################################################################### +# Copyright (c) Lawrence Livermore National Security, LLC and other Ascent +# Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +# other details. No copyright assignment is required to contribute to Ascent. +############################################################################### + +############################################################################### +# Setup ANARI +############################################################################### + +if(NOT ANARI_DIR) + MESSAGE(FATAL_ERROR "ANARI support needs explicit ANARI_DIR") +endif() + +MESSAGE(STATUS "Looking for ANARI using ANARI_DIR = ${ANARI_DIR}") + +set(ANARI_DIR_ORIG ${ANARI_DIR}) +set(ANARI_FOUND TRUE) + +file(GLOB ANARI_DIR "${ANARI_DIR}/lib/cmake/anari-*") +if(NOT EXISTS ${ANARI_DIR}/anariConfig.cmake) + MESSAGE(FATAL_ERROR "Could not find ANARI CMake at (${ANARI_DIR}/lib/cmake/anari-*)") +endif() + +############################################################################### +# Import ANARI CMake targets +############################################################################### +#find_package(anari REQUIRED) +find_package(anari REQUIRED + NO_DEFAULT_PATH + PATHS ${ANARI_DIR}) +if(NOT TARGET vtkm::anari) + message(FATAL_ERROR "vtkm::anari not found, check your VTK-m install") +endif() + diff --git a/src/config/ascent_setup_deps.cmake b/src/config/ascent_setup_deps.cmake index 072c1acc2..910f77192 100644 --- a/src/config/ascent_setup_deps.cmake +++ b/src/config/ascent_setup_deps.cmake @@ -188,6 +188,35 @@ if(KOKKOS_DIR) PATHS ${KOKKOS_CMAKE_CONFIG_DIR}) endif() +############################################################################### +# Setup ANARI +############################################################################### +if(NOT ANARI_DIR) + set(ANARI_DIR ${ASCENT_ANARI_DIR}) +endif() + +if(EXISTS ${ANARI_DIR}/lib64/cmake/Kokkos/) + set(ANARI_CMAKE_CONFIG_DIR ${ANARI_DIR}/lib64/cmake/anari/) +endif() + +if(EXISTS ${ANARI_DIR}/lib/cmake/Kokkos/) + set(ANARI_CMAKE_CONFIG_DIR ${ANARI_DIR}/lib/cmake/anari/) +endif() + + +if(ANARI_DIR) + if(NOT EXISTS ${ANARI_CMAKE_CONFIG_DIR}/anariConfig.cmake) + MESSAGE(FATAL_ERROR "Could not find ANARI CMake include file (${ANARI_CMAKE_CONFIG_DIR}/anariConfig.cmake)") + endif() + + ############################################################################### + # Import CMake targets + ############################################################################### + find_dependency(anari REQUIRED + NO_DEFAULT_PATH + PATHS ${ANARI_CMAKE_CONFIG_DIR}) +endif() + ############################################################################### # Setup VTK-m ############################################################################### diff --git a/src/libs/ascent/CMakeLists.txt b/src/libs/ascent/CMakeLists.txt index 7c41d157a..683eee60e 100644 --- a/src/libs/ascent/CMakeLists.txt +++ b/src/libs/ascent/CMakeLists.txt @@ -43,6 +43,7 @@ set(ASCENT_DRAY_ENABLED ${ENABLE_DRAY}) set(ASCENT_VTKH_ENABLED ${ENABLE_VTKH}) set(ASCENT_VTKM_ENABLED ${VTKM_FOUND}) +set(ASCENT_ANARI_ENABLED ${ANARI_FOUND}) set(ASCENT_HDF5_ENABLED ${CONDUIT_RELAY_HDF5_ENABLED}) set(ASCENT_MFEM_ENABLED ${MFEM_FOUND}) @@ -70,6 +71,7 @@ if(ASCENT_VTKM_ENABLED) set(ASCENT_VTKM_KOKKOS_ENABLED ${HIP_FOUND}) endif() + # ASCENT_ZZZ_ENTIRES are derived vars. # We mark them as advanced b/c otherwise folks may see them # in ccmake or the cmake gui and think they should be changing them @@ -101,6 +103,7 @@ mark_as_advanced(ASCENT_INSTALL_PREFIX ASCENT_VTKM_OPENMP_ENABLED ASCENT_VTKM_CUDA_ENABLED ASCENT_VTKM_KOKKOS_ENABLED + ASCENT_ANARI_ENABLED ) # gen config header @@ -337,7 +340,6 @@ if(FORTRAN_FOUND) endif() - ################################ # Add python wrappers if python # support was selected @@ -358,6 +360,13 @@ endif() ################################## if(CUDA_FOUND) set_source_files_properties(${ascent_device_sources} PROPERTIES LANGUAGE CUDA) + + set(CUDA_ARCH_FLAGS) + foreach(arch ${CMAKE_CUDA_ARCHITECTURES}) + list(APPEND CUDA_ARCH_FLAGS --generate-code=arch=compute_${arch},code=sm_${arch}) + endforeach() + message(STATUS "CUDA_ARCH_FLAGS: ${CUDA_ARCH_FLAGS}") + endif() if(HIP_FOUND) @@ -431,6 +440,10 @@ if(GENTEN_FOUND) list(APPEND ascent_thirdparty_libs genten) endif() +if(ANARI_FOUND) + list(APPEND ascent_thirdparty_libs vtkm::anari) +endif() + ########################################## # Build a serial version of ascent ########################################## @@ -498,7 +511,6 @@ if (ENABLE_SERIAL) # updated for vtkm 1.9 list(APPEND imported_targets_to_link - vtkm::vtkmdiympi_nompi vtkm::io vtkm::rendering vtkm::filter_clean_grid @@ -555,11 +567,11 @@ if (ENABLE_SERIAL) RESULT locations_on_disk) separate_arguments(params NATIVE_COMMAND "${CMAKE_CUDA_FLAGS} ${locations_on_disk}") - message(STATUS "${CMAKE_CUDA_COMPILER} --device-link ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm.o") + message(STATUS "${CMAKE_CUDA_COMPILER} --device-link ${CUDA_ARCH_FLAGS} ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm.o") add_custom_command(TARGET ascent PRE_LINK DEPENDS ascent rover vtkh - COMMAND ${CMAKE_CUDA_COMPILER} --device-link ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm.o + COMMAND ${CMAKE_CUDA_COMPILER} --device-link ${CUDA_ARCH_FLAGS} ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm.o COMMENT "manual device link step for Ascent" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bcal_vtkm.o DESTINATION lib) @@ -679,7 +691,7 @@ if(MPI_FOUND) # manual device link for CUDA if(VTKM_FOUND AND CUDA_FOUND AND NOT BUILD_SHARED_LIBS) - set(internal_targets_to_link $) + set(internal_targets_to_link $) if(ENABLE_VTKH) list(APPEND internal_targets_to_link @@ -699,7 +711,6 @@ if(MPI_FOUND) # updated for vtkm 1.9 list(APPEND imported_targets_to_link - vtkm::vtkmdiympi_nompi vtkm::io vtkm::rendering vtkm::filter_clean_grid @@ -756,10 +767,10 @@ if(MPI_FOUND) RESULT locations_on_disk) separate_arguments(params NATIVE_COMMAND "${CMAKE_CUDA_FLAGS} ${locations_on_disk}") - message(STATUS "${CMAKE_CUDA_COMPILER} --device-link ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm_mpi.o") + message(STATUS "${CMAKE_CUDA_COMPILER} --device-link ${CUDA_ARCH_FLAGS} ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm_mpi.o") add_custom_command(TARGET ascent_mpi PRE_LINK DEPENDS ascent_mpi rover_mpi - COMMAND ${CMAKE_CUDA_COMPILER} --device-link ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm_mpi.o + COMMAND ${CMAKE_CUDA_COMPILER} --device-link ${CUDA_ARCH_FLAGS} ${params} -lcudadevrt -lcudart_static --output-file bcal_vtkm_mpi.o COMMENT "manual device link step for Ascent parallel" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/bcal_vtkm_mpi.o DESTINATION lib) diff --git a/src/libs/ascent/ascent_config.h.in b/src/libs/ascent/ascent_config.h.in index 7a29d2b39..4e12a71ed 100644 --- a/src/libs/ascent/ascent_config.h.in +++ b/src/libs/ascent/ascent_config.h.in @@ -54,6 +54,7 @@ #cmakedefine ASCENT_RAJA_ENABLED "@RAJA_FOUND@" #cmakedefine ASCENT_VTKM_ENABLED "@VTKM_FOUND@" +#cmakedefine ASCENT_ANARI_ENABLED "@ANARI_FOUND@" #cmakedefine ASCENT_VTKM_OPENMP_ENABLED "@OPENMP_FOUND@" #cmakedefine ASCENT_VTKM_CUDA_ENABLED "@CUDA_FOUND@" diff --git a/src/libs/ascent/runtimes/ascent_main_runtime.cpp b/src/libs/ascent/runtimes/ascent_main_runtime.cpp index 4a2629641..d10c5cc76 100644 --- a/src/libs/ascent/runtimes/ascent_main_runtime.cpp +++ b/src/libs/ascent/runtimes/ascent_main_runtime.cpp @@ -202,11 +202,22 @@ AscentRuntime::Initialize(const conduit::Node &options) } #endif +//What if Kokkos is Serial/OpenMP/TBB w/no backend? +//Ascent may not be the one in charge of initializing kokkos; ex: Genten(?) +//Probably should get a flag from VTKh saying it wants Kokkos for VKTm #if defined(ASCENT_KOKKOS_ENABLED) && defined(ASCENT_VTKM_ENABLED) - int device_count = vtkh::KokkosDeviceCount(); - int rank_device = m_rank % device_count; - vtkh::SelectKokkosDevice(rank_device); + vtkh::SelectKokkosDevice(1); +#ifdef VTKM_KOKKOS_HIP + vtkh::SelectKokkosDevice(1); #endif +#ifdef VTKM_KOKKOS_CUDA + //TODO: Figure out how to get device index for kokkos cuda + //int device_count = vtkh::CUDADeviceCount(); + //int rank_device = m_rank % device_count; + vtkh::SelectKokkosDevice(1); +#endif +#endif + #if defined(ASCENT_UMPIRE_ENABLED) // @@ -1077,10 +1088,6 @@ AscentRuntime::ConvertExtractToFlow(const conduit::Node &extract, py_src_final << "jupyter_bridge()" << std::endl; params["source"] = py_src_final.str(); } - else if(extract_type == "steering") - { - filter_name = "steering"; - } // generic extract support else if(n_extracts.has_child(extract_type)) { diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp index f83503059..9fe6f3682 100644 --- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp +++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp @@ -57,7 +57,6 @@ #include #endif - using namespace flow; //----------------------------------------------------------------------------- @@ -150,7 +149,6 @@ register_builtin() AscentRuntime::register_filter_type("extracts", "vtk"); AscentRuntime::register_filter_type("transforms","mir"); - AscentRuntime::register_filter_type("extracts", "xray"); AscentRuntime::register_filter_type("extracts", "volume"); @@ -189,7 +187,6 @@ register_builtin() #if defined(ASCENT_PYTHON_ENABLED) AscentRuntime::register_filter_type(); #endif - } diff --git a/src/libs/vtkh/CMakeLists.txt b/src/libs/vtkh/CMakeLists.txt index c9dc72818..90c1bce0a 100644 --- a/src/libs/vtkh/CMakeLists.txt +++ b/src/libs/vtkh/CMakeLists.txt @@ -19,6 +19,10 @@ if(KOKKOS_FOUND) set(VTKH_KOKKOS_ENABLED TRUE) endif() +if(ANARI_FOUND) + set(VTKH_ANARI_ENABLED TRUE) +endif() + configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/vtkh_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/vtkh_config.h") @@ -44,6 +48,9 @@ if(KOKKOS_FOUND) list(APPEND vtkh_base_deps Kokkos::kokkos) endif() +if(ANARI_FOUND) + list(APPEND vtkh_base_deps vtkm::anari) +endif() add_subdirectory(utils) diff --git a/src/libs/vtkh/rendering/ANARIVolumeRenderer.cpp b/src/libs/vtkh/rendering/ANARIVolumeRenderer.cpp new file mode 100644 index 000000000..d0ea0bd30 --- /dev/null +++ b/src/libs/vtkh/rendering/ANARIVolumeRenderer.cpp @@ -0,0 +1,745 @@ +#include "ANARIVolumeRenderer.hpp" + +#include +#include +#include + + +#include + +#ifdef VTKH_PARALLEL +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VTKH_OPACITY_CORRECTION 10.f + +namespace vtkh { + +namespace detail +{ + +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 anari_cpp::Device +anari_device_load() +{ + static char* libraryName + = std::getenv("ANARI_LIBRARY") ? std::getenv("ANARI_LIBRARY") + : std::getenv("VTKM_ANARI_LIBRARY") ? std::getenv("VTKM_ANARI_LIBRARY") + : std::getenv("VTKM_TEST_ANARI_LIBRARY") ? std::getenv("VTKM_TEST_ANARI_LIBRARY") // fall back to the old environment variable + : nullptr; + std::cout << "Library loaded: " << (libraryName ? libraryName : "helide") << std::endl; + static bool verbose = std::getenv("VTKM_ANARI_VERBOSE") != nullptr; + static bool debug = std::getenv("VTKM_ANARI_DEBUG_DEVICE") != nullptr; + static char* trace_dir = std::getenv("VTKM_ANARI_DEBUG_TRACE_DIR"); + + auto lib = anari_cpp::loadLibrary(libraryName ? libraryName : "helide", StatusFunc, &verbose); + auto dev = anari_cpp::newDevice(lib, "default"); + anari_cpp::unloadLibrary(lib); + + return dev; +} + +void +set_tfn(vtkm::interop::anari::ANARIMapper& mapper, + anari_cpp::Device& device, + vtkm::cont::ColorTable& color_table, + vtkm::Range& scalar_range) +{ + //auto colorArray = anari_cpp::newArray1D(device, ANARI_FLOAT32_VEC3, 3); + //auto* colors = anari_cpp::map(device, 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(device, colorArray); + + //auto opacityArray = anari_cpp::newArray1D(device, ANARI_FLOAT32, 2); + //auto* opacities = anari_cpp::map(device, opacityArray); + //opacities[0] = 0.f; + //opacities[1] = 1.f; + //anari_cpp::unmap(device, opacityArray); + + //mapper.SetColorTable(color_table); + //mapper.SetANARIColorMap(colorArray, opacityArray, true); + //mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(0.f, 10.f)); + //mapper.SetANARIColorMapOpacityScale(0.5f); + + constexpr int resolution = 256; + + constexpr vtkm::Float32 conversionToFloatSpace = (1.0f / 255.0f); + vtkm::cont::ArrayHandle temp; + { + color_table.Sample(resolution, temp); + } + auto colorPortal = temp.ReadPortal(); + + // Create the color and opacity arrays + auto colorArray = anari_cpp::newArray1D(device, ANARI_FLOAT32_VEC3, resolution); + auto* colors = anari_cpp::map(device, colorArray ); + auto opacityArray = anari_cpp::newArray1D(device, ANARI_FLOAT32, resolution); + auto* opacities = anari_cpp::map(device, opacityArray); + for (vtkm::Id i = 0; i < resolution; ++i) + { + auto color = colorPortal.Get(i); + colors[i] = vtkm::Vec3f_32(color[0], color[1], color[2]) * conversionToFloatSpace; + opacities[i] = color[3] * conversionToFloatSpace; + } + + anari_cpp::unmap(device, colorArray); + anari_cpp::unmap(device, opacityArray); + + mapper.SetANARIColorMap(colorArray, opacityArray, true); + if (scalar_range.IsNonEmpty()) { + mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(scalar_range.Min, scalar_range.Max)); + } + else { + auto range = color_table.GetRange(); + mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(range.Min, range.Max)); + } + mapper.SetANARIColorMapOpacityScale(1.0f); +} + +struct VisOrdering +{ + int m_rank; + int m_domain_index; + int m_order; + float m_minz; +}; + +struct DepthOrder +{ + inline bool operator()(const VisOrdering &lhs, const VisOrdering &rhs) + { + return lhs.m_minz < rhs.m_minz; + } +}; + +struct RankOrder +{ + inline bool operator()(const VisOrdering &lhs, const VisOrdering &rhs) + { + if(lhs.m_rank < rhs.m_rank) + { + return true; + } + else if(lhs.m_rank == rhs.m_rank) + { + return lhs.m_domain_index < rhs.m_domain_index; + } + return false; + } +}; + +} // namespace detail + +ANARIVolumeRenderer::ANARIVolumeRenderer() +{ + std::cerr << "HERE" << std::endl; + if(!m_device) std::cerr <<" devide setup likely failed for some reason??? " << std::endl; + m_device = detail::anari_device_load(); + if(!m_device) std::cerr <<" devide setup likely failed for some reason??? " << std::endl; + m_renderer = anari_cpp::newObject(m_device,"default"); + if(!m_renderer) std::cerr <<" renderer setup likely failed for some reason??? " << std::endl; + m_frame = anari_cpp::newObject(m_device); + if(!m_frame) std::cerr <<" frame setup likely failed for some reason??? " << std::endl; + + for (auto& light : m_lights) + { + anari_cpp::release(m_device, light); + } + m_lights.clear(); + + // create default m_lights + std::cerr << "setting up lights" << std::endl; + anari_cpp::Light sun = anari_cpp::newObject(m_device, "directional"); + anari_cpp::setParameter(m_device, sun, "direction", vtkm::Vec3f_32(0.0f, -1.0f, 0.0f)); + anari_cpp::setParameter(m_device, sun, "irradiance", 2.f); + anari_cpp::setParameter(m_device, sun, "angularDiameter", 0.00925f); + anari_cpp::setParameter(m_device, sun, "radiance", 1.f); + anari_cpp::commitParameters(m_device, sun); + m_lights.push_back(sun); + //old: + typedef vtkm::rendering::MapperVolume TracerType; + m_tracer = std::make_shared(); + this->m_mapper = m_tracer; + //m_tracer->SetCompositeBackground(false); + //// + //// add some default opacity to the color table + //// + m_color_table.AddPointAlpha(0.0f, .02); + m_color_table.AddPointAlpha(.0f, .5); + m_num_samples = 100.f; + //m_has_unstructured = false; +} + +ANARIVolumeRenderer::~ANARIVolumeRenderer() +{ +} + +void +ANARIVolumeRenderer::Update() +{ + VTKH_DATA_OPEN(this->GetName()); +#ifdef VTKH_ENABLE_LOGGING + VTKH_DATA_ADD("device", GetCurrentDevice()); + long long int in_cells = this->m_input->GetNumberOfCells(); + VTKH_DATA_ADD("input_cells", in_cells); + VTKH_DATA_ADD("input_domains", this->m_input->GetNumberOfDomains()); + int in_topo_dims; + //old: + //bool in_structured = this->m_input->IsStructured(in_topo_dims); + //if(in_structured) + //{ + // VTKH_DATA_ADD("in_topology", "structured"); + //} + //else + //{ + // VTKH_DATA_ADD("in_topology", "unstructured"); + //} +#endif + +std::cerr << "before PreExecute!" << std::endl; + PreExecute(); +std::cerr << "before DoExecute!" << std::endl; + DoExecute(); +std::cerr << "before PostExecute!" << std::endl; + PostExecute(); + + VTKH_DATA_CLOSE(); +} + +void ANARIVolumeRenderer::SetColorTable(const vtkm::cont::ColorTable &color_table) +{ + m_color_table = color_table; +} + +void +ANARIVolumeRenderer::DoExecute() +{ + //old from Qi: + //shouldn't need this since calling + //Renderer::PreExecute sets m_range & m_bounds to global values + // Compute value range if necessary + //if (!scalar_range.IsNonEmpty()) + //{ + // auto ranges = dset.GetGlobalRange(field_name); + // auto size = ranges.GetNumberOfValues(); + // if (size != 1) + // { + // ASCENT_ERROR("Anari Volume only supports scalar fields"); + // } + // auto portal = ranges.ReadPortal(); + // for (int cc = 0; cc < size; ++cc) + // { + // auto range = portal.Get(cc); + // scalar_range.Include(range); + // break; + // } + //} + + std::cerr << "HERE DoExecute 1" << std::endl; + // Build Scene + vtkm::interop::anari::ANARIScene scene(m_device); + std::cerr << "INPUT in ANARI: " << std::endl; + m_input->PrintSummary(std::cerr); + for (int i = 0; i < m_input->GetNumberOfDomains(); ++i) + { + //std::cerr << "CELLSET: " << std::endl; + //m_input->GetDomain(i).GetCellSet().PrintSummary(std::cerr); + //std::cerr << "COORDSET: " << std::endl; + //m_input->GetDomain(i).GetCoordinateSystem().PrintSummary(std::cerr); + //std::cerr << "FIELD: " << std::endl; + //m_input->GetDomain(i).GetField(m_field_name).PrintSummary(std::cerr); + if(m_input->IsUnstructured()) + { + auto& mIso = scene.AddMapper(vtkm::interop::anari::ANARIMapperTriangles(m_device)); + mIso.SetName(("isosurface_" + std::to_string(i)).c_str()); + mIso.SetActor({ + m_input->GetDomain(i).GetCellSet(), + m_input->GetDomain(i).GetCoordinateSystem(), + m_input->GetDomain(i).GetField(m_field_name) + }); + mIso.SetCalculateNormals(true); + detail::set_tfn(mIso,m_device,m_color_table,m_range); + } + + auto& mVol = scene.AddMapper(vtkm::interop::anari::ANARIMapperVolume(m_device)); + mVol.SetName(("volume_" + std::to_string(i)).c_str()); + mVol.SetActor({ + m_input->GetDomain(i).GetCellSet(), + m_input->GetDomain(i).GetCoordinateSystem(), + m_input->GetDomain(i).GetField(m_field_name) + }); + detail::set_tfn(mVol,m_device,m_color_table,m_range); + } + std::cerr << "HERE DoExecute 2" << std::endl; + int num_renders = static_cast(m_renders.size()); + std::cerr << "HERE DoExecute 3" << std::endl; + std::cerr << "num renders; " << num_renders << std::endl; + for(int i = 0; i < num_renders; ++i) + { + std::cerr << "render: " << i << std::endl; + std::cerr << "get camera stuff" << std::endl; + vtkm::rendering::Camera cam = m_renders[i].GetCamera(); + vtkm::rendering::Canvas &canvas = m_renders[i].GetCanvas(); + vtkm::Vec4f_32 background = m_renders[i].GetBackgroundColor().Components; + std::string img_name = m_renders[i].GetImageName(); + vtkm::Float32 height = m_renders[i].GetHeight(); + vtkm::Float32 width = m_renders[i].GetWidth(); + std::cerr <<"m_field_name: " << m_field_name << std::endl; + std::cerr <<"img_name: " << img_name << std::endl; + std::cerr << "width and height: " << width << " " << height << std::endl; + // Finalize + //render(scene); + // m_renderer parameters + std::cerr << "set anari parameters" << std::endl; + std::cerr << "m_num_samples"<< m_num_samples << std::endl; + anari_cpp::setParameter(m_device, m_renderer, "background", background); + anari_cpp::setParameter(m_device, m_renderer, "pixelSamples", m_num_samples); + anari_cpp::setParameter(m_device, m_renderer, "ambientRadiance", 0.8f); + anari_cpp::commitParameters(m_device, m_renderer); + + // TODO support all camera parameters + // -- missing parameters: xpan, ypan (through imageRegion) + // + std::cerr << "more cam and anari parameters" << std::endl; + const auto cam_zoom = cam.GetZoom(); + const auto cam_type = cam.GetMode() == vtkm::rendering::Camera::Mode::ThreeD ? "perspective" : "orthographic"; + const auto cam_dir = cam.GetLookAt() - cam.GetPosition(); + // TODO: what is the correct way to apply zoom? + const auto cam_pos = cam_zoom > 0 + ? cam.GetLookAt() - cam_dir / cam_zoom + : cam.GetPosition(); + const auto cam_up = cam.GetViewUp(); + const auto cam_range = cam.GetClippingRange(); + anari_cpp::Camera camera = anari_cpp::newObject(m_device, cam_type); + anari_cpp::setParameter(m_device, camera, "aspect", float(width) / float(height)); + anari_cpp::setParameter(m_device, camera, "position", cam_pos); + anari_cpp::setParameter(m_device, camera, "direction", cam_dir); + anari_cpp::setParameter(m_device, camera, "up", cam_up); + anari_cpp::setParameter(m_device, camera, "near", cam_range.Min); + anari_cpp::setParameter(m_device, camera, "far", cam_range.Max); + std::cerr << "set cam type" << std::endl; + if (cam_type == "perspective") + { + anari_cpp::setParameter(m_device, camera, "fov", cam.GetFieldOfView() / 180.0 * vtkm::Pi()); + } + else + { + anari_cpp::setParameter(m_device, camera, "height", cam.GetXScale() / width * height); + } + anari_cpp::commitParameters(m_device, camera); + +std::cerr << "anari world stuff" << std::endl; + // commit world with lights + auto world = scene.GetANARIWorld(); + anari_cpp::setAndReleaseParameter(m_device, world, "light", + anari_cpp::newArray1D(m_device, m_lights.data(), m_lights.size())); + anari_cpp::commitParameters(m_device, world); + + // m_frame parameters + vtkm::Vec2ui_32 img_size = vtkm::Vec2ui_32(width,height); + anari_cpp::setParameter(m_device, m_frame, "size", img_size); + //anari_cpp::setParameter(m_device, m_frame, "channel.color", ANARI_UFIXED8_VEC4); + anari_cpp::setParameter(m_device, m_frame, "channel.color", ANARI_FLOAT32_VEC4); + anari_cpp::setParameter(m_device, m_frame, "channel.depth", ANARI_FLOAT32); + anari_cpp::setParameter(m_device, m_frame, "world", world); + anari_cpp::setParameter(m_device, m_frame, "camera", camera); + anari_cpp::setParameter(m_device, m_frame, "renderer", m_renderer); + anari_cpp::commitParameters(m_device, m_frame); + +std::cerr <<" anari render" << std::endl; + // render and wait for completion + anari_cpp::render(m_device, m_frame); +std::cerr <<" anari wait" << std::endl; + anari_cpp::wait(m_device, m_frame); + +std::cerr <<" anari colors and depth" << std::endl; + //const auto a_colors = anari_cpp::map(m_device, m_frame, "channel.color"); + const auto a_colors = anari_cpp::map(m_device, m_frame, "channel.color"); + const auto a_depths = anari_cpp::map(m_device, m_frame, "channel.depth"); + + ascent::PNGEncoder encoder; +// encoder.Encode((unsigned char*)a_colors.data, a_colors.width, a_colors.height); + encoder.Encode((float *)a_colors.data, a_colors.width, a_colors.height); + encoder.Save("encoder_image.png"); + auto v_colors = canvas.GetColorBuffer().WritePortal(); + auto v_depths = canvas.GetDepthBuffer().WritePortal(); + std::cerr << "v_clors size " << v_colors.GetNumberOfValues() << std::endl; + std::cerr << "v_depths size " << v_depths.GetNumberOfValues() << std::endl; + const float *d_pixels = anari::map(m_device, m_frame, "channel.depth").data; + int size = width*height; + std::cerr << "size: " << size << std::endl; + std::cerr << "try accessing d_pixels at 237815: " << d_pixels[237815] << std::endl; + std::cerr << "try accessing v_depths at 237879: " << v_depths.Get(237879) << std::endl; + std::cerr << "a_colors height and width: " << a_colors.width << " " << a_colors.height << std::endl; + std::cerr << "a_depths height and width: " << a_depths.width << " " << a_depths.height << std::endl; + for(int pixel = 0; pixel < size; ++pixel) + { + int color_index = pixel*4; + //std::cerr << "color index: " << color_index << std::endl; + vtkm::Vec4f_32 color; + //color[0] = a_colors.data[color_index]; + //color[1] = a_colors.data[color_index+1]; + //color[2] = a_colors.data[color_index+2]; + //color[3] = a_colors.data[color_index+3]; + v_colors.Set(pixel,a_colors.data[pixel]); + //vtkm::rendering::Color color; + //color.SetComponentFromByte(0, a_colors.data[color_index]); + //color.SetComponentFromByte(1, a_colors.data[color_index + 1]); + //color.SetComponentFromByte(2, a_colors.data[color_index + 2]); + //color.SetComponentFromByte(3, a_colors.data[color_index + 3]); + //std::cerr << "get depth" << std::endl; + vtkm::Float32 d = d_pixels[pixel]; + //if(d < 10000) + // std::cerr << "set depth: " << d << " at pixel: " << pixel << std::endl; + v_depths.Set(pixel,d); + + } + + std::cerr <<" set canvas and unmap" << std::endl; + m_mapper->SetCanvas(&canvas); + anari_cpp::unmap(m_device, m_frame, "channel.color"); + anari_cpp::unmap(m_device, m_frame, "channel.depth"); + + // release resources + std::cerr << "release resources" << std::endl; + anari_cpp::release(m_device, camera); + } + std::cerr << "HERE DoExecute 4" << std::endl; + if(m_do_composite) + { + this->Composite(num_renders); + } + std::cerr << "HERE DoExecute 5" << std::endl; +} + + +void +ANARIVolumeRenderer::PreExecute() +{ + Renderer::PreExecute(); + + vtkm::Vec extent; + extent[0] = static_cast(this->m_bounds.X.Length()); + extent[1] = static_cast(this->m_bounds.Y.Length()); + extent[2] = static_cast(this->m_bounds.Z.Length()); + vtkm::Float32 dist = vtkm::Magnitude(extent) / m_num_samples; + m_sample_dist = dist; +} + +void +ANARIVolumeRenderer::PostExecute() +{ + // do nothing and override compositing since + // we already did it +} + +float +ANARIVolumeRenderer::FindMinDepth(const vtkm::rendering::Camera &camera, + const vtkm::Bounds &bounds) const +{ + + vtkm::Vec center = bounds.Center(); + vtkm::Vec fcenter; + fcenter[0] = static_cast(center[0]); + fcenter[1] = static_cast(center[1]); + fcenter[2] = static_cast(center[2]); + vtkm::Vec pos = camera.GetPosition(); + vtkm::Float32 dist = vtkm::Magnitude(fcenter - pos); + return dist; +} + +void +ANARIVolumeRenderer::Composite(const int &num_images) +{ + const int num_domains = static_cast(m_input->GetNumberOfDomains()); + + m_compositor->SetCompositeMode(Compositor::VIS_ORDER_BLEND); + + FindVisibilityOrdering(); + + for(int i = 0; i < num_images; ++i) + { + float* color_buffer = + &GetVTKMPointer(m_renders[i].GetCanvas().GetColorBuffer())[0][0]; + float* depth_buffer = + GetVTKMPointer(m_renders[i].GetCanvas().GetDepthBuffer()); + int height = m_renders[i].GetCanvas().GetHeight(); + int width = m_renders[i].GetCanvas().GetWidth(); + + m_compositor->AddImage(color_buffer, + depth_buffer, + width, + height, + m_visibility_orders[i][0]); + + Image result = m_compositor->Composite(); + const std::string image_name = m_renders[i].GetImageName() + ".png"; +#ifdef VTKH_PARALLEL + if(vtkh::GetMPIRank() == 0) + { +#endif + ImageToCanvas(result, m_renders[i].GetCanvas(), true); +#ifdef VTKH_PARALLEL + } +#endif + m_compositor->ClearImages(); + } // for image +} + +void +ANARIVolumeRenderer::DepthSort(int num_domains, + std::vector &min_depths, + std::vector &local_vis_order) +{ + if(min_depths.size() != num_domains) + { + throw Error("min depths size does not equal the number of domains"); + } + if(local_vis_order.size() != num_domains) + { + throw Error("local vis order not equal to number of domains"); + } +#ifdef VTKH_PARALLEL + int root = 0; + MPI_Comm comm = MPI_Comm_f2c(vtkh::GetMPICommHandle()); + int num_ranks = vtkh::GetMPISize(); + int rank = vtkh::GetMPIRank(); + int *domain_counts = NULL; + int *domain_offsets = NULL; + int *vis_order = NULL; + float *depths = NULL; + + if(rank == root) + { + domain_counts = new int[num_ranks]; + domain_offsets = new int[num_ranks]; + } + + MPI_Gather(&num_domains, + 1, + MPI_INT, + domain_counts, + 1, + MPI_INT, + root, + comm); + + int depths_size = 0; + if(rank == root) + { + //scan for dispacements + domain_offsets[0] = 0; + for(int i = 1; i < num_ranks; ++i) + { + domain_offsets[i] = domain_offsets[i - 1] + domain_counts[i - 1]; + } + + for(int i = 0; i < num_ranks; ++i) + { + depths_size += domain_counts[i]; + } + + depths = new float[depths_size]; + + } + + MPI_Gatherv(&min_depths[0], + num_domains, + MPI_FLOAT, + depths, + domain_counts, + domain_offsets, + MPI_FLOAT, + root, + comm); + + if(rank == root) + { + std::vector order; + order.resize(depths_size); + + for(int i = 0; i < num_ranks; ++i) + { + for(int c = 0; c < domain_counts[i]; ++c) + { + int index = domain_offsets[i] + c; + order[index].m_rank = i; + order[index].m_domain_index = c; + order[index].m_minz = depths[index]; + } + } + + std::sort(order.begin(), order.end(), detail::DepthOrder()); + + for(int i = 0; i < depths_size; ++i) + { + order[i].m_order = i; + } + + std::sort(order.begin(), order.end(), detail::RankOrder()); + + vis_order = new int[depths_size]; + for(int i = 0; i < depths_size; ++i) + { + vis_order[i] = order[i].m_order; + } + } + + MPI_Scatterv(vis_order, + domain_counts, + domain_offsets, + MPI_INT, + &local_vis_order[0], + num_domains, + MPI_INT, + root, + comm); + + if(rank == root) + { + delete[] domain_counts; + delete[] domain_offsets; + delete[] vis_order; + delete[] depths; + } +#else + + std::vector order; + order.resize(num_domains); + + for(int i = 0; i < num_domains; ++i) + { + order[i].m_rank = 0; + order[i].m_domain_index = i; + order[i].m_minz = min_depths[i]; + } + std::sort(order.begin(), order.end(), detail::DepthOrder()); + + for(int i = 0; i < num_domains; ++i) + { + order[i].m_order = i; + } + + std::sort(order.begin(), order.end(), detail::RankOrder()); + + for(int i = 0; i < num_domains; ++i) + { + local_vis_order[i] = order[i].m_order; + } +#endif +} + +void +ANARIVolumeRenderer::FindVisibilityOrdering() +{ + const int num_domains = static_cast(m_input->GetNumberOfDomains()); + const int num_cameras = static_cast(m_renders.size()); + m_visibility_orders.resize(num_cameras); + + for(int i = 0; i < num_cameras; ++i) + { + m_visibility_orders[i].resize(num_domains); + } + + // + // In order for parallel volume rendering to composite correctly, + // we nee to establish a visibility ordering to pass to IceT. + // We will transform the data extents into camera space and + // take the minimum z value. Then sort them while keeping + // track of rank, then pass the list in. + // + std::vector min_depths; + min_depths.resize(num_domains); + + for(int i = 0; i < num_cameras; ++i) + { + const vtkm::rendering::Camera &camera = m_renders[i].GetCamera(); + for(int dom = 0; dom < num_domains; ++dom) + { + vtkm::Bounds bounds = this->m_input->GetDomainBounds(dom); + min_depths[dom] = FindMinDepth(camera, bounds); + } + + DepthSort(num_domains, min_depths, m_visibility_orders[i]); + + } // for each camera +} + +void +ANARIVolumeRenderer::SetNumberOfSamples(const int num_samples) +{ + if(num_samples < 1) + { + throw Error("Volume rendering samples must be greater than 0"); + } + m_num_samples = num_samples; +} + +Renderer::vtkmCanvasPtr +ANARIVolumeRenderer::GetNewCanvas(int width, int height) +{ + return std::make_shared(width, height); +} + +std::string +ANARIVolumeRenderer::GetName() const +{ + return "vtkh::ANARIVolumeRenderer"; +} + +} // namespace vtkh diff --git a/src/libs/vtkh/rendering/ANARIVolumeRenderer.hpp b/src/libs/vtkh/rendering/ANARIVolumeRenderer.hpp new file mode 100644 index 000000000..cff93d8b7 --- /dev/null +++ b/src/libs/vtkh/rendering/ANARIVolumeRenderer.hpp @@ -0,0 +1,52 @@ +#ifndef VTK_H_RENDERER_ANARI_VOLUME_HPP +#define VTK_H_RENDERER_ANARI_VOLUME_HPP + +#include +#include +#include +#include +#include + +namespace vtkh { + + +class VTKH_API ANARIVolumeRenderer : public Renderer +{ +public: + ANARIVolumeRenderer(); + virtual ~ANARIVolumeRenderer(); + std::string GetName() const override; + void SetNumberOfSamples(const int num_samples); + static Renderer::vtkmCanvasPtr GetNewCanvas(int width = 1024, int height = 1024); + + void Update() override; + + virtual void SetColorTable(const vtkm::cont::ColorTable &color_table) override; +protected: + virtual void Composite(const int &num_images) override; + virtual void PreExecute() override; + virtual void DoExecute() override; + virtual void PostExecute() override; + + void FindVisibilityOrdering(); + void DepthSort(int num_domains, + std::vector &min_depths, + std::vector &local_vis_order); + float FindMinDepth(const vtkm::rendering::Camera &camera, + const vtkm::Bounds &bounds) const; + + int m_num_samples; + float m_sample_dist; + std::shared_ptr m_tracer; + vtkm::cont::ColorTable m_corrected_color_table; + std::vector> m_visibility_orders; + + anari_cpp::Device m_device; + anari_cpp::Renderer m_renderer; + anari_cpp::Frame m_frame; + std::vector m_lights; + +}; + +} // namespace vtkh +#endif diff --git a/src/libs/vtkh/rendering/CMakeLists.txt b/src/libs/vtkh/rendering/CMakeLists.txt index bde13fbdc..5de31a3bc 100644 --- a/src/libs/vtkh/rendering/CMakeLists.txt +++ b/src/libs/vtkh/rendering/CMakeLists.txt @@ -2,6 +2,7 @@ # See License.txt #============================================================================== set(vtkh_rendering_headers + ANARIVolumeRenderer.hpp Annotator.hpp AutoCamera.hpp LineRenderer.hpp @@ -16,6 +17,7 @@ set(vtkh_rendering_headers ) set(vtkh_rendering_sources + ANARIVolumeRenderer.cpp Annotator.cpp AutoCamera.cpp LineRenderer.cpp diff --git a/src/libs/vtkh/rendering/Renderer.cpp b/src/libs/vtkh/rendering/Renderer.cpp index fd70ba04f..05b431e08 100644 --- a/src/libs/vtkh/rendering/Renderer.cpp +++ b/src/libs/vtkh/rendering/Renderer.cpp @@ -258,7 +258,6 @@ Renderer::DoExecute() } } - } void diff --git a/src/libs/vtkh/rendering/Scene.cpp b/src/libs/vtkh/rendering/Scene.cpp index e669ec918..e05179dbb 100644 --- a/src/libs/vtkh/rendering/Scene.cpp +++ b/src/libs/vtkh/rendering/Scene.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #ifdef VTKH_PARALLEL @@ -71,6 +72,10 @@ Scene::IsVolume(vtkh::Renderer *renderer) { is_volume = true; } + else if(dynamic_cast(renderer) != nullptr) + { + is_volume = true; + } return is_volume; } @@ -244,6 +249,14 @@ Scene::Render() batch_start = batch_end; } // while +std::cerr << "print ranges in SCENE" << std::endl; +std::cerr << "num ranges: " << ranges.size() << std::endl; +int num_r = ranges.size(); +for(int i = 0; i < num_r; i++) +{ + std::cerr <<"RANGE " << i << " min: " << ranges[i].Min << " max: " << ranges[i].Max << std::endl; + +} } void Scene::SynchDepths(std::vector &renders) diff --git a/src/libs/vtkh/rendering/ascent_runtime_anari_filters.cpp b/src/libs/vtkh/rendering/ascent_runtime_anari_filters.cpp new file mode 100644 index 000000000..a9573b1c6 --- /dev/null +++ b/src/libs/vtkh/rendering/ascent_runtime_anari_filters.cpp @@ -0,0 +1,874 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Copyright (c) Lawrence Livermore National Security, LLC and other Ascent +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Ascent. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + + +//----------------------------------------------------------------------------- +/// +/// file: ascent_runtime_anari_filters.cpp +/// +//----------------------------------------------------------------------------- + +#include "ascent_runtime_anari_filters.hpp" + +//----------------------------------------------------------------------------- +// thirdparty includes +//----------------------------------------------------------------------------- + +// conduit includes +#include +#include + +//----------------------------------------------------------------------------- +// ascent includes +//----------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include + + +#if !defined(ASCENT_VTKM_ENABLED) +#error The Ascent Anari filters require VTK-m. Please rebuild Ascent with VTK-m support. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +// #include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +// mpi +#ifdef ASCENT_MPI_ENABLED +#include +#endif + +#include + +using namespace conduit; +using namespace flow; + +using namespace vtkm::interop::anari; + +//----------------------------------------------------------------------------- +// -- begin ascent:: -- +//----------------------------------------------------------------------------- +namespace ascent +{ + +//----------------------------------------------------------------------------- +// -- begin ascent::runtime -- +//----------------------------------------------------------------------------- +namespace runtime +{ + +//----------------------------------------------------------------------------- +// -- begin ascent::runtime::filters -- +//----------------------------------------------------------------------------- +namespace filters +{ + +namespace detail +{ + +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 anari_cpp::Device +anari_device_load() +{ + static char* libraryName + = std::getenv("ANARI_LIBRARY") ? std::getenv("ANARI_LIBRARY") + : std::getenv("VTKM_ANARI_LIBRARY") ? std::getenv("VTKM_ANARI_LIBRARY") + : std::getenv("VTKM_TEST_ANARI_LIBRARY") ? std::getenv("VTKM_TEST_ANARI_LIBRARY") // fall back to the old environment variable + : nullptr; + static bool verbose = std::getenv("VTKM_ANARI_VERBOSE") != nullptr; + static bool debug = std::getenv("VTKM_ANARI_DEBUG_DEVICE") != nullptr; + static char* trace_dir = std::getenv("VTKM_ANARI_DEBUG_TRACE_DIR"); + + auto lib = anari_cpp::loadLibrary(libraryName ? libraryName : "helide", StatusFunc, &verbose); + auto dev = anari_cpp::newDevice(lib, "default"); + anari_cpp::unloadLibrary(lib); + + if (debug) { + auto g_debug = anari_cpp::loadLibrary("debug", StatusFunc, &verbose); + anari::Device dbg = anariNewDevice(g_debug, "debug"); + anari::setParameter(dbg, dbg, "wrappedDevice", dev); + if (trace_dir) { + anari::setParameter(dbg, dbg, "traceDir", trace_dir); + anari::setParameter(dbg, dbg, "traceMode", "code"); + } + anari::commitParameters(dbg, dbg); + anari::release(dev, dev); + dev = dbg; + anari_cpp::unloadLibrary(g_debug); + } + + return dev; +} + +/* defined in ascent_runtime_anari_filters.cpp */ +std::string +check_color_table_surprises(const conduit::Node &color_table); + +static bool +check_image_names(const conduit::Node ¶ms, conduit::Node &info) +{ + bool res = true; + if (!params.has_path("image_prefix") && !params.has_path("camera/db_name")) + { + res = false; + info.append() = "Anari ray rendering paths must include either " + "a 'image_prefix' (if its a single image) or a " + "'camera/db_name' (if using a cinema camere)"; + } + if (params.has_path("image_prefix") && params.has_path("camera/db_name")) + { + res = false; + info.append() = "Anari ray rendering paths cannot use both " + "a 'image_prefix' (if its a single image) and a " + "'camera/db_name' (if using a cinema camere)"; + } + return res; +} + +static bool +verify_params(const conduit::Node ¶ms, conduit::Node &info) +{ + info.reset(); + + bool res = true; + + std::vector valid_paths; + std::vector ignore_paths; + + res &= check_string("field", params, info, true); + valid_paths.push_back("field"); + + res &= detail::check_image_names(params, info); // check "image_prefix" or "camera/db_name" + valid_paths.push_back("image_prefix"); + + res &= check_numeric("min_value", params, info, false); + res &= check_numeric("max_value", params, info, false); + valid_paths.push_back("min_value"); + valid_paths.push_back("max_value"); + + res &= check_numeric("image_width", params, info, false); + res &= check_numeric("image_height", params, info, false); + valid_paths.push_back("image_width"); + valid_paths.push_back("image_height"); + + // res &= check_string("log_scale",params, info, false); + // res &= check_string("annotations",params, info, false); + // valid_paths.push_back("log_scale"); + // valid_paths.push_back("annotations"); + + valid_paths.push_back("camera/look_at"); + valid_paths.push_back("camera/position"); + valid_paths.push_back("camera/up"); + valid_paths.push_back("camera/fov"); + valid_paths.push_back("camera/xpan"); + valid_paths.push_back("camera/ypan"); + valid_paths.push_back("camera/zoom"); + valid_paths.push_back("camera/near_plane"); + valid_paths.push_back("camera/far_plane"); + valid_paths.push_back("camera/azimuth"); + valid_paths.push_back("camera/elevation"); + + // res &= check_numeric("samples",params, info, false); + // res &= check_string("use_lighing",params, info, false); + // valid_paths.push_back("samples"); + // valid_paths.push_back("use_lighting"); + + ignore_paths.push_back("color_table"); + + std::string surprises = surprise_check(valid_paths, ignore_paths, params); + + if (params.has_path("color_table")) + { + surprises += detail::check_color_table_surprises(params["color_table"]); + } + + if (surprises != "") + { + res = false; + info["errors"].append() = surprises; + } + return res; +} + +//----------------------------------------------------------------------------- +}; // namespace detail +//----------------------------------------------------------------------------- +// -- end ascent::runtime::filters::detail -- +//----------------------------------------------------------------------------- + +anari_cpp::Device gd; +anari_cpp::Renderer gr; + +struct AnariImpl { +public: + anari_cpp::Device device; + anari_cpp::Renderer renderer; + anari_cpp::Frame frame; + std::vector lights; + + std::string field_name; + vtkm::Range scalar_range; // scalar value range set by users + vtkm::cont::ColorTable tfn = vtkm::cont::ColorTable("Cool to Warm"); + + // camera parameters + vtkm::rendering::Camera cam; + + // framebuffer parameters + std::string img_name = "anari_volume"; + vtkm::Vec2ui_32 img_size = vtkm::Vec2ui_32(1024, 768); + + // renderer parameters + vtkm::Vec4f_32 background = vtkm::Vec4f_32(0.0f, 0.0f, 0.0f, 0.f); + int pixelSamples = 128; + +public: + ~AnariImpl(); + AnariImpl(); + void set_tfn(ANARIMapper& mapper); + void set_lights(); + void render_triangles(vtkh::DataSet &dset); + void render_glyphs(vtkh::DataSet &dset); + void render_volume(vtkh::DataSet &dset); + void render(ANARIScene& scene); +}; + +AnariImpl::AnariImpl() +{ + if (!gd) gd = detail::anari_device_load(); + if (!gr) gr = anari_cpp::newObject(gd, "default"); + device = gd; renderer = gr; + // device = detail::anari_device_load(); + // renderer = anari_cpp::newObject(device, "default"); + frame = anari_cpp::newObject(device); + set_lights(); +} + +AnariImpl::~AnariImpl() +{ + for (auto& light : lights) + { + anari_cpp::release(device, light); + } + anari_cpp::release(device, frame); + // anari_cpp::release(device, renderer); + // anari_cpp::release(device, device); +} + +void +AnariImpl::set_tfn(ANARIMapper& mapper) +{ + constexpr int resolution = 256; + + constexpr vtkm::Float32 conversionToFloatSpace = (1.0f / 255.0f); + vtkm::cont::ArrayHandle temp; + { + vtkm::cont::ScopedRuntimeDeviceTracker tracker(vtkm::cont::DeviceAdapterTagSerial{}); + tfn.Sample(resolution, temp); + } + auto colorPortal = temp.ReadPortal(); + + // Create the color and opacity arrays + auto colorArray = anari_cpp::newArray1D(device, ANARI_FLOAT32_VEC3, resolution); + auto* colors = anari_cpp::map(device, colorArray ); + auto opacityArray = anari_cpp::newArray1D(device, ANARI_FLOAT32, resolution); + auto* opacities = anari_cpp::map(device, opacityArray); + for (vtkm::Id i = 0; i < resolution; ++i) + { + auto color = colorPortal.Get(i); + colors[i] = vtkm::Vec3f_32(color[0], color[1], color[2]) * conversionToFloatSpace; + opacities[i] = color[3] * conversionToFloatSpace; + } + + anari_cpp::unmap(device, colorArray); + anari_cpp::unmap(device, opacityArray); + + mapper.SetANARIColorMap(colorArray, opacityArray, true); + if (scalar_range.IsNonEmpty()) { + mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(scalar_range.Min, scalar_range.Max)); + } + else { + auto range = tfn.GetRange(); + mapper.SetANARIColorMapValueRange(vtkm::Vec2f_32(range.Min, range.Max)); + } + mapper.SetANARIColorMapOpacityScale(1.0f); +} + +void +AnariImpl::set_lights() +{ + for (auto& light : lights) + { + anari_cpp::release(device, light); + } + lights.clear(); + + // create default lights + anari_cpp::Light sun = anari_cpp::newObject(device, "directional"); + anari_cpp::setParameter(device, sun, "direction", vtkm::Vec3f_32(0.0f, -1.0f, 0.0f)); + anari_cpp::setParameter(device, sun, "irradiance", 2.f); + anari_cpp::setParameter(device, sun, "angularDiameter", 0.00925f); + anari_cpp::setParameter(device, sun, "radiance", 1.f); + anari_cpp::commitParameters(device, sun); + lights.push_back(sun); +} + +void +AnariImpl::render_triangles(vtkh::DataSet &dset) +{ + // Compute value range if necessary + if (!scalar_range.IsNonEmpty()) + { + auto ranges = dset.GetGlobalRange(field_name); + auto size = ranges.GetNumberOfValues(); + if (size != 1) + { + ASCENT_ERROR("Anari Triangles only supports scalar fields"); + } + auto portal = ranges.ReadPortal(); + for (int cc = 0; cc < size; ++cc) + { + auto range = portal.Get(cc); + scalar_range.Include(range); + break; + } + } + + // static int step = 0; + // for (int i = 0; i < dset.GetNumberOfDomains(); ++i) { + // vtkm::io::VTKDataSetWriter writer("anari_triangles_" + std::to_string(i) + "_" + std::to_string(step) + ".vtk"); + // writer.SetFileType(vtkm::io::FileType::BINARY); + // writer.WriteDataSet(dset.GetDomain(i)); + // } + // step++; + + // Build Scene + ANARIScene scene(device); + for (int i = 0; i < dset.GetNumberOfDomains(); ++i) + { + auto& mTri = scene.AddMapper(vtkm::interop::anari::ANARIMapperTriangles(device)); + mTri.SetName(("triangles_" + std::to_string(i)).c_str()); + mTri.SetActor({ + dset.GetDomain(i).GetCellSet(), + dset.GetDomain(i).GetCoordinateSystem(), + dset.GetDomain(i).GetField(field_name) + }); + mTri.SetCalculateNormals(true); + set_tfn(mTri); + } + + // Finalize + render(scene); +} + +void +AnariImpl::render_glyphs(vtkh::DataSet &dset) +{ + // Compute value range if necessary + if (!scalar_range.IsNonEmpty()) + { + auto ranges = dset.GetGlobalRange(field_name); + auto size = ranges.GetNumberOfValues(); + if (size != 3) + { + ASCENT_ERROR("Anari Glyphs only supports 3-vector fields"); + } + auto portal = ranges.ReadPortal(); + for (int cc = 0; cc < size; ++cc) + { + auto range = portal.Get(cc); + scalar_range.Include(range); + // break; + } + } + + // Build Scene + ANARIScene scene(device); + for (int i = 0; i < dset.GetNumberOfDomains(); ++i) + { + auto& mVol = scene.AddMapper(vtkm::interop::anari::ANARIMapperGlyphs(device)); + mVol.SetName(("glyphs_" + std::to_string(i)).c_str()); + mVol.SetActor({ + dset.GetDomain(i).GetCellSet(), + dset.GetDomain(i).GetCoordinateSystem(), + dset.GetDomain(i).GetField(field_name) + }); + set_tfn(mVol); + } + + // Finalize + render(scene); +} + +void +AnariImpl::render_volume(vtkh::DataSet &dset) +{ + // Compute value range if necessary + if (!scalar_range.IsNonEmpty()) + { + auto ranges = dset.GetGlobalRange(field_name); + auto size = ranges.GetNumberOfValues(); + if (size != 1) + { + ASCENT_ERROR("Anari Volume only supports scalar fields"); + } + auto portal = ranges.ReadPortal(); + for (int cc = 0; cc < size; ++cc) + { + auto range = portal.Get(cc); + scalar_range.Include(range); + break; + } + } + + // Build Scene + ANARIScene scene(device); + for (int i = 0; i < dset.GetNumberOfDomains(); ++i) + { + auto& mVol = scene.AddMapper(vtkm::interop::anari::ANARIMapperVolume(device)); + mVol.SetName(("volume_" + std::to_string(i)).c_str()); + mVol.SetActor({ + dset.GetDomain(i).GetCellSet(), + dset.GetDomain(i).GetCoordinateSystem(), + dset.GetDomain(i).GetField(field_name) + }); + set_tfn(mVol); + } + + // Finalize + render(scene); +} + +void +AnariImpl::render(ANARIScene& scene) +{ + // renderer parameters + anari_cpp::setParameter(device, renderer, "background", background); + anari_cpp::setParameter(device, renderer, "pixelSamples", pixelSamples); + anari_cpp::setParameter(device, renderer, "ambientRadiance", 0.8f); + anari_cpp::commitParameters(device, renderer); + + // TODO support all camera parameters + // -- missing parameters: xpan, ypan (through imageRegion) + // + const auto cam_zoom = cam.GetZoom(); + const auto cam_type = cam.GetMode() == vtkm::rendering::Camera::Mode::ThreeD ? "perspective" : "orthographic"; + const auto cam_dir = cam.GetLookAt() - cam.GetPosition(); + // TODO: what is the correct way to apply zoom? + const auto cam_pos = cam_zoom > 0 + ? cam.GetLookAt() - cam_dir / cam_zoom + : cam.GetPosition(); + const auto cam_up = cam.GetViewUp(); + const auto cam_range = cam.GetClippingRange(); + anari_cpp::Camera camera = anari_cpp::newObject(device, cam_type); + anari_cpp::setParameter(device, camera, "aspect", img_size[0] / float(img_size[1])); + anari_cpp::setParameter(device, camera, "position", cam_pos); + anari_cpp::setParameter(device, camera, "direction", cam_dir); + anari_cpp::setParameter(device, camera, "up", cam_up); + anari_cpp::setParameter(device, camera, "near", cam_range.Min); + anari_cpp::setParameter(device, camera, "far", cam_range.Max); + if (cam_type == "perspective") + { + anari_cpp::setParameter(device, camera, "fov", cam.GetFieldOfView() / 180.0 * vtkm::Pi()); + } + else + { + anari_cpp::setParameter(device, camera, "height", cam.GetXScale() / img_size[0] * img_size[1]); + } + anari_cpp::commitParameters(device, camera); + + // commit world with lights + auto world = scene.GetANARIWorld(); + anari_cpp::setAndReleaseParameter(device, world, "light", + anari_cpp::newArray1D(device, lights.data(), lights.size())); + anari_cpp::commitParameters(device, world); + + // frame parameters + anari_cpp::setParameter(device, frame, "size", img_size); + anari_cpp::setParameter(device, frame, "channel.color", ANARI_UFIXED8_VEC4); + anari_cpp::setParameter(device, frame, "world", world); + anari_cpp::setParameter(device, frame, "camera", camera); + anari_cpp::setParameter(device, frame, "renderer", renderer); + anari_cpp::commitParameters(device, frame); + + // render and wait for completion + anari_cpp::render(device, frame); + anari_cpp::wait(device, frame); + + // on rank 0, access framebuffer and write its content as PNG file + int rank = 0; +#ifdef ASCENT_MPI_ENABLED + rank = vtkm::cont::EnvironmentTracker::GetCommunicator().rank(); +#endif + if (rank == 0) + { + const auto fb = anari_cpp::map(device, frame, "channel.color"); + ascent::PNGEncoder encoder; + encoder.Encode((unsigned char*)fb.data, fb.width, fb.height); + encoder.Save(img_name + ".png"); + anari_cpp::unmap(device, frame, "channel.color"); + } + + // release resources + anari_cpp::release(device, camera); +} + +//----------------------------------------------------------------------------- +static void +parse_params(AnariImpl& self, const conduit::Node ¶ms, const vtkm::Bounds& bounds) +{ + Node meta = Metadata::n_metadata; + int cycle = 0; + if(meta.has_path("cycle")) + { + cycle = meta["cycle"].to_int32(); + } + + // Parse field name + self.field_name = params["field"].as_string(); + + // Parse camera + vtkm::rendering::Camera camera; + camera.ResetToBounds(bounds); // if we don't have camera params, we need to add a default camera + if (params.has_path("camera")) + { + parse_camera(params["camera"], camera); + } + self.cam = camera; + + // Set transfer function + if (params.has_path("color_table")) + { + vtkm::cont::ColorTable color_table = parse_color_table(params["color_table"]); + self.tfn = color_table; + } + + // Set data value range + self.scalar_range = vtkm::Range(); + if (params.has_path("min_value")) + { + self.scalar_range.Min = params["min_value"].to_float64(); + } + if (params.has_path("max_value")) + { + self.scalar_range.Max = params["max_value"].to_float64(); + } + + // This is the path for the default render attached directly to a scene + std::string image_name; + image_name = params["image_prefix"].as_string(); + image_name = expand_family_name(image_name, cycle); + image_name = output_dir(image_name); + self.img_name = image_name; + + int image_width; + int image_height; + parse_image_dims(params, image_width, image_height); + self.img_size = vtkm::Vec2ui_32(image_width, image_height); +} + + + + + +//----------------------------------------------------------------------------- +AnariTriangles::AnariTriangles() + : Filter(), pimpl(new AnariImpl()) +{ + // empty +} + +//----------------------------------------------------------------------------- +AnariTriangles::~AnariTriangles() +{ + // empty +} + +//----------------------------------------------------------------------------- +void +AnariTriangles::declare_interface(Node &i) +{ + i["type_name"] = "anari_pseudocolor"; + i["port_names"].append() = "in"; + i["output_port"] = "false"; +} + +//----------------------------------------------------------------------------- +bool +AnariTriangles::verify_params(const conduit::Node ¶ms, conduit::Node &info) +{ + return detail::verify_params(params, info); +} + +//----------------------------------------------------------------------------- +void +AnariTriangles::execute() +{ + if (!input(0).check_type()) + { + ASCENT_ERROR("Anari Pseudocolor input must be a DataObject"); + } + + DataObject *d_input = input(0); + if (!d_input->is_valid()) + { + return; + } + + // Parse input data as a VTK-h collection + VTKHCollection *collection = d_input->as_vtkh_collection().get(); + std::vector topos = collection->topology_names(); + if (topos.size() != 1) + { + ASCENT_ERROR("Anari Glyphs accepts only one topology"); + } + // Access the first (and only) topology + auto &topo = collection->dataset_by_topology(topos[0]); + + // Check if the field is a scalar field + std::string field_name = params()["field"].as_string(); + if (topo.NumberOfComponents(field_name) != 1) { + ASCENT_ERROR("Anari Pseudocolor only supports scalar fields"); + } + + // It is important to compute the data bounds + vtkm::Bounds bounds = collection->global_bounds(); + + // Initialize ANARI ///////////////////////////////////////////////////////// + parse_params(*pimpl, params(), bounds); + pimpl->render_triangles(topo); + // Finalize ANARI /////////////////////////////////////////////////////////// +} + + + + + +//----------------------------------------------------------------------------- +AnariGlyphs::AnariGlyphs() + : Filter(), pimpl(new AnariImpl()) +{ + // empty +} + +//----------------------------------------------------------------------------- +AnariGlyphs::~AnariGlyphs() +{ + // empty +} + +//----------------------------------------------------------------------------- +void +AnariGlyphs::declare_interface(Node &i) +{ + i["type_name"] = "anari_glyphs"; + i["port_names"].append() = "in"; + i["output_port"] = "false"; +} + +//----------------------------------------------------------------------------- +bool +AnariGlyphs::verify_params(const conduit::Node ¶ms, conduit::Node &info) +{ + return detail::verify_params(params, info); +} + +//----------------------------------------------------------------------------- +void +AnariGlyphs::execute() +{ + if (!input(0).check_type()) + { + ASCENT_ERROR("Anari Glyphs input must be a DataObject"); + } + + DataObject *d_input = input(0); + if (!d_input->is_valid()) + { + return; + } + + // Parse input data as a VTK-h collection + VTKHCollection *collection = d_input->as_vtkh_collection().get(); + std::vector topos = collection->topology_names(); + if (topos.size() != 1) + { + ASCENT_ERROR("Anari Glyphs accepts only one topology"); + } + // Access the first (and only) topology + auto &topo = collection->dataset_by_topology(topos[0]); + + // Check if the field is a scalar field + std::string field_name = params()["field"].as_string(); + if (topo.NumberOfComponents(field_name) != 3) { + ASCENT_ERROR("Anari Glyphs only supports 3-vector fields"); + } + + // It is important to compute the data bounds + vtkm::Bounds bounds = collection->global_bounds(); + + // Initialize ANARI ///////////////////////////////////////////////////////// + parse_params(*pimpl, params(), bounds); + pimpl->render_glyphs(topo); + // Finalize ANARI /////////////////////////////////////////////////////////// +} + + + + +//----------------------------------------------------------------------------- +AnariVolume::AnariVolume() + : Filter(), pimpl(new AnariImpl()) +{ + // empty +} + +//----------------------------------------------------------------------------- +AnariVolume::~AnariVolume() +{ + // empty +} + +//----------------------------------------------------------------------------- +void +AnariVolume::declare_interface(Node &i) +{ + i["type_name"] = "anari_volume"; + i["port_names"].append() = "in"; + i["output_port"] = "false"; +} + +//----------------------------------------------------------------------------- +bool +AnariVolume::verify_params(const conduit::Node ¶ms, conduit::Node &info) +{ + return detail::verify_params(params, info); +} + +//----------------------------------------------------------------------------- +void +AnariVolume::execute() +{ + if (!input(0).check_type()) + { + ASCENT_ERROR("Anari Volume input must be a DataObject"); + } + + DataObject *d_input = input(0); + if (!d_input->is_valid()) + { + return; + } + + // Parse input data as a VTK-h collection + VTKHCollection *collection = d_input->as_vtkh_collection().get(); + std::vector topos = collection->topology_names(); + if (topos.size() != 1) + { + ASCENT_ERROR("Anari Glyphs accepts only one topology"); + } + // Access the first (and only) topology + auto &topo = collection->dataset_by_topology(topos[0]); + + // Check if the field is a scalar field + std::string field_name = params()["field"].as_string(); + if (topo.NumberOfComponents(field_name) != 1) { + ASCENT_ERROR("Anari Volume only supports scalar fields"); + } + + // It is important to compute the data bounds + vtkm::Bounds bounds = collection->global_bounds(); + + // Initialize ANARI ///////////////////////////////////////////////////////// + parse_params(*pimpl, params(), bounds); + pimpl->render_volume(topo); + // Finalize ANARI /////////////////////////////////////////////////////////// +} + +//----------------------------------------------------------------------------- +}; +//----------------------------------------------------------------------------- +// -- end ascent::runtime::filters -- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +}; +//----------------------------------------------------------------------------- +// -- end ascent::runtime -- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +}; +//----------------------------------------------------------------------------- +// -- end ascent:: -- +//----------------------------------------------------------------------------- diff --git a/src/libs/vtkh/rendering/ascent_runtime_anari_filters.hpp b/src/libs/vtkh/rendering/ascent_runtime_anari_filters.hpp new file mode 100644 index 000000000..c7149a499 --- /dev/null +++ b/src/libs/vtkh/rendering/ascent_runtime_anari_filters.hpp @@ -0,0 +1,120 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Copyright (c) Lawrence Livermore National Security, LLC and other Ascent +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Ascent. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + + +//----------------------------------------------------------------------------- +/// +/// file: ascent_runtime_anari_filters.hpp +/// +//----------------------------------------------------------------------------- + +#ifndef ASCENT_RUNTIME_ANARI_FILTERS +#define ASCENT_RUNTIME_ANARI_FILTERS + +#include + +#include + +//----------------------------------------------------------------------------- +// -- begin ascent:: -- +//----------------------------------------------------------------------------- +namespace ascent +{ + +//----------------------------------------------------------------------------- +// -- begin ascent::runtime -- +//----------------------------------------------------------------------------- +namespace runtime +{ + +//----------------------------------------------------------------------------- +// -- begin ascent::runtime::filters -- +//----------------------------------------------------------------------------- +namespace filters +{ + +//----------------------------------------------------------------------------- +/// +/// Anari Filters +/// +//----------------------------------------------------------------------------- + +struct AnariImpl; + +//----------------------------------------------------------------------------- +class ASCENT_API AnariTriangles : public ::flow::Filter +{ +public: + AnariTriangles(); + virtual ~AnariTriangles(); + + virtual void declare_interface(conduit::Node &i); + virtual bool verify_params(const conduit::Node ¶ms, + conduit::Node &info); + virtual void execute(); + +private: + std::shared_ptr pimpl; +}; + +//----------------------------------------------------------------------------- +class ASCENT_API AnariGlyphs : public ::flow::Filter +{ +public: + AnariGlyphs(); + virtual ~AnariGlyphs(); + + virtual void declare_interface(conduit::Node &i); + virtual bool verify_params(const conduit::Node ¶ms, + conduit::Node &info); + virtual void execute(); + +private: + std::shared_ptr pimpl; +}; + +//----------------------------------------------------------------------------- +class ASCENT_API AnariVolume : public ::flow::Filter +{ +public: + AnariVolume(); + virtual ~AnariVolume(); + + virtual void declare_interface(conduit::Node &i); + virtual bool verify_params(const conduit::Node ¶ms, + conduit::Node &info); + virtual void execute(); + +private: + std::shared_ptr pimpl; +}; + +}; +//----------------------------------------------------------------------------- +// -- end ascent::runtime::filters -- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +}; +//----------------------------------------------------------------------------- +// -- end ascent::runtime -- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +}; +//----------------------------------------------------------------------------- +// -- end ascent:: -- +//----------------------------------------------------------------------------- + + + + +#endif +//----------------------------------------------------------------------------- +// -- end header ifdef guard +//----------------------------------------------------------------------------- diff --git a/src/tests/vtkh/CMakeLists.txt b/src/tests/vtkh/CMakeLists.txt index 2f2eac8bd..0abcf1ec6 100644 --- a/src/tests/vtkh/CMakeLists.txt +++ b/src/tests/vtkh/CMakeLists.txt @@ -13,6 +13,7 @@ # Core VTK-h Unit Tests ################################ set(BASIC_TESTS t_vtk-h_smoke + t_vtk-h_anari_volume_renderer t_vtk-h_dataset t_vtk-h_clip t_vtk-h_clip_field diff --git a/src/tests/vtkh/t_vtk-h_anari_volume_renderer.cpp b/src/tests/vtkh/t_vtk-h_anari_volume_renderer.cpp new file mode 100644 index 000000000..5035abb07 --- /dev/null +++ b/src/tests/vtkh/t_vtk-h_anari_volume_renderer.cpp @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +/// +/// file: t_vtk-h_dataset.cpp +/// +//----------------------------------------------------------------------------- + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include "t_vtkm_test_utils.hpp" + +#include + + + +//---------------------------------------------------------------------------- +TEST(vtkh_volume_renderer, vtkh_parallel_render_ustructured) +{ +#ifdef VTKM_ENABLE_KOKKOS + vtkh::InitializeKokkos(); +#endif + + vtkh::DataSet data_set; + + const int base_size = 32; + const int num_blocks = 1; + + for(int i = 0; i < num_blocks; ++i) + { + data_set.AddDomain(CreateTestData(i, num_blocks, base_size), i); + } + + vtkh::IsoVolume iso; + + vtkm::Range iso_range; + iso_range.Min = 10.; + iso_range.Max = 40.; + iso.SetRange(iso_range); + iso.SetField("point_data_Float64"); + iso.SetInput(&data_set); + iso.Update(); + + vtkh::DataSet *iso_output = iso.GetOutput(); + + vtkm::Bounds bounds = iso_output->GetGlobalBounds(); + + vtkm::rendering::Camera camera; + vtkm::Vec pos = camera.GetPosition(); + pos[0]+=.1; + pos[1]+=.1; + camera.SetPosition(pos); + camera.ResetToBounds(bounds); + vtkh::Render render = vtkh::MakeRender(512, + 512, + camera, + *iso_output, + "volume_unstructured"); + + + vtkm::cont::ColorTable color_map("Cool to Warm"); + color_map.AddPointAlpha(0.0, 0.01); + color_map.AddPointAlpha(1.0, 0.6); + + vtkh::ANARIVolumeRenderer tracer; + tracer.SetColorTable(color_map); + tracer.SetInput(iso_output); + tracer.SetField("point_data_Float64"); + + vtkh::Scene scene; + scene.AddRender(render); + scene.AddRenderer(&tracer); + scene.Render(); +} + +TEST(vtkh_volume_renderer, vtkh_parallel_render) +{ +#ifdef VTKM_ENABLE_KOKKOS + vtkh::SelectKokkosDevice(1); +#endif + + vtkh::DataSet data_set; + + const int base_size = 32; + const int num_blocks = 2; + + for(int i = 0; i < num_blocks; ++i) + { + data_set.AddDomain(CreateTestData(i, num_blocks, base_size), i); + } + + vtkm::Bounds bounds = data_set.GetGlobalBounds(); + + vtkm::rendering::Camera camera; + vtkm::Vec pos = camera.GetPosition(); + pos[0]+=.1; + pos[1]+=.1; + camera.SetPosition(pos); + camera.ResetToBounds(bounds); + vtkh::Render render = vtkh::MakeRender(512, + 512, + camera, + data_set, + "volume"); + + + vtkm::cont::ColorTable color_map("Cool to Warm"); + color_map.AddPointAlpha(0.0, 0.01); + color_map.AddPointAlpha(1.0, 0.6); + std::cerr << "input data in UNIT TEST START " << std::endl; + data_set.PrintSummary(std::cerr); + std::cerr << "input data in UNIT TEST END " << std::endl; + vtkh::ANARIVolumeRenderer tracer; + tracer.SetColorTable(color_map); + tracer.SetInput(&data_set); + tracer.SetField("point_data_Float64"); + + vtkh::Scene scene; + scene.AddRender(render); + scene.AddRenderer(&tracer); + scene.Render(); +}