Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Mours <pmours@nvidia.com>2022-03-21 12:58:51 +0300
committerPatrick Mours <pmours@nvidia.com>2022-03-21 15:23:25 +0300
commit08e719910bf2065ef0603cba8cc43ea236b2d090 (patch)
tree59894e7421aaec73691e653d5cc173ce12effbb3
parent9ed63ebb458141661bcbfe81c187f843e9dcbacf (diff)
Cycles: Add Hydra render delegate
This patch adds a [Hydra](https://graphics.pixar.com/usd/release/glossary.html#usdglossary-hydra) render delegate to Cycles, allowing Cycles to be used for rendering in applications that provide a Hydra viewport (e.g. USDView or NVIDIA Omniverse Kit). The implementation was written from scratch against Cycles X, for integration into the Blender repository to make it possible to continue developing it in step with the rest of Cycles. For this purpose it follows the style of the rest of the Cycles code and can be built with a CMake option (`WITH_CYCLES_HYDRA_RENDER_DELEGATE=1`) similar to the existing standalone version of Cycles. Supported features: - CPU/CUDA/OptiX/HIP/Metal support - Camera Settings - Render Settings (automatically queried from Cycles via node type system) - Basic AOVs (color, depth, normal, primId, instanceId) - Lights (Disk, Distant, Dome, Rect, Sphere) - Meshes - Geom Subsets - Subdivision Surfaces (using native Cycles support) - Custom Primvars (converted to Cycles attributes) - Cycles Materials (can be exported to USD using the [universal-scene-description branch of Blender](https://developer.blender.org/diffusion/B/history/universal-scene-description/)) - USD Preview Surface Materials - Curves - Point Clouds - OpenVDB Volumes Still missing features: - Motion Blur - Custom AOVs - ... Since Hydra render delegates need to be built against the exact USD version and other dependencies as the target application is using, this is intended to be built separate from Blender (`WITH_BLENDER=0` CMake option) and with support for library versions different from what Blender is using. As such the CMake build scripts for Windows had to be modified slightly, so that the Cycles Hydra render delegate can e.g. be built with MSVC 2017 again even though Blender requires MSVC 2019 now, and it's possible to specify custom paths to the USD SDK etc. The codebase supports building against the latest USD release 22.03 and all the way back to USD 20.08 (with some limitations). This also includes an optimization for Hydra viewports that display the result using OpenGL, in which case the texture can be kept entirely on the GPU (see display_driver.cpp). Unfortunately this is a bit difficult since Hydra doesn't give any control over the OpenGL context created by an application, so the only way to make it available to Cycles (which is rendering on a separate thread) without disturbing the target application is to create a second OpenGL context that is sharing resources with the primary one. The USD SDK doesn't provide an API to do so, and can't use Blenders Ghost since the render delegate is built without Blender, so have to create it manualy. That is only implemented for Windows right now, so on Linux the optimization is disabled (see `HdCyclesDelegate::IsDisplaySupported()`). --- **To build:** 1. [Set up a Blender build environment](https://wiki.blender.org/wiki/Building_Blender) as usual but download and apply this patch to the Git repository (Download Raw Diff on the right via `Save Link As` and then run `git apply patch.diff` with the downloaded file in your local repository after syncing to latest master branch). 2. Set these CMake variables: ``` WITH_BLENDER=0 WITH_CYCLES_HYDRA_RENDER_DELEGATE=1 USD_INCLUDE_DIRS=<path to your USD build>/include USD_LIBRARY_DIR=<path to your USD build>/lib USD_LIBRARY_PREFIX=<optional, if e.g. USD libs are called "usd_hd.lib", set to "usd_"> ``` 3. Continue following the usual Blender build instructions. After building the INSTALL target, the output directory contains the `hdCycles` shared library and associated resource files which can be loaded as a USD plugin. **To execute:** 4. Copy `hdCycles.dll`/`hdCycles.a` and the `hdCycles` directory from the output directory to the USD plugin directory of the target application, or point a `PXR_PLUGINPATH_NAME` environment variable to the output directory. 5. Launch the target application, it should now automatically detect the Cycles Hydra render delegate. Differential Revision: https://developer.blender.org/D14398
-rw-r--r--CMakeLists.txt8
-rw-r--r--build_files/cmake/Modules/FindUSD.cmake82
-rw-r--r--build_files/cmake/macros.cmake2
-rw-r--r--build_files/cmake/platform/platform_win32.cmake101
-rw-r--r--intern/cycles/CMakeLists.txt19
-rw-r--r--intern/cycles/device/CMakeLists.txt3
-rw-r--r--intern/cycles/hydra/CMakeLists.txt174
-rw-r--r--intern/cycles/hydra/attribute.cpp71
-rw-r--r--intern/cycles/hydra/attribute.h21
-rw-r--r--intern/cycles/hydra/camera.cpp297
-rw-r--r--intern/cycles/hydra/camera.h43
-rw-r--r--intern/cycles/hydra/config.h44
-rw-r--r--intern/cycles/hydra/curves.cpp210
-rw-r--r--intern/cycles/hydra/curves.h41
-rw-r--r--intern/cycles/hydra/display_driver.cpp240
-rw-r--r--intern/cycles/hydra/display_driver.h61
-rw-r--r--intern/cycles/hydra/field.cpp88
-rw-r--r--intern/cycles/hydra/field.h34
-rw-r--r--intern/cycles/hydra/geometry.h54
-rw-r--r--intern/cycles/hydra/geometry.inl247
-rw-r--r--intern/cycles/hydra/instancer.cpp138
-rw-r--r--intern/cycles/hydra/instancer.h45
-rw-r--r--intern/cycles/hydra/light.cpp399
-rw-r--r--intern/cycles/hydra/light.h35
-rw-r--r--intern/cycles/hydra/material.cpp589
-rw-r--r--intern/cycles/hydra/material.h60
-rw-r--r--intern/cycles/hydra/mesh.cpp524
-rw-r--r--intern/cycles/hydra/mesh.h48
-rw-r--r--intern/cycles/hydra/node_util.cpp561
-rw-r--r--intern/cycles/hydra/node_util.h18
-rw-r--r--intern/cycles/hydra/output_driver.cpp78
-rw-r--r--intern/cycles/hydra/output_driver.h23
-rw-r--r--intern/cycles/hydra/plugInfo.json3
-rw-r--r--intern/cycles/hydra/plugin.cpp71
-rw-r--r--intern/cycles/hydra/plugin.h25
-rw-r--r--intern/cycles/hydra/pointcloud.cpp199
-rw-r--r--intern/cycles/hydra/pointcloud.h39
-rw-r--r--intern/cycles/hydra/render_buffer.cpp276
-rw-r--r--intern/cycles/hydra/render_buffer.h85
-rw-r--r--intern/cycles/hydra/render_delegate.cpp514
-rw-r--r--intern/cycles/hydra/render_delegate.h98
-rw-r--r--intern/cycles/hydra/render_pass.cpp175
-rw-r--r--intern/cycles/hydra/render_pass.h34
-rw-r--r--intern/cycles/hydra/resources/plugInfo.json22
-rw-r--r--intern/cycles/hydra/session.cpp170
-rw-r--r--intern/cycles/hydra/session.h59
-rw-r--r--intern/cycles/hydra/volume.cpp91
-rw-r--r--intern/cycles/hydra/volume.h32
-rw-r--r--intern/cycles/integrator/render_scheduler.cpp8
-rw-r--r--intern/cycles/kernel/svm/vertex_color.h51
-rw-r--r--intern/cycles/scene/integrator.cpp2
-rw-r--r--intern/cycles/scene/mesh.cpp8
-rw-r--r--intern/cycles/scene/mesh.h2
-rw-r--r--intern/cycles/session/session.h4
-rw-r--r--intern/cycles/util/tbb.h1
55 files changed, 6219 insertions, 108 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04cedcc6722..d6625a3d5b3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -408,6 +408,8 @@ option(WITH_CYCLES_DEBUG "Build Cycles with options useful for debug
option(WITH_CYCLES_STANDALONE "Build Cycles standalone application" OFF)
option(WITH_CYCLES_STANDALONE_GUI "Build Cycles standalone with GUI" OFF)
+option(WITH_CYCLES_HYDRA_RENDER_DELEGATE "Build Cycles Hydra render delegate" OFF)
+
option(WITH_CYCLES_DEBUG_NAN "Build Cycles with additional asserts for detecting NaNs and invalid values" OFF)
option(WITH_CYCLES_NATIVE_ONLY "Build Cycles with native kernel only (which fits current CPU, use for development only)" OFF)
option(WITH_CYCLES_KERNEL_ASAN "Build Cycles kernels with address sanitizer when WITH_COMPILER_ASAN is on, even if it's very slow" OFF)
@@ -729,9 +731,10 @@ endif()
#-----------------------------------------------------------------------------
# Check for conflicting/unsupported configurations
-if(NOT WITH_BLENDER AND NOT WITH_CYCLES_STANDALONE)
+if(NOT WITH_BLENDER AND NOT WITH_CYCLES_STANDALONE AND NOT WITH_CYCLES_HYDRA_RENDER_DELEGATE)
message(FATAL_ERROR
"At least one of WITH_BLENDER or WITH_CYCLES_STANDALONE "
+ "or WITH_CYCLES_HYDRA_RENDER_DELEGATE "
"must be enabled, nothing to do!"
)
endif()
@@ -1884,14 +1887,13 @@ if(WITH_BLENDER)
# source after intern and extern to gather all
# internal and external library information first, for test linking
add_subdirectory(source)
-elseif(WITH_CYCLES_STANDALONE)
+elseif(WITH_CYCLES_STANDALONE OR WITH_CYCLES_HYDRA_RENDER_DELEGATE)
add_subdirectory(intern/glew-mx)
add_subdirectory(intern/guardedalloc)
add_subdirectory(intern/libc_compat)
add_subdirectory(intern/sky)
add_subdirectory(intern/cycles)
- add_subdirectory(extern/clew)
if(WITH_CYCLES_LOGGING)
if(NOT WITH_SYSTEM_GFLAGS)
add_subdirectory(extern/gflags)
diff --git a/build_files/cmake/Modules/FindUSD.cmake b/build_files/cmake/Modules/FindUSD.cmake
index 840fa2d538f..c8c1f043b63 100644
--- a/build_files/cmake/Modules/FindUSD.cmake
+++ b/build_files/cmake/Modules/FindUSD.cmake
@@ -17,50 +17,60 @@ IF(NOT USD_ROOT_DIR AND NOT $ENV{USD_ROOT_DIR} STREQUAL "")
SET(USD_ROOT_DIR $ENV{USD_ROOT_DIR})
ENDIF()
-SET(_usd_SEARCH_DIRS
- ${USD_ROOT_DIR}
- /opt/lib/usd
-)
+find_package(pxr REQUIRED OFF)
-FIND_PATH(USD_INCLUDE_DIR
- NAMES
- pxr/usd/usd/api.h
- HINTS
- ${_usd_SEARCH_DIRS}
- PATH_SUFFIXES
- include
- DOC "Universal Scene Description (USD) header files"
-)
+if (NOT pxr_FOUND)
-FIND_LIBRARY(USD_LIBRARY
- NAMES
- usd_m usd_ms
- NAMES_PER_DIR
- HINTS
- ${_usd_SEARCH_DIRS}
- PATH_SUFFIXES
- lib64 lib lib/static
- DOC "Universal Scene Description (USD) monolithic library"
-)
+ SET(_usd_SEARCH_DIRS
+ ${USD_ROOT_DIR}
+ /opt/lib/usd
+ )
-IF(${USD_LIBRARY_NOTFOUND})
- set(USD_FOUND FALSE)
-ELSE()
- # handle the QUIETLY and REQUIRED arguments and set USD_FOUND to TRUE if
- # all listed variables are TRUE
- INCLUDE(FindPackageHandleStandardArgs)
- FIND_PACKAGE_HANDLE_STANDARD_ARGS(USD DEFAULT_MSG USD_LIBRARY USD_INCLUDE_DIR)
+ FIND_PATH(USD_INCLUDE_DIR
+ NAMES
+ pxr/usd/usd/api.h
+ HINTS
+ ${_usd_SEARCH_DIRS}
+ PATH_SUFFIXES
+ include
+ DOC "Universal Scene Description (USD) header files"
+ )
+
+ FIND_LIBRARY(USD_LIBRARY
+ NAMES
+ usd_m usd_ms
+ NAMES_PER_DIR
+ HINTS
+ ${_usd_SEARCH_DIRS}
+ PATH_SUFFIXES
+ lib64 lib lib/static
+ DOC "Universal Scene Description (USD) monolithic library"
+ )
- IF(USD_FOUND)
- get_filename_component(USD_LIBRARY_DIR ${USD_LIBRARY} DIRECTORY)
- SET(USD_INCLUDE_DIRS ${USD_INCLUDE_DIR})
- set(USD_LIBRARIES ${USD_LIBRARY})
+ IF(${USD_LIBRARY_NOTFOUND})
+ set(USD_FOUND FALSE)
+ ELSE()
+ # handle the QUIETLY and REQUIRED arguments and set USD_FOUND to TRUE if
+ # all listed variables are TRUE
+ INCLUDE(FindPackageHandleStandardArgs)
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(USD DEFAULT_MSG USD_LIBRARY USD_INCLUDE_DIR)
+
+ IF(USD_FOUND)
+ get_filename_component(USD_LIBRARY_DIR ${USD_LIBRARY} DIRECTORY)
+ SET(USD_INCLUDE_DIRS ${USD_INCLUDE_DIR})
+ set(USD_LIBRARIES ${USD_LIBRARY})
+ ENDIF()
ENDIF()
+
+ UNSET(_usd_SEARCH_DIRS)
+
+ELSE()
+ SET(USD_FOUND ON)
+ SET(USD_INCLUDE_DIR ${PXR_INCLUDE_DIRS})
+ SET(USD_LIBRARIES ${PXR_LIBRARIES})
ENDIF()
MARK_AS_ADVANCED(
USD_INCLUDE_DIR
USD_LIBRARY_DIR
)
-
-UNSET(_usd_SEARCH_DIRS)
diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake
index bf3e56922c9..5508e8f2104 100644
--- a/build_files/cmake/macros.cmake
+++ b/build_files/cmake/macros.cmake
@@ -879,7 +879,7 @@ function(delayed_install
destination)
foreach(f ${files})
- if(IS_ABSOLUTE ${f})
+ if(IS_ABSOLUTE ${f} OR "${base}" STREQUAL "")
set_property(GLOBAL APPEND PROPERTY DELAYED_INSTALL_FILES ${f})
else()
set_property(GLOBAL APPEND PROPERTY DELAYED_INSTALL_FILES ${base}/${f})
diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake
index e2e49ca0bcd..ec0c83195e9 100644
--- a/build_files/cmake/platform/platform_win32.cmake
+++ b/build_files/cmake/platform/platform_win32.cmake
@@ -39,7 +39,7 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
set(WITH_WINDOWS_STRIPPED_PDB OFF)
endif()
else()
- if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.28.29921) # MSVC 2019 16.9.16
+ if(WITH_BLENDER AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.28.29921) # MSVC 2019 16.9.16
message(FATAL_ERROR "Compiler is unsupported, MSVC 2019 16.9.16 or newer is required for building blender.")
endif()
endif()
@@ -252,6 +252,12 @@ if(NOT DEFINED LIBDIR)
elseif(MSVC_VERSION GREATER 1919)
message(STATUS "Visual Studio 2019 detected.")
set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15)
+ elseif(MSVC_VERSION GREATER 1909)
+ message(STATUS "Visual Studio 2017 detected.")
+ set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15)
+ elseif(MSVC_VERSION EQUAL 1900)
+ message(STATUS "Visual Studio 2015 detected.")
+ set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15)
endif()
else()
message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}")
@@ -403,10 +409,10 @@ if(WITH_CODEC_FFMPEG)
endif()
if(WITH_IMAGE_OPENEXR)
- set(OPENEXR_ROOT_DIR ${LIBDIR}/openexr)
- set(OPENEXR_VERSION "2.1")
windows_find_package(OPENEXR REQUIRED)
if(NOT OPENEXR_FOUND)
+ set(OPENEXR_ROOT_DIR ${LIBDIR}/openexr)
+ set(OPENEXR_VERSION "2.1")
warn_hardcoded_paths(OpenEXR)
set(OPENEXR ${LIBDIR}/openexr)
set(OPENEXR_INCLUDE_DIR ${OPENEXR}/include)
@@ -462,15 +468,17 @@ if(WITH_PYTHON)
endif()
if(WITH_BOOST)
- if(WITH_CYCLES AND WITH_CYCLES_OSL)
+ if(WITH_CYCLES_OSL)
set(boost_extra_libs wave)
endif()
- if(WITH_INTERNATIONAL)
- list(APPEND boost_extra_libs locale)
+ if(WITH_BLENDER)
+ if(WITH_INTERNATIONAL)
+ list(APPEND boost_extra_libs locale)
+ endif()
+ set(Boost_USE_STATIC_RUNTIME ON) # prefix lib
+ set(Boost_USE_MULTITHREADED ON) # suffix -mt
+ set(Boost_USE_STATIC_LIBS ON) # suffix -s
endif()
- set(Boost_USE_STATIC_RUNTIME ON) # prefix lib
- set(Boost_USE_MULTITHREADED ON) # suffix -mt
- set(Boost_USE_STATIC_LIBS ON) # suffix -s
if(WITH_WINDOWS_FIND_MODULES)
find_package(Boost COMPONENTS date_time filesystem thread regex system ${boost_extra_libs})
endif()
@@ -505,7 +513,7 @@ if(WITH_BOOST)
debug ${BOOST_LIBPATH}/libboost_thread-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_LIBPATH}/libboost_chrono-${BOOST_DEBUG_POSTFIX}
)
- if(WITH_CYCLES AND WITH_CYCLES_OSL)
+ if(WITH_CYCLES_OSL)
set(BOOST_LIBRARIES ${BOOST_LIBRARIES}
optimized ${BOOST_LIBPATH}/libboost_wave-${BOOST_POSTFIX}
debug ${BOOST_LIBPATH}/libboost_wave-${BOOST_DEBUG_POSTFIX})
@@ -624,21 +632,23 @@ if(WITH_IMAGE_OPENJPEG)
endif()
if(WITH_OPENSUBDIV)
- set(OPENSUBDIV_INCLUDE_DIRS ${LIBDIR}/opensubdiv/include)
- set(OPENSUBDIV_LIBPATH ${LIBDIR}/opensubdiv/lib)
- set(OPENSUBDIV_LIBRARIES
- optimized ${OPENSUBDIV_LIBPATH}/osdCPU.lib
- optimized ${OPENSUBDIV_LIBPATH}/osdGPU.lib
- debug ${OPENSUBDIV_LIBPATH}/osdCPU_d.lib
- debug ${OPENSUBDIV_LIBPATH}/osdGPU_d.lib
- )
- set(OPENSUBDIV_HAS_OPENMP TRUE)
- set(OPENSUBDIV_HAS_TBB FALSE)
- set(OPENSUBDIV_HAS_OPENCL TRUE)
- set(OPENSUBDIV_HAS_CUDA FALSE)
- set(OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK TRUE)
- set(OPENSUBDIV_HAS_GLSL_COMPUTE TRUE)
windows_find_package(OpenSubdiv)
+ if (NOT OpenSubdiv_FOUND)
+ set(OPENSUBDIV_INCLUDE_DIRS ${LIBDIR}/opensubdiv/include)
+ set(OPENSUBDIV_LIBPATH ${LIBDIR}/opensubdiv/lib)
+ set(OPENSUBDIV_LIBRARIES
+ optimized ${OPENSUBDIV_LIBPATH}/osdCPU.lib
+ optimized ${OPENSUBDIV_LIBPATH}/osdGPU.lib
+ debug ${OPENSUBDIV_LIBPATH}/osdCPU_d.lib
+ debug ${OPENSUBDIV_LIBPATH}/osdGPU_d.lib
+ )
+ set(OPENSUBDIV_HAS_OPENMP TRUE)
+ set(OPENSUBDIV_HAS_TBB FALSE)
+ set(OPENSUBDIV_HAS_OPENCL TRUE)
+ set(OPENSUBDIV_HAS_CUDA FALSE)
+ set(OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK TRUE)
+ set(OPENSUBDIV_HAS_GLSL_COMPUTE TRUE)
+ endif()
endif()
if(WITH_SDL)
@@ -659,12 +669,15 @@ if(WITH_SYSTEM_AUDASPACE)
endif()
if(WITH_TBB)
- set(TBB_LIBRARIES optimized ${LIBDIR}/tbb/lib/tbb.lib debug ${LIBDIR}/tbb/lib/tbb_debug.lib)
- set(TBB_INCLUDE_DIR ${LIBDIR}/tbb/include)
- set(TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR})
- if(WITH_TBB_MALLOC_PROXY)
- set(TBB_MALLOC_LIBRARIES optimized ${LIBDIR}/tbb/lib/tbbmalloc.lib debug ${LIBDIR}/tbb/lib/tbbmalloc_debug.lib)
- add_definitions(-DWITH_TBB_MALLOC)
+ windows_find_package(TBB)
+ if (NOT TBB_FOUND)
+ set(TBB_LIBRARIES optimized ${LIBDIR}/tbb/lib/tbb.lib debug ${LIBDIR}/tbb/lib/tbb_debug.lib)
+ set(TBB_INCLUDE_DIR ${LIBDIR}/tbb/include)
+ set(TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR})
+ if(WITH_TBB_MALLOC_PROXY)
+ set(TBB_MALLOC_LIBRARIES optimized ${LIBDIR}/tbb/lib/tbbmalloc.lib debug ${LIBDIR}/tbb/lib/tbbmalloc_debug.lib)
+ add_definitions(-DWITH_TBB_MALLOC)
+ endif()
endif()
endif()
@@ -693,7 +706,7 @@ if(WITH_CODEC_SNDFILE)
set(LIBSNDFILE_LIBRARIES ${LIBSNDFILE_LIBPATH}/libsndfile-1.lib)
endif()
-if(WITH_CYCLES AND WITH_CYCLES_OSL)
+if(WITH_CYCLES_OSL)
set(CYCLES_OSL ${LIBDIR}/osl CACHE PATH "Path to OpenShadingLanguage installation")
set(OSL_SHADER_DIR ${CYCLES_OSL}/shaders)
# Shaders have moved around a bit between OSL versions, check multiple locations
@@ -726,7 +739,7 @@ if(WITH_CYCLES AND WITH_CYCLES_OSL)
endif()
endif()
-if(WITH_CYCLES AND WITH_CYCLES_EMBREE)
+if(WITH_CYCLES_EMBREE)
windows_find_package(Embree)
if(NOT EMBREE_FOUND)
set(EMBREE_INCLUDE_DIRS ${LIBDIR}/embree/include)
@@ -754,17 +767,21 @@ if(WITH_CYCLES AND WITH_CYCLES_EMBREE)
endif()
if(WITH_USD)
- windows_find_package(USD)
- if(NOT USD_FOUND)
+ if(USD_INCLUDE_DIRS AND USD_LIBRARY_DIR)
set(USD_FOUND ON)
- set(USD_INCLUDE_DIRS ${LIBDIR}/usd/include)
- set(USD_RELEASE_LIB ${LIBDIR}/usd/lib/libusd_m.lib)
- set(USD_DEBUG_LIB ${LIBDIR}/usd/lib/libusd_m_d.lib)
- set(USD_LIBRARY_DIR ${LIBDIR}/usd/lib)
- set(USD_LIBRARIES
- debug ${USD_DEBUG_LIB}
- optimized ${USD_RELEASE_LIB}
- )
+ else()
+ windows_find_package(USD)
+ if(NOT USD_FOUND)
+ set(USD_FOUND ON)
+ set(USD_INCLUDE_DIRS ${LIBDIR}/usd/include)
+ set(USD_RELEASE_LIB ${LIBDIR}/usd/lib/libusd_m.lib)
+ set(USD_DEBUG_LIB ${LIBDIR}/usd/lib/libusd_m_d.lib)
+ set(USD_LIBRARY_DIR ${LIBDIR}/usd/lib)
+ set(USD_LIBRARIES
+ debug ${USD_DEBUG_LIB}
+ optimized ${USD_RELEASE_LIB}
+ )
+ endif()
endif()
endif()
diff --git a/intern/cycles/CMakeLists.txt b/intern/cycles/CMakeLists.txt
index ad2b5ab4d7b..4157693b6cb 100644
--- a/intern/cycles/CMakeLists.txt
+++ b/intern/cycles/CMakeLists.txt
@@ -2,8 +2,12 @@
# Copyright 2011-2022 Blender Foundation
# Standalone or with Blender
-if(NOT WITH_BLENDER AND WITH_CYCLES_STANDALONE)
- set(CYCLES_INSTALL_PATH ${CMAKE_INSTALL_PREFIX})
+if(NOT WITH_BLENDER)
+ if(CYCLES_STANDALONE_REPOSITORY OR NOT WITH_CYCLES_HYDRA_RENDER_DELEGATE)
+ set(CYCLES_INSTALL_PATH ${CMAKE_INSTALL_PREFIX})
+ else()
+ set(CYCLES_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/hdCycles/resources)
+ endif()
else()
set(WITH_CYCLES_BLENDER ON)
# WINDOWS_PYTHON_DEBUG needs to write into the user addons folder since it will
@@ -335,6 +339,11 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER_ID MATCHES "Clang")
unset(_has_no_error_unused_macros)
endif()
+if(WITH_CYCLES_HYDRA_RENDER_DELEGATE AND NOT USD_FOUND)
+ message(STATUS "USD not found, disabling WITH_CYCLES_HYDRA_RENDER_DELEGATE")
+ set(WITH_CYCLES_HYDRA_RENDER_DELEGATE OFF)
+endif()
+
if(WITH_CYCLES_CUDA_BINARIES AND (NOT WITH_CYCLES_CUBIN_COMPILER))
if(MSVC)
set(MAX_MSVC 1800)
@@ -395,6 +404,10 @@ if(WITH_GTESTS)
add_subdirectory(test)
endif()
-if(NOT WITH_BLENDER AND WITH_CYCLES_STANDALONE)
+if(WITH_CYCLES_HYDRA_RENDER_DELEGATE)
+ add_subdirectory(hydra)
+endif()
+
+if(NOT WITH_BLENDER)
delayed_do_install(${CMAKE_BINARY_DIR}/bin)
endif()
diff --git a/intern/cycles/device/CMakeLists.txt b/intern/cycles/device/CMakeLists.txt
index 670285fb310..d4e128093fa 100644
--- a/intern/cycles/device/CMakeLists.txt
+++ b/intern/cycles/device/CMakeLists.txt
@@ -197,7 +197,8 @@ cycles_add_library(cycles_device "${LIB}" ${SRC})
source_group("cpu" FILES ${SRC_CPU})
source_group("cuda" FILES ${SRC_CUDA})
source_group("dummy" FILES ${SRC_DUMMY})
+source_group("hip" FILES ${SRC_HIP})
source_group("multi" FILES ${SRC_MULTI})
source_group("metal" FILES ${SRC_METAL})
source_group("optix" FILES ${SRC_OPTIX})
-source_group("common" FILES ${SRC} ${SRC_HEADERS})
+source_group("common" FILES ${SRC_BASE} ${SRC_HEADERS})
diff --git a/intern/cycles/hydra/CMakeLists.txt b/intern/cycles/hydra/CMakeLists.txt
new file mode 100644
index 00000000000..679aa80550b
--- /dev/null
+++ b/intern/cycles/hydra/CMakeLists.txt
@@ -0,0 +1,174 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2022 Blender Foundation
+
+#####################################################################
+# Cycles Hydra render delegate
+#####################################################################
+
+set(INC
+ ..
+)
+set(INC_SYS
+ ${USD_INCLUDE_DIRS}
+ ${GLEW_INCLUDE_DIR}
+)
+
+set(INC_HD_CYCLES
+ attribute.h
+ camera.h
+ config.h
+ curves.h
+ display_driver.h
+ field.h
+ geometry.h
+ geometry.inl
+ instancer.h
+ light.h
+ material.h
+ mesh.h
+ node_util.h
+ output_driver.h
+ pointcloud.h
+ render_buffer.h
+ render_delegate.h
+ render_pass.h
+ session.h
+ volume.h
+)
+
+set(SRC_HD_CYCLES
+ attribute.cpp
+ curves.cpp
+ camera.cpp
+ display_driver.cpp
+ field.cpp
+ instancer.cpp
+ light.cpp
+ material.cpp
+ mesh.cpp
+ node_util.cpp
+ output_driver.cpp
+ pointcloud.cpp
+ render_buffer.cpp
+ render_delegate.cpp
+ render_pass.cpp
+ session.cpp
+ volume.cpp
+)
+
+add_definitions(${GL_DEFINITIONS})
+
+if(WITH_OPENVDB)
+ add_definitions(-DWITH_OPENVDB ${OPENVDB_DEFINITIONS})
+ list(APPEND INC_SYS
+ ${OPENVDB_INCLUDE_DIRS}
+ )
+ list(APPEND LIB
+ ${OPENVDB_LIBRARIES}
+ )
+endif()
+
+include_directories(${INC})
+include_directories(SYSTEM ${INC_SYS})
+
+add_library(hdCyclesStatic STATIC
+ ${SRC_HD_CYCLES}
+ ${INC_HD_CYCLES}
+)
+
+target_compile_options(hdCyclesStatic
+ PRIVATE
+ $<$<CXX_COMPILER_ID:MSVC>:/wd4003 /wd4244 /wd4506>
+ $<$<CXX_COMPILER_ID:GNU>:-Wno-float-conversion -Wno-double-promotion -Wno-deprecated>
+)
+
+target_compile_definitions(hdCyclesStatic
+ PRIVATE
+ GLOG_NO_ABBREVIATED_SEVERITIES=1
+ OSL_DEBUG=$<CONFIG:DEBUG>
+ TBB_USE_DEBUG=$<CONFIG:DEBUG>
+ $<$<CXX_COMPILER_ID:MSVC>:NOMINMAX=1>
+)
+
+target_link_libraries(hdCyclesStatic
+ PRIVATE
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}hd${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}plug${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}tf${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}trace${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}vt${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}work${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}sdf${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}cameraUtil${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}hf${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}pxOsd${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}gf${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}arch${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}hgi${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}glf${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}hdx${CMAKE_LINK_LIBRARY_SUFFIX}
+ ${USD_LIBRARY_DIR}/${USD_LIBRARY_PREFIX}usdGeom${CMAKE_LINK_LIBRARY_SUFFIX}
+ cycles_scene
+ cycles_session
+ cycles_graph
+)
+
+if (USD_PYTHON_LIBRARIES)
+ target_link_libraries(hdCyclesStatic
+ PRIVATE
+ ${USD_PYTHON_LIBRARIES}
+ )
+endif()
+
+set(HdCyclesPluginName hdCycles)
+add_library(${HdCyclesPluginName} SHARED
+ plugin.h
+ plugin.cpp
+)
+
+set_target_properties(${HdCyclesPluginName}
+ PROPERTIES PREFIX ""
+)
+
+target_compile_definitions(${HdCyclesPluginName}
+ PRIVATE
+ MFB_PACKAGE_NAME=${HdCyclesPluginName}
+ MFB_ALT_PACKAGE_NAME=${HdCyclesPluginName}
+ GLOG_NO_ABBREVIATED_SEVERITIES=1
+ OSL_DEBUG=$<CONFIG:DEBUG>
+ TBB_USE_DEBUG=$<CONFIG:DEBUG>
+ $<$<CXX_COMPILER_ID:MSVC>:NOMINMAX=1>
+)
+
+target_link_libraries(${HdCyclesPluginName}
+ hdCyclesStatic
+)
+
+target_link_directories(${HdCyclesPluginName}
+ BEFORE
+ PRIVATE
+ ${USD_LIBRARY_DIR}
+)
+
+cycles_target_link_libraries(${HdCyclesPluginName})
+
+if(WITH_CYCLES_BLENDER)
+ set(CYCLES_HYDRA_INSTALL_PATH "../")
+else()
+ set(CYCLES_HYDRA_INSTALL_PATH ${CMAKE_INSTALL_PREFIX})
+ # Put the root plugInfo.json one level up
+ delayed_install("${CMAKE_CURRENT_SOURCE_DIR}" "plugInfo.json" ${CMAKE_INSTALL_PREFIX})
+endif()
+
+delayed_install("" $<TARGET_FILE:${HdCyclesPluginName}> ${CYCLES_HYDRA_INSTALL_PATH})
+
+set(PLUG_INFO_ROOT "..")
+set(PLUG_INFO_LIBRARY_PATH "../${HdCyclesPluginName}${CMAKE_SHARED_LIBRARY_SUFFIX}")
+set(PLUG_INFO_RESOURCE_PATH "resources")
+
+configure_file(resources/plugInfo.json
+ ${CMAKE_CURRENT_BINARY_DIR}/resources/plugInfo.json
+ @ONLY
+)
+
+delayed_install("${CMAKE_CURRENT_BINARY_DIR}/resources" "plugInfo.json" "${CYCLES_HYDRA_INSTALL_PATH}/${HdCyclesPluginName}/resources")
diff --git a/intern/cycles/hydra/attribute.cpp b/intern/cycles/hydra/attribute.cpp
new file mode 100644
index 00000000000..f22665345e6
--- /dev/null
+++ b/intern/cycles/hydra/attribute.cpp
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/attribute.h"
+#include "scene/attribute.h"
+#include "scene/geometry.h"
+#include "scene/scene.h"
+
+#include <pxr/base/gf/vec2f.h>
+#include <pxr/base/gf/vec3f.h>
+#include <pxr/base/gf/vec4f.h>
+#include <pxr/base/vt/array.h>
+#include <pxr/imaging/hd/tokens.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+void ApplyPrimvars(AttributeSet &attributes,
+ const ustring &name,
+ VtValue value,
+ AttributeElement elem,
+ AttributeStandard std)
+{
+ const void *data = HdGetValueData(value);
+ size_t size = value.GetArraySize();
+
+ const HdType valueType = HdGetValueTupleType(value).type;
+
+ TypeDesc attrType = CCL_NS::TypeUnknown;
+ switch (valueType) {
+ case HdTypeFloat:
+ attrType = CCL_NS::TypeFloat;
+ size *= sizeof(float);
+ break;
+ case HdTypeFloatVec2:
+ attrType = CCL_NS::TypeFloat2;
+ size *= sizeof(float2);
+ static_assert(sizeof(GfVec2f) == sizeof(float2));
+ break;
+ case HdTypeFloatVec3: {
+ attrType = CCL_NS::TypeVector;
+ size *= sizeof(float3);
+ // The Cycles "float3" data type is padded to "float4", so need to convert the array
+ const auto &valueData = value.Get<VtVec3fArray>();
+ VtArray<float3> valueConverted;
+ valueConverted.reserve(valueData.size());
+ for (const GfVec3f &vec : valueData) {
+ valueConverted.push_back(make_float3(vec[0], vec[1], vec[2]));
+ }
+ data = valueConverted.data();
+ value = std::move(valueConverted);
+ break;
+ }
+ case HdTypeFloatVec4:
+ attrType = CCL_NS::TypeFloat4;
+ size *= sizeof(float4);
+ static_assert(sizeof(GfVec4f) == sizeof(float4));
+ break;
+ default:
+ TF_WARN("Unsupported attribute type %d", static_cast<int>(valueType));
+ return;
+ }
+
+ Attribute *const attr = attributes.add(name, attrType, elem);
+ attr->std = std;
+
+ assert(size == attr->buffer.size());
+ std::memcpy(attr->data(), data, size);
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/attribute.h b/intern/cycles/hydra/attribute.h
new file mode 100644
index 00000000000..3277f97f44d
--- /dev/null
+++ b/intern/cycles/hydra/attribute.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "scene/attribute.h"
+
+#include <pxr/base/vt/value.h>
+#include <pxr/imaging/hd/types.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+void ApplyPrimvars(CCL_NS::AttributeSet &attributes,
+ const CCL_NS::ustring &name,
+ PXR_NS::VtValue value,
+ CCL_NS::AttributeElement elem,
+ CCL_NS::AttributeStandard std);
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/camera.cpp b/intern/cycles/hydra/camera.cpp
new file mode 100644
index 00000000000..05f1c32d3c4
--- /dev/null
+++ b/intern/cycles/hydra/camera.cpp
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/camera.h"
+#include "scene/camera.h"
+
+#include <pxr/base/gf/frustum.h>
+#include <pxr/imaging/hd/sceneDelegate.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+extern Transform convert_transform(const GfMatrix4d &matrix);
+
+HdCyclesCamera::HdCyclesCamera(const SdfPath &sprimId) : HdCamera(sprimId)
+{
+#if PXR_VERSION >= 2102
+ // Synchronize default values
+ _horizontalAperture = _data.GetHorizontalAperture() * GfCamera::APERTURE_UNIT;
+ _verticalAperture = _data.GetVerticalAperture() * GfCamera::APERTURE_UNIT;
+ _horizontalApertureOffset = _data.GetHorizontalApertureOffset() * GfCamera::APERTURE_UNIT;
+ _verticalApertureOffset = _data.GetVerticalApertureOffset() * GfCamera::APERTURE_UNIT;
+ _focalLength = _data.GetFocalLength() * GfCamera::FOCAL_LENGTH_UNIT;
+ _clippingRange = _data.GetClippingRange();
+ _fStop = _data.GetFStop();
+ _focusDistance = _data.GetFocusDistance();
+#endif
+}
+
+HdCyclesCamera::~HdCyclesCamera()
+{
+}
+
+HdDirtyBits HdCyclesCamera::GetInitialDirtyBitsMask() const
+{
+ return DirtyBits::AllDirty;
+}
+
+void HdCyclesCamera::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+ if (*dirtyBits == DirtyBits::Clean) {
+ return;
+ }
+
+ VtValue value;
+ const SdfPath &id = GetId();
+
+#if PXR_VERSION >= 2102
+ if (*dirtyBits & DirtyBits::DirtyTransform) {
+ sceneDelegate->SampleTransform(id, &_transformSamples);
+
+ for (size_t i = 0; i < _transformSamples.count; ++i) {
+ if (_transformSamples.times[i] == 0.0f) {
+ _transform = _transformSamples.values[i];
+ _data.SetTransform(_transform);
+ break;
+ }
+ }
+ }
+#else
+ if (*dirtyBits & DirtyBits::DirtyViewMatrix) {
+ sceneDelegate->SampleTransform(id, &_transformSamples);
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->worldToViewMatrix);
+ if (!value.IsEmpty()) {
+ _worldToViewMatrix = value.Get<GfMatrix4d>();
+ _worldToViewInverseMatrix = _worldToViewMatrix.GetInverse();
+ _data.SetTransform(_worldToViewInverseMatrix);
+ }
+ }
+#endif
+
+ if (*dirtyBits & DirtyBits::DirtyProjMatrix) {
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->projectionMatrix);
+ if (!value.IsEmpty()) {
+ _projectionMatrix = value.Get<GfMatrix4d>();
+ const float focalLength = _data.GetFocalLength(); // Get default focal length
+#if PXR_VERSION >= 2102
+ _data.SetFromViewAndProjectionMatrix(GetViewMatrix(), _projectionMatrix, focalLength);
+#else
+ if (_projectionMatrix[2][3] < -0.5) {
+ _data.SetProjection(GfCamera::Perspective);
+
+ const float horizontalAperture = (2.0 * focalLength) / _projectionMatrix[0][0];
+ _data.SetHorizontalAperture(horizontalAperture);
+ _data.SetHorizontalApertureOffset(0.5 * horizontalAperture * _projectionMatrix[2][0]);
+ const float verticalAperture = (2.0 * focalLength) / _projectionMatrix[1][1];
+ _data.SetVerticalAperture(verticalAperture);
+ _data.SetVerticalApertureOffset(0.5 * verticalAperture * _projectionMatrix[2][1]);
+
+ _data.SetClippingRange(
+ GfRange1f(_projectionMatrix[3][2] / (_projectionMatrix[2][2] - 1.0),
+ _projectionMatrix[3][2] / (_projectionMatrix[2][2] + 1.0)));
+ }
+ else {
+ _data.SetProjection(GfCamera::Orthographic);
+
+ const float horizontalAperture = (2.0 / GfCamera::APERTURE_UNIT) / _projectionMatrix[0][0];
+ _data.SetHorizontalAperture(horizontalAperture);
+ _data.SetHorizontalApertureOffset(-0.5 * horizontalAperture * _projectionMatrix[3][0]);
+ const float verticalAperture = (2.0 / GfCamera::APERTURE_UNIT) / _projectionMatrix[1][1];
+ _data.SetVerticalAperture(verticalAperture);
+ _data.SetVerticalApertureOffset(-0.5 * verticalAperture * _projectionMatrix[3][1]);
+
+ const double nearMinusFarHalf = 1.0 / _projectionMatrix[2][2];
+ const double nearPlusFarHalf = nearMinusFarHalf * _projectionMatrix[3][2];
+ _data.SetClippingRange(
+ GfRange1f(nearPlusFarHalf + nearMinusFarHalf, nearPlusFarHalf - nearMinusFarHalf));
+ }
+#endif
+ }
+ }
+
+ if (*dirtyBits & DirtyBits::DirtyWindowPolicy) {
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->windowPolicy);
+ if (!value.IsEmpty()) {
+ _windowPolicy = value.Get<CameraUtilConformWindowPolicy>();
+ }
+ }
+
+ if (*dirtyBits & DirtyBits::DirtyClipPlanes) {
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->clipPlanes);
+ if (!value.IsEmpty()) {
+ _clipPlanes = value.Get<std::vector<GfVec4d>>();
+ }
+ }
+
+ if (*dirtyBits & DirtyBits::DirtyParams) {
+#if PXR_VERSION >= 2102
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->projection);
+ if (!value.IsEmpty()) {
+ _projection = value.Get<Projection>();
+ _data.SetProjection(_projection != Orthographic ? GfCamera::Perspective :
+ GfCamera::Orthographic);
+ }
+#else
+ value = sceneDelegate->GetCameraParamValue(id, UsdGeomTokens->projection);
+ if (!value.IsEmpty()) {
+ _data.SetProjection(value.Get<TfToken>() != UsdGeomTokens->orthographic ?
+ GfCamera::Perspective :
+ GfCamera::Orthographic);
+ }
+#endif
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->horizontalAperture);
+ if (!value.IsEmpty()) {
+ const auto horizontalAperture = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _horizontalAperture = horizontalAperture;
+#endif
+ _data.SetHorizontalAperture(horizontalAperture / GfCamera::APERTURE_UNIT);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->verticalAperture);
+ if (!value.IsEmpty()) {
+ const auto verticalAperture = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _verticalAperture = verticalAperture;
+#endif
+ _data.SetVerticalAperture(verticalAperture / GfCamera::APERTURE_UNIT);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->horizontalApertureOffset);
+ if (!value.IsEmpty()) {
+ const auto horizontalApertureOffset = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _horizontalApertureOffset = horizontalApertureOffset;
+#endif
+ _data.SetHorizontalApertureOffset(horizontalApertureOffset / GfCamera::APERTURE_UNIT);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->verticalApertureOffset);
+ if (!value.IsEmpty()) {
+ const auto verticalApertureOffset = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _verticalApertureOffset = verticalApertureOffset;
+#endif
+ _data.SetVerticalApertureOffset(verticalApertureOffset / GfCamera::APERTURE_UNIT);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->focalLength);
+ if (!value.IsEmpty()) {
+ const auto focalLength = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _focalLength = focalLength;
+#endif
+ _data.SetFocalLength(focalLength / GfCamera::FOCAL_LENGTH_UNIT);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->clippingRange);
+ if (!value.IsEmpty()) {
+ const auto clippingRange = value.Get<GfRange1f>();
+#if PXR_VERSION >= 2102
+ _clippingRange = clippingRange;
+#endif
+ _data.SetClippingRange(clippingRange);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->fStop);
+ if (!value.IsEmpty()) {
+ const auto fStop = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _fStop = fStop;
+#endif
+ _data.SetFStop(fStop);
+ }
+
+ value = sceneDelegate->GetCameraParamValue(id, HdCameraTokens->focusDistance);
+ if (!value.IsEmpty()) {
+ const auto focusDistance = value.Get<float>();
+#if PXR_VERSION >= 2102
+ _focusDistance = focusDistance;
+#endif
+ _data.SetFocusDistance(focusDistance);
+ }
+ }
+
+ *dirtyBits = DirtyBits::Clean;
+}
+
+void HdCyclesCamera::Finalize(HdRenderParam *renderParam)
+{
+ HdCamera::Finalize(renderParam);
+}
+
+void HdCyclesCamera::ApplyCameraSettings(Camera *cam) const
+{
+ ApplyCameraSettings(_data, _windowPolicy, cam);
+
+ array<Transform> motion(_transformSamples.count);
+ for (size_t i = 0; i < _transformSamples.count; ++i)
+ motion[i] = convert_transform(_transformSamples.values[i]) *
+ transform_scale(1.0f, 1.0f, -1.0f);
+ cam->set_motion(motion);
+}
+
+void HdCyclesCamera::ApplyCameraSettings(const GfCamera &dataUnconformedWindow,
+ CameraUtilConformWindowPolicy windowPolicy,
+ Camera *cam)
+{
+ const float width = cam->get_full_width();
+ const float height = cam->get_full_height();
+
+ auto data = dataUnconformedWindow;
+ CameraUtilConformWindow(&data, windowPolicy, width / height);
+
+ static_assert(GfCamera::Perspective == CAMERA_PERSPECTIVE &&
+ GfCamera::Orthographic == CAMERA_ORTHOGRAPHIC);
+ cam->set_camera_type(static_cast<CameraType>(data.GetProjection()));
+
+ auto viewplane = data.GetFrustum().GetWindow();
+ auto focalLength = 1.0f;
+ if (data.GetProjection() == GfCamera::Perspective) {
+ viewplane *= 2.0 / viewplane.GetSize()[1]; // Normalize viewplane
+ focalLength = data.GetFocalLength() * 1e-3f;
+
+ cam->set_fov(GfDegreesToRadians(data.GetFieldOfView(GfCamera::FOVVertical)));
+ }
+
+ cam->set_sensorwidth(data.GetHorizontalAperture() * GfCamera::APERTURE_UNIT);
+ cam->set_sensorheight(data.GetVerticalAperture() * GfCamera::APERTURE_UNIT);
+
+ cam->set_nearclip(data.GetClippingRange().GetMin());
+ cam->set_farclip(data.GetClippingRange().GetMax());
+
+ cam->set_viewplane_left(viewplane.GetMin()[0]);
+ cam->set_viewplane_right(viewplane.GetMax()[0]);
+ cam->set_viewplane_bottom(viewplane.GetMin()[1]);
+ cam->set_viewplane_top(viewplane.GetMax()[1]);
+
+ if (data.GetFStop() != 0.0f) {
+ cam->set_focaldistance(data.GetFocusDistance());
+ cam->set_aperturesize(focalLength / (2.0f * data.GetFStop()));
+ }
+
+ cam->set_matrix(convert_transform(data.GetTransform()) * transform_scale(1.0f, 1.0f, -1.0f));
+}
+
+void HdCyclesCamera::ApplyCameraSettings(const GfMatrix4d &worldToViewMatrix,
+ const GfMatrix4d &projectionMatrix,
+ const std::vector<GfVec4d> &clipPlanes,
+ Camera *cam)
+{
+#if PXR_VERSION >= 2102
+ GfCamera data;
+ data.SetFromViewAndProjectionMatrix(worldToViewMatrix, projectionMatrix);
+
+ ApplyCameraSettings(data, CameraUtilFit, cam);
+#else
+ TF_CODING_ERROR("Not implemented");
+#endif
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/camera.h b/intern/cycles/hydra/camera.h
new file mode 100644
index 00000000000..8b7fed5a6bb
--- /dev/null
+++ b/intern/cycles/hydra/camera.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/base/gf/camera.h>
+#include <pxr/imaging/hd/camera.h>
+#include <pxr/imaging/hd/timeSampleArray.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesCamera final : public PXR_NS::HdCamera {
+ public:
+ HdCyclesCamera(const PXR_NS::SdfPath &sprimId);
+ ~HdCyclesCamera() override;
+
+ void ApplyCameraSettings(CCL_NS::Camera *targetCamera) const;
+
+ static void ApplyCameraSettings(const PXR_NS::GfCamera &cameraData,
+ PXR_NS::CameraUtilConformWindowPolicy windowPolicy,
+ CCL_NS::Camera *targetCamera);
+ static void ApplyCameraSettings(const PXR_NS::GfMatrix4d &worldToViewMatrix,
+ const PXR_NS::GfMatrix4d &projectionMatrix,
+ const std::vector<PXR_NS::GfVec4d> &clipPlanes,
+ CCL_NS::Camera *targetCamera);
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits) override;
+
+ void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ private:
+ PXR_NS::GfCamera _data;
+ PXR_NS::HdTimeSampleArray<PXR_NS::GfMatrix4d, 2> _transformSamples;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/config.h b/intern/cycles/hydra/config.h
new file mode 100644
index 00000000000..034be302d9f
--- /dev/null
+++ b/intern/cycles/hydra/config.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include <pxr/pxr.h>
+
+#define CCL_NS ccl
+#define CCL_NAMESPACE_USING_DIRECTIVE using namespace CCL_NS;
+
+#define HD_CYCLES_NS HdCycles
+#define HDCYCLES_NAMESPACE_OPEN_SCOPE \
+ namespace HD_CYCLES_NS { \
+ CCL_NAMESPACE_USING_DIRECTIVE; \
+ PXR_NAMESPACE_USING_DIRECTIVE;
+#define HDCYCLES_NAMESPACE_CLOSE_SCOPE }
+
+namespace HD_CYCLES_NS {
+class HdCyclesCamera;
+class HdCyclesDelegate;
+class HdCyclesSession;
+class HdCyclesRenderBuffer;
+} // namespace HD_CYCLES_NS
+
+namespace CCL_NS {
+class AttributeSet;
+class BufferParams;
+class Camera;
+class Geometry;
+class Hair;
+class Light;
+class Mesh;
+class Object;
+class ParticleSystem;
+class Pass;
+class PointCloud;
+class Scene;
+class Session;
+class SessionParams;
+class Shader;
+class ShaderGraph;
+class Volume;
+} // namespace CCL_NS
diff --git a/intern/cycles/hydra/curves.cpp b/intern/cycles/hydra/curves.cpp
new file mode 100644
index 00000000000..2007a5d09ca
--- /dev/null
+++ b/intern/cycles/hydra/curves.cpp
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/curves.h"
+#include "hydra/geometry.inl"
+#include "scene/hair.h"
+
+#include <pxr/imaging/hd/extComputationUtils.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesCurves::HdCyclesCurves(const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+ )
+ : HdCyclesGeometry(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ )
+{
+}
+
+HdCyclesCurves::~HdCyclesCurves()
+{
+}
+
+HdDirtyBits HdCyclesCurves::GetInitialDirtyBitsMask() const
+{
+ HdDirtyBits bits = HdCyclesGeometry::GetInitialDirtyBitsMask();
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths |
+ HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyTopology;
+ return bits;
+}
+
+HdDirtyBits HdCyclesCurves::_PropagateDirtyBits(HdDirtyBits bits) const
+{
+ if (bits & (HdChangeTracker::DirtyTopology)) {
+ // Changing topology clears the geometry, so need to populate everything again
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths |
+ HdChangeTracker::DirtyPrimvar;
+ }
+
+ return bits;
+}
+
+void HdCyclesCurves::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
+{
+ if (HdChangeTracker::IsTopologyDirty(dirtyBits, GetId())) {
+ PopulateTopology(sceneDelegate);
+ }
+
+ if (dirtyBits & HdChangeTracker::DirtyPoints) {
+ PopulatePoints(sceneDelegate);
+ }
+
+ if (dirtyBits & HdChangeTracker::DirtyWidths) {
+ PopulateWidths(sceneDelegate);
+ }
+
+ if (dirtyBits & HdChangeTracker::DirtyPrimvar) {
+ PopulatePrimvars(sceneDelegate);
+ }
+
+ rebuild = (_geom->curve_keys_is_modified()) || (_geom->curve_radius_is_modified());
+}
+
+void HdCyclesCurves::PopulatePoints(HdSceneDelegate *sceneDelegate)
+{
+ VtValue value;
+
+ for (const HdExtComputationPrimvarDescriptor &desc :
+ sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(), HdInterpolationVertex)) {
+ if (desc.name == HdTokens->points) {
+ auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
+ const auto valueStoreIt = valueStore.find(desc.name);
+ if (valueStoreIt != valueStore.end()) {
+ value = std::move(valueStoreIt->second);
+ }
+ break;
+ }
+ }
+
+ if (value.IsEmpty()) {
+ value = GetPrimvar(sceneDelegate, HdTokens->points);
+ }
+
+ if (!value.IsHolding<VtVec3fArray>()) {
+ TF_WARN("Invalid points data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &points = value.UncheckedGet<VtVec3fArray>();
+
+ array<float3> pointsDataCycles;
+ pointsDataCycles.reserve(points.size());
+
+ for (const GfVec3f &point : points) {
+ pointsDataCycles.push_back_reserved(make_float3(point[0], point[1], point[2]));
+ }
+
+ _geom->set_curve_keys(pointsDataCycles);
+}
+
+void HdCyclesCurves::PopulateWidths(HdSceneDelegate *sceneDelegate)
+{
+ VtValue value = GetPrimvar(sceneDelegate, HdTokens->widths);
+ const HdInterpolation interpolation = GetPrimvarInterpolation(sceneDelegate, HdTokens->widths);
+
+ if (!value.IsHolding<VtFloatArray>()) {
+ TF_WARN("Invalid widths data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &widths = value.UncheckedGet<VtFloatArray>();
+
+ array<float> radiusDataCycles;
+ radiusDataCycles.reserve(widths.size());
+
+ if (interpolation == HdInterpolationConstant) {
+ TF_VERIFY(widths.size() == 1);
+
+ const float constantRadius = widths[0] * 0.5f;
+
+ for (size_t i = 0; i < _geom->num_keys(); ++i) {
+ radiusDataCycles.push_back_reserved(constantRadius);
+ }
+ }
+ else if (interpolation == HdInterpolationVertex) {
+ TF_VERIFY(widths.size() == _geom->num_keys());
+
+ for (size_t i = 0; i < _geom->num_keys(); ++i) {
+ radiusDataCycles.push_back_reserved(widths[i] * 0.5f);
+ }
+ }
+
+ _geom->set_curve_radius(radiusDataCycles);
+}
+
+void HdCyclesCurves::PopulatePrimvars(HdSceneDelegate *sceneDelegate)
+{
+ Scene *const scene = (Scene *)_geom->get_owner();
+
+ const std::pair<HdInterpolation, AttributeElement> interpolations[] = {
+ std::make_pair(HdInterpolationVertex, ATTR_ELEMENT_CURVE_KEY),
+ std::make_pair(HdInterpolationVarying, ATTR_ELEMENT_CURVE_KEY),
+ std::make_pair(HdInterpolationUniform, ATTR_ELEMENT_CURVE),
+ std::make_pair(HdInterpolationConstant, ATTR_ELEMENT_OBJECT),
+ };
+
+ for (const auto &interpolation : interpolations) {
+ for (const HdPrimvarDescriptor &desc :
+ GetPrimvarDescriptors(sceneDelegate, interpolation.first)) {
+ // Skip special primvars that are handled separately
+ if (desc.name == HdTokens->points || desc.name == HdTokens->widths) {
+ continue;
+ }
+
+ VtValue value = GetPrimvar(sceneDelegate, desc.name);
+ if (value.IsEmpty()) {
+ continue;
+ }
+
+ const ustring name(desc.name.GetString());
+
+ AttributeStandard std = ATTR_STD_NONE;
+ if (desc.role == HdPrimvarRoleTokens->textureCoordinate) {
+ std = ATTR_STD_UV;
+ }
+ else if (desc.name == HdTokens->displayColor &&
+ interpolation.first == HdInterpolationConstant) {
+ if (value.IsHolding<VtVec3fArray>() && value.GetArraySize() == 1) {
+ const GfVec3f color = value.UncheckedGet<VtVec3fArray>()[0];
+ _instances[0]->set_color(make_float3(color[0], color[1], color[2]));
+ }
+ }
+
+ // Skip attributes that are not needed
+ if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
+ _geom->need_attribute(scene, name)) {
+ ApplyPrimvars(_geom->attributes, name, value, interpolation.second, std);
+ }
+ }
+ }
+}
+
+void HdCyclesCurves::PopulateTopology(HdSceneDelegate *sceneDelegate)
+{
+ // Clear geometry before populating it again with updated topology
+ _geom->clear(true);
+
+ HdBasisCurvesTopology topology = GetBasisCurvesTopology(sceneDelegate);
+
+ _geom->reserve_curves(topology.GetNumCurves(), topology.CalculateNeededNumberOfControlPoints());
+
+ const VtIntArray vertCounts = topology.GetCurveVertexCounts();
+
+ for (int curve = 0, key = 0; curve < topology.GetNumCurves(); ++curve) {
+ // Always reference shader at index zero, which is the primitive material
+ _geom->add_curve(key, 0);
+
+ key += vertCounts[curve];
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/curves.h b/intern/cycles/hydra/curves.h
new file mode 100644
index 00000000000..a1ac4a86715
--- /dev/null
+++ b/intern/cycles/hydra/curves.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "hydra/geometry.h"
+
+#include <pxr/imaging/hd/basisCurves.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesCurves final : public HdCyclesGeometry<PXR_NS::HdBasisCurves, CCL_NS::Hair> {
+ public:
+ HdCyclesCurves(const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId = {}
+#endif
+ );
+ ~HdCyclesCurves() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ private:
+ PXR_NS::HdDirtyBits _PropagateDirtyBits(PXR_NS::HdDirtyBits bits) const override;
+
+ void Populate(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdDirtyBits dirtyBits,
+ bool &rebuild) override;
+
+ void PopulatePoints(PXR_NS::HdSceneDelegate *sceneDelegate);
+ void PopulateWidths(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ void PopulatePrimvars(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ void PopulateTopology(PXR_NS::HdSceneDelegate *sceneDelegate);
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/display_driver.cpp b/intern/cycles/hydra/display_driver.cpp
new file mode 100644
index 00000000000..6f6ca35cd31
--- /dev/null
+++ b/intern/cycles/hydra/display_driver.cpp
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#ifdef _WIN32
+// Include first to avoid "NOGDI" definition set in Cycles headers
+# include <Windows.h>
+#endif
+
+#include "hydra/display_driver.h"
+#include "hydra/render_buffer.h"
+#include "hydra/session.h"
+
+#include <GL/glew.h>
+#include <pxr/imaging/hgiGL/texture.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesDisplayDriver::HdCyclesDisplayDriver(HdCyclesSession *renderParam, Hgi *hgi)
+ : _renderParam(renderParam), _hgi(hgi)
+{
+#ifdef _WIN32
+ hdc_ = GetDC(CreateWindowA("STATIC",
+ "HdCycles",
+ WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+ 0,
+ 0,
+ 64,
+ 64,
+ NULL,
+ NULL,
+ GetModuleHandle(NULL),
+ NULL));
+
+ int pixelFormat = GetPixelFormat(wglGetCurrentDC());
+ PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd)};
+ DescribePixelFormat((HDC)hdc_, pixelFormat, sizeof(pfd), &pfd);
+ SetPixelFormat((HDC)hdc_, pixelFormat, &pfd);
+
+ TF_VERIFY(gl_context_ = wglCreateContext((HDC)hdc_));
+ TF_VERIFY(wglShareLists(wglGetCurrentContext(), (HGLRC)gl_context_));
+#endif
+
+ glewInit();
+
+ glGenBuffers(1, &gl_pbo_id_);
+}
+
+HdCyclesDisplayDriver::~HdCyclesDisplayDriver()
+{
+ if (texture_) {
+ _hgi->DestroyTexture(&texture_);
+ }
+
+ glDeleteBuffers(1, &gl_pbo_id_);
+
+#ifdef _WIN32
+ TF_VERIFY(wglDeleteContext((HGLRC)gl_context_));
+ DestroyWindow(WindowFromDC((HDC)hdc_));
+#endif
+}
+
+void HdCyclesDisplayDriver::next_tile_begin()
+{
+}
+
+bool HdCyclesDisplayDriver::update_begin(const Params &params,
+ int texture_width,
+ int texture_height)
+{
+#ifdef _WIN32
+ if (!hdc_ || !gl_context_) {
+ return false;
+ }
+#endif
+
+ graphics_interop_activate();
+
+ if (gl_render_sync_) {
+ glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ if (pbo_size_.x != params.full_size.x || pbo_size_.y != params.full_size.y) {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
+ glBufferData(GL_PIXEL_UNPACK_BUFFER,
+ sizeof(half4) * params.full_size.x * params.full_size.y,
+ 0,
+ GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ pbo_size_ = params.full_size;
+ }
+
+ need_update_ = true;
+
+ return true;
+}
+
+void HdCyclesDisplayDriver::update_end()
+{
+ gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+ glFlush();
+
+ graphics_interop_deactivate();
+}
+
+void HdCyclesDisplayDriver::flush()
+{
+ graphics_interop_activate();
+
+ if (gl_upload_sync_) {
+ glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ if (gl_render_sync_) {
+ glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ graphics_interop_deactivate();
+}
+
+half4 *HdCyclesDisplayDriver::map_texture_buffer()
+{
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
+
+ const auto mapped_rgba_pixels = static_cast<half4 *>(
+ glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
+
+ if (need_clear_ && mapped_rgba_pixels) {
+ memset(mapped_rgba_pixels, 0, sizeof(half4) * pbo_size_.x * pbo_size_.y);
+ need_clear_ = false;
+ }
+
+ return mapped_rgba_pixels;
+}
+
+void HdCyclesDisplayDriver::unmap_texture_buffer()
+{
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+DisplayDriver::GraphicsInterop HdCyclesDisplayDriver::graphics_interop_get()
+{
+ GraphicsInterop interop_dst;
+ interop_dst.buffer_width = pbo_size_.x;
+ interop_dst.buffer_height = pbo_size_.y;
+ interop_dst.opengl_pbo_id = gl_pbo_id_;
+
+ interop_dst.need_clear = need_clear_;
+ need_clear_ = false;
+
+ return interop_dst;
+}
+
+void HdCyclesDisplayDriver::graphics_interop_activate()
+{
+ mutex_.lock();
+
+#ifdef _WIN32
+ // Do not change context if this is called in the main thread
+ if (wglGetCurrentContext() == nullptr) {
+ TF_VERIFY(wglMakeCurrent((HDC)hdc_, (HGLRC)gl_context_));
+ }
+#endif
+}
+
+void HdCyclesDisplayDriver::graphics_interop_deactivate()
+{
+#ifdef _WIN32
+ if (wglGetCurrentContext() == gl_context_) {
+ TF_VERIFY(wglMakeCurrent(nullptr, nullptr));
+ }
+#endif
+
+ mutex_.unlock();
+}
+
+void HdCyclesDisplayDriver::clear()
+{
+ need_clear_ = true;
+}
+
+void HdCyclesDisplayDriver::draw(const Params &params)
+{
+ const auto renderBuffer = static_cast<HdCyclesRenderBuffer *>(
+ _renderParam->GetDisplayAovBinding().renderBuffer);
+ if (!renderBuffer || // Ensure this render buffer matches the texture dimensions
+ (renderBuffer->GetWidth() != params.size.x || renderBuffer->GetHeight() != params.size.y)) {
+ return;
+ }
+
+ // Cycles 'DisplayDriver' only supports 'half4' format
+ TF_VERIFY(renderBuffer->GetFormat() == HdFormatFloat16Vec4);
+
+ const thread_scoped_lock lock(mutex_);
+
+ const GfVec3i dimensions(params.size.x, params.size.y, 1);
+ if (!texture_ || texture_->GetDescriptor().dimensions != dimensions) {
+ if (texture_) {
+ _hgi->DestroyTexture(&texture_);
+ }
+
+ HgiTextureDesc texDesc;
+ texDesc.usage = 0;
+ texDesc.format = HgiFormatFloat16Vec4;
+ texDesc.type = HgiTextureType2D;
+ texDesc.dimensions = dimensions;
+ texDesc.sampleCount = HgiSampleCount1;
+
+ texture_ = _hgi->CreateTexture(texDesc);
+
+ renderBuffer->SetResource(VtValue(texture_));
+ }
+
+ HgiGLTexture *const texture = dynamic_cast<HgiGLTexture *>(texture_.Get());
+ if (!texture || !need_update_ || pbo_size_.x != params.size.x || pbo_size_.y != params.size.y) {
+ return;
+ }
+
+ if (gl_upload_sync_) {
+ glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, texture->GetTextureId());
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_pbo_id_);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pbo_size_.x, pbo_size_.y, GL_RGBA, GL_HALF_FLOAT, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+ glFlush();
+
+ need_update_ = false;
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/display_driver.h b/intern/cycles/hydra/display_driver.h
new file mode 100644
index 00000000000..668f7d76eed
--- /dev/null
+++ b/intern/cycles/hydra/display_driver.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "session/display_driver.h"
+#include "util/thread.h"
+
+#include <pxr/imaging/hgi/hgi.h>
+#include <pxr/imaging/hgi/texture.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesDisplayDriver final : public CCL_NS::DisplayDriver {
+ public:
+ HdCyclesDisplayDriver(HdCyclesSession *renderParam, Hgi *hgi);
+ ~HdCyclesDisplayDriver();
+
+ private:
+ void next_tile_begin() override;
+
+ bool update_begin(const Params &params, int texture_width, int texture_height) override;
+ void update_end() override;
+
+ void flush() override;
+
+ CCL_NS::half4 *map_texture_buffer() override;
+ void unmap_texture_buffer() override;
+
+ GraphicsInterop graphics_interop_get() override;
+
+ void graphics_interop_activate() override;
+ void graphics_interop_deactivate() override;
+
+ void clear() override;
+
+ void draw(const Params &params) override;
+
+ HdCyclesSession *const _renderParam;
+ Hgi *const _hgi;
+
+#ifdef _WIN32
+ void *hdc_ = nullptr;
+ void *gl_context_ = nullptr;
+#endif
+
+ CCL_NS::thread_mutex mutex_;
+
+ PXR_NS::HgiTextureHandle texture_;
+ unsigned int gl_pbo_id_ = 0;
+ CCL_NS::int2 pbo_size_ = CCL_NS::make_int2(0, 0);
+ bool need_update_ = false;
+ std::atomic_bool need_clear_ = false;
+
+ void *gl_render_sync_ = nullptr;
+ void *gl_upload_sync_ = nullptr;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/field.cpp b/intern/cycles/hydra/field.cpp
new file mode 100644
index 00000000000..79bc05c5a77
--- /dev/null
+++ b/intern/cycles/hydra/field.cpp
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/field.h"
+#include "hydra/session.h"
+#include "scene/image_vdb.h"
+#include "scene/scene.h"
+
+#include <pxr/imaging/hd/sceneDelegate.h>
+#include <pxr/usd/sdf/assetPath.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+// clang-format off
+TF_DEFINE_PRIVATE_TOKENS(_tokens,
+ (fieldName)
+);
+// clang-format on
+
+#ifdef WITH_OPENVDB
+class HdCyclesVolumeLoader : public VDBImageLoader {
+ public:
+ HdCyclesVolumeLoader(const std::string &filePath, const std::string &gridName)
+ : VDBImageLoader(gridName)
+ {
+ openvdb::io::File file(filePath);
+ file.setCopyMaxBytes(0);
+ if (file.open()) {
+ grid = file.readGrid(gridName);
+ }
+ }
+};
+#endif
+
+HdCyclesField::HdCyclesField(const SdfPath &bprimId, const TfToken &typeId) : HdField(bprimId)
+{
+}
+
+HdCyclesField::~HdCyclesField()
+{
+}
+
+HdDirtyBits HdCyclesField::GetInitialDirtyBitsMask() const
+{
+ return DirtyBits::DirtyParams;
+}
+
+void HdCyclesField::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+#ifdef WITH_OPENVDB
+ VtValue value;
+ const SdfPath &id = GetId();
+
+ if (*dirtyBits & DirtyBits::DirtyParams) {
+ value = sceneDelegate->Get(id, HdFieldTokens->filePath);
+ if (value.IsHolding<SdfAssetPath>()) {
+ std::string filename = value.UncheckedGet<SdfAssetPath>().GetResolvedPath();
+ if (filename.empty()) {
+ filename = value.UncheckedGet<SdfAssetPath>().GetAssetPath();
+ }
+
+# if PXR_VERSION >= 2108
+ value = sceneDelegate->Get(id, HdFieldTokens->fieldName);
+# else
+ value = sceneDelegate->Get(id, _tokens->fieldName);
+# endif
+ if (value.IsHolding<TfToken>()) {
+ ImageLoader *const loader = new HdCyclesVolumeLoader(
+ filename, value.UncheckedGet<TfToken>().GetString());
+
+ const SceneLock lock(renderParam);
+
+ ImageParams params;
+ params.frame = 0.0f;
+
+ m_handle = lock.scene->image_manager->add_image(loader, params, false);
+ }
+ }
+ }
+#endif
+
+ *dirtyBits = DirtyBits::Clean;
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/field.h b/intern/cycles/hydra/field.h
new file mode 100644
index 00000000000..c509ba3e0be
--- /dev/null
+++ b/intern/cycles/hydra/field.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "scene/image.h"
+
+#include <pxr/imaging/hd/field.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesField final : public PXR_NS::HdField {
+ public:
+ HdCyclesField(const PXR_NS::SdfPath &bprimId, const PXR_NS::TfToken &typeId);
+ ~HdCyclesField() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits) override;
+
+ CCL_NS::ImageHandle GetImageHandle() const
+ {
+ return m_handle;
+ }
+
+ private:
+ CCL_NS::ImageHandle m_handle;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/geometry.h b/intern/cycles/hydra/geometry.h
new file mode 100644
index 00000000000..1a516ed691d
--- /dev/null
+++ b/intern/cycles/hydra/geometry.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/rprim.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+template<typename Base, typename CyclesBase> class HdCyclesGeometry : public Base {
+ public:
+ HdCyclesGeometry(const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId
+#endif
+ );
+
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits,
+ const PXR_NS::TfToken &reprToken) override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ virtual void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ protected:
+ void _InitRepr(const PXR_NS::TfToken &reprToken, PXR_NS::HdDirtyBits *dirtyBits) override;
+
+ PXR_NS::HdDirtyBits _PropagateDirtyBits(PXR_NS::HdDirtyBits bits) const override;
+
+ virtual void Populate(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdDirtyBits dirtyBits,
+ bool &rebuild) = 0;
+
+ PXR_NS::HdInterpolation GetPrimvarInterpolation(PXR_NS::HdSceneDelegate *sceneDelegate,
+ const PXR_NS::TfToken &name) const;
+
+ CyclesBase *_geom = nullptr;
+ std::vector<CCL_NS::Object *> _instances;
+
+ private:
+ void Initialize(PXR_NS::HdRenderParam *renderParam);
+
+ void InitializeInstance(int index);
+
+ PXR_NS::GfMatrix4d _geomTransform;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/geometry.inl b/intern/cycles/hydra/geometry.inl
new file mode 100644
index 00000000000..007fc6f2667
--- /dev/null
+++ b/intern/cycles/hydra/geometry.inl
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/attribute.h"
+#include "hydra/geometry.h"
+#include "hydra/instancer.h"
+#include "hydra/material.h"
+#include "hydra/session.h"
+#include "scene/geometry.h"
+#include "scene/object.h"
+#include "scene/scene.h"
+#include "util/hash.h"
+
+#include <pxr/imaging/hd/sceneDelegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+extern Transform convert_transform(const GfMatrix4d &matrix);
+
+template<typename Base, typename CyclesBase>
+HdCyclesGeometry<Base, CyclesBase>::HdCyclesGeometry(const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+ )
+ : Base(rprimId
+#if PXR_VERSION < 2102
+
+ ,
+ instancerId
+#endif
+ ),
+ _geomTransform(1.0)
+{
+}
+
+template<typename Base, typename CyclesBase>
+void HdCyclesGeometry<Base, CyclesBase>::_InitRepr(const TfToken &reprToken,
+ HdDirtyBits *dirtyBits)
+{
+ TF_UNUSED(reprToken);
+ TF_UNUSED(dirtyBits);
+}
+
+template<typename Base, typename CyclesBase>
+HdDirtyBits HdCyclesGeometry<Base, CyclesBase>::GetInitialDirtyBitsMask() const
+{
+ return HdChangeTracker::DirtyPrimID | HdChangeTracker::DirtyTransform |
+ HdChangeTracker::DirtyMaterialId | HdChangeTracker::DirtyVisibility |
+ HdChangeTracker::DirtyInstancer;
+}
+
+template<typename Base, typename CyclesBase>
+HdDirtyBits HdCyclesGeometry<Base, CyclesBase>::_PropagateDirtyBits(HdDirtyBits bits) const
+{
+ return bits;
+}
+
+template<typename Base, typename CyclesBase>
+void HdCyclesGeometry<Base, CyclesBase>::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits,
+ const TfToken &reprToken)
+{
+ TF_UNUSED(reprToken);
+
+ if (*dirtyBits == HdChangeTracker::Clean) {
+ return;
+ }
+
+ Initialize(renderParam);
+
+#if PXR_VERSION >= 2102
+ Base::_UpdateInstancer(sceneDelegate, dirtyBits);
+ HdInstancer::_SyncInstancerAndParents(sceneDelegate->GetRenderIndex(), Base::GetInstancerId());
+#endif
+ Base::_UpdateVisibility(sceneDelegate, dirtyBits);
+
+ const SceneLock lock(renderParam);
+
+ if (*dirtyBits & HdChangeTracker::DirtyMaterialId) {
+#if HD_API_VERSION >= 37 && PXR_VERSION >= 2105
+ Base::SetMaterialId(sceneDelegate->GetMaterialId(Base::GetId()));
+#else
+ Base::_SetMaterialId(sceneDelegate->GetRenderIndex().GetChangeTracker(),
+ sceneDelegate->GetMaterialId(Base::GetId()));
+#endif
+
+ const auto material = static_cast<const HdCyclesMaterial *>(
+ sceneDelegate->GetRenderIndex().GetSprim(HdPrimTypeTokens->material,
+ Base::GetMaterialId()));
+
+ array<Node *> usedShaders(1);
+ if (material && material->GetCyclesShader()) {
+ usedShaders[0] = material->GetCyclesShader();
+ }
+ else {
+ usedShaders[0] = lock.scene->default_surface;
+ }
+
+ for (Node *shader : usedShaders) {
+ static_cast<Shader *>(shader)->tag_used(lock.scene);
+ }
+
+ _geom->set_used_shaders(usedShaders);
+ }
+
+ const SdfPath &id = Base::GetId();
+
+ if (HdChangeTracker::IsPrimIdDirty(*dirtyBits, id)) {
+ // This needs to be corrected in the AOV
+ _instances[0]->set_pass_id(Base::GetPrimId() + 1);
+ }
+
+ if (HdChangeTracker::IsTransformDirty(*dirtyBits, id)) {
+ _geomTransform = sceneDelegate->GetTransform(id);
+ }
+
+ if (HdChangeTracker::IsTransformDirty(*dirtyBits, id) ||
+ HdChangeTracker::IsInstancerDirty(*dirtyBits, id)) {
+ const auto instancer = static_cast<HdCyclesInstancer *>(
+ sceneDelegate->GetRenderIndex().GetInstancer(Base::GetInstancerId()));
+
+ // Make sure the first object attribute is the instanceId
+ assert(_instances[0]->attributes.size() >= 1 &&
+ _instances[0]->attributes.front().name() == HdAovTokens->instanceId.GetString());
+
+ VtMatrix4dArray transforms;
+ if (instancer) {
+ transforms = instancer->ComputeInstanceTransforms(id);
+ _instances[0]->attributes.front() = ParamValue(HdAovTokens->instanceId.GetString(), +0.0f);
+ }
+ else {
+ // Default to a single instance with an identity transform
+ transforms.push_back(GfMatrix4d(1.0));
+ _instances[0]->attributes.front() = ParamValue(HdAovTokens->instanceId.GetString(), -1.0f);
+ }
+
+ const size_t oldSize = _instances.size();
+ const size_t newSize = transforms.size();
+
+ // Resize instance list
+ for (size_t i = newSize; i < oldSize; ++i) {
+ lock.scene->delete_node(_instances[i]);
+ }
+ _instances.resize(newSize);
+ for (size_t i = oldSize; i < newSize; ++i) {
+ _instances[i] = lock.scene->create_node<Object>();
+ InitializeInstance(static_cast<int>(i));
+ }
+
+ // Update transforms of all instances
+ for (size_t i = 0; i < transforms.size(); ++i) {
+ const Transform tfm = convert_transform(_geomTransform * transforms[i]);
+ _instances[i]->set_tfm(tfm);
+ }
+ }
+
+ if (HdChangeTracker::IsVisibilityDirty(*dirtyBits, id)) {
+ for (Object *instance : _instances) {
+ instance->set_visibility(Base::IsVisible() ? ~0 : 0);
+ }
+ }
+
+ // Must happen after material ID update, so that attribute decisions can be made
+ // based on it (e.g. check whether an attribute is actually needed)
+ bool rebuild = false;
+ Populate(sceneDelegate, *dirtyBits, rebuild);
+
+ if (_geom->is_modified() || rebuild) {
+ _geom->tag_update(lock.scene, rebuild);
+ }
+
+ for (Object *instance : _instances) {
+ instance->tag_update(lock.scene);
+ }
+
+ *dirtyBits = HdChangeTracker::Clean;
+}
+
+template<typename Base, typename CyclesBase>
+void HdCyclesGeometry<Base, CyclesBase>::Finalize(HdRenderParam *renderParam)
+{
+ if (!_geom && _instances.empty()) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ lock.scene->delete_node(_geom);
+ _geom = nullptr;
+
+ lock.scene->delete_nodes(set<Object *>(_instances.begin(), _instances.end()));
+ _instances.clear();
+ _instances.shrink_to_fit();
+}
+
+template<typename Base, typename CyclesBase>
+void HdCyclesGeometry<Base, CyclesBase>::Initialize(HdRenderParam *renderParam)
+{
+ if (_geom) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ // Create geometry
+ _geom = lock.scene->create_node<CyclesBase>();
+ _geom->name = Base::GetId().GetString();
+
+ // Create default instance
+ _instances.push_back(lock.scene->create_node<Object>());
+ InitializeInstance(0);
+}
+
+template<typename Base, typename CyclesBase>
+void HdCyclesGeometry<Base, CyclesBase>::InitializeInstance(int index)
+{
+ Object *instance = _instances[index];
+ instance->set_geometry(_geom);
+
+ instance->attributes.emplace_back(HdAovTokens->instanceId.GetString(),
+ _instances.size() == 1 ? -1.0f : static_cast<float>(index));
+
+ instance->set_color(make_float3(0.8f, 0.8f, 0.8f));
+ instance->set_random_id(hash_uint2(hash_string(_geom->name.c_str()), index));
+}
+
+template<typename Base, typename CyclesBase>
+HdInterpolation HdCyclesGeometry<Base, CyclesBase>::GetPrimvarInterpolation(
+ HdSceneDelegate *sceneDelegate, const TfToken &name) const
+{
+ for (int i = 0; i < HdInterpolationCount; ++i) {
+ for (const HdPrimvarDescriptor &desc :
+ Base::GetPrimvarDescriptors(sceneDelegate, static_cast<HdInterpolation>(i))) {
+ if (desc.name == name) {
+ return static_cast<HdInterpolation>(i);
+ }
+ }
+ }
+
+ return HdInterpolationCount;
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/instancer.cpp b/intern/cycles/hydra/instancer.cpp
new file mode 100644
index 00000000000..ade5141cd19
--- /dev/null
+++ b/intern/cycles/hydra/instancer.cpp
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/instancer.h"
+
+#include <pxr/base/gf/quatd.h>
+#include <pxr/imaging/hd/sceneDelegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesInstancer::HdCyclesInstancer(HdSceneDelegate *delegate,
+ const SdfPath &instancerId
+#if PXR_VERSION <= 2011
+ ,
+ const SdfPath &parentId
+#endif
+ )
+ : HdInstancer(delegate,
+ instancerId
+#if PXR_VERSION <= 2011
+ ,
+ parentId
+#endif
+ )
+{
+}
+
+HdCyclesInstancer::~HdCyclesInstancer()
+{
+}
+
+#if PXR_VERSION > 2011
+void HdCyclesInstancer::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+ _UpdateInstancer(sceneDelegate, dirtyBits);
+
+ if (HdChangeTracker::IsAnyPrimvarDirty(*dirtyBits, GetId())) {
+ SyncPrimvars();
+ }
+}
+#endif
+
+void HdCyclesInstancer::SyncPrimvars()
+{
+ HdSceneDelegate *const sceneDelegate = GetDelegate();
+ const HdDirtyBits dirtyBits =
+ sceneDelegate->GetRenderIndex().GetChangeTracker().GetInstancerDirtyBits(GetId());
+
+ for (const HdPrimvarDescriptor &desc :
+ sceneDelegate->GetPrimvarDescriptors(GetId(), HdInterpolationInstance)) {
+ if (!HdChangeTracker::IsPrimvarDirty(dirtyBits, GetId(), desc.name)) {
+ continue;
+ }
+
+ const VtValue value = sceneDelegate->Get(GetId(), desc.name);
+ if (value.IsEmpty()) {
+ continue;
+ }
+
+ if (desc.name == HdInstancerTokens->translate) {
+ _translate = value.Get<VtVec3fArray>();
+ }
+ else if (desc.name == HdInstancerTokens->rotate) {
+ _rotate = value.Get<VtVec4fArray>();
+ }
+ else if (desc.name == HdInstancerTokens->scale) {
+ _scale = value.Get<VtVec3fArray>();
+ }
+ else if (desc.name == HdInstancerTokens->instanceTransform) {
+ _instanceTransform = value.Get<VtMatrix4dArray>();
+ }
+ }
+
+ sceneDelegate->GetRenderIndex().GetChangeTracker().MarkInstancerClean(GetId());
+}
+
+VtMatrix4dArray HdCyclesInstancer::ComputeInstanceTransforms(const SdfPath &prototypeId)
+{
+#if PXR_VERSION <= 2011
+ SyncPrimvars();
+#endif
+
+ const VtIntArray instanceIndices = GetDelegate()->GetInstanceIndices(GetId(), prototypeId);
+ const GfMatrix4d instanceTransform = GetDelegate()->GetInstancerTransform(GetId());
+
+ VtMatrix4dArray transforms;
+ transforms.reserve(instanceIndices.size());
+
+ for (int index : instanceIndices) {
+ GfMatrix4d transform = instanceTransform;
+
+ if (index < _translate.size()) {
+ GfMatrix4d translateMat(1);
+ translateMat.SetTranslate(_translate[index]);
+ transform *= translateMat;
+ }
+
+ if (index < _rotate.size()) {
+ GfMatrix4d rotateMat(1);
+ const GfVec4f &quat = _rotate[index];
+ rotateMat.SetRotate(GfQuatd(quat[0], quat[1], quat[2], quat[3]));
+ transform *= rotateMat;
+ }
+
+ if (index < _scale.size()) {
+ GfMatrix4d scaleMat(1);
+ scaleMat.SetScale(_scale[index]);
+ transform *= scaleMat;
+ }
+
+ if (index < _instanceTransform.size()) {
+ transform *= _instanceTransform[index];
+ }
+
+ transforms.push_back(transform);
+ }
+
+ VtMatrix4dArray resultTransforms;
+
+ if (const auto instancer = static_cast<HdCyclesInstancer *>(
+ GetDelegate()->GetRenderIndex().GetInstancer(GetParentId()))) {
+ for (const GfMatrix4d &parentTransform : instancer->ComputeInstanceTransforms(GetId())) {
+ for (const GfMatrix4d &localTransform : transforms) {
+ resultTransforms.push_back(parentTransform * localTransform);
+ }
+ }
+ }
+ else {
+ resultTransforms = std::move(transforms);
+ }
+
+ return resultTransforms;
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/instancer.h b/intern/cycles/hydra/instancer.h
new file mode 100644
index 00000000000..ade1dfba8ee
--- /dev/null
+++ b/intern/cycles/hydra/instancer.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/base/gf/matrix4d.h>
+#include <pxr/base/gf/vec3f.h>
+#include <pxr/base/gf/vec4f.h>
+#include <pxr/base/vt/array.h>
+#include <pxr/imaging/hd/instancer.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesInstancer final : public PXR_NS::HdInstancer {
+ public:
+ HdCyclesInstancer(PXR_NS::HdSceneDelegate *delegate,
+ const PXR_NS::SdfPath &instancerId
+#if PXR_VERSION <= 2011
+ ,
+ const PXR_NS::SdfPath &parentId
+#endif
+ );
+ ~HdCyclesInstancer() override;
+
+#if PXR_VERSION > 2011
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits) override;
+#endif
+
+ PXR_NS::VtMatrix4dArray ComputeInstanceTransforms(const PXR_NS::SdfPath &prototypeId);
+
+ private:
+ void SyncPrimvars();
+
+ PXR_NS::VtVec3fArray _translate;
+ PXR_NS::VtVec4fArray _rotate;
+ PXR_NS::VtVec3fArray _scale;
+ PXR_NS::VtMatrix4dArray _instanceTransform;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/light.cpp b/intern/cycles/hydra/light.cpp
new file mode 100644
index 00000000000..b691da0d6a6
--- /dev/null
+++ b/intern/cycles/hydra/light.cpp
@@ -0,0 +1,399 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/light.h"
+#include "hydra/session.h"
+#include "scene/light.h"
+#include "scene/scene.h"
+#include "scene/shader.h"
+#include "scene/shader_graph.h"
+#include "scene/shader_nodes.h"
+#include "util/hash.h"
+
+#include <pxr/imaging/hd/sceneDelegate.h>
+#include <pxr/usd/sdf/assetPath.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+extern Transform convert_transform(const GfMatrix4d &matrix);
+
+// clang-format off
+TF_DEFINE_PRIVATE_TOKENS(_tokens,
+ (visibleInPrimaryRay)
+);
+// clang-format on
+
+HdCyclesLight::HdCyclesLight(const SdfPath &sprimId, const TfToken &lightType)
+ : HdLight(sprimId), _lightType(lightType)
+{
+}
+
+HdCyclesLight::~HdCyclesLight()
+{
+}
+
+HdDirtyBits HdCyclesLight::GetInitialDirtyBitsMask() const
+{
+ return DirtyBits::DirtyTransform | DirtyBits::DirtyParams;
+}
+
+void HdCyclesLight::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+ if (*dirtyBits == DirtyBits::Clean) {
+ return;
+ }
+
+ Initialize(renderParam);
+
+ const SceneLock lock(renderParam);
+
+ VtValue value;
+ const SdfPath &id = GetId();
+
+ if (*dirtyBits & DirtyBits::DirtyTransform) {
+#if PXR_VERSION >= 2011
+ const Transform tfm = convert_transform(sceneDelegate->GetTransform(id));
+#else
+ const Transform tfm = convert_transform(
+ sceneDelegate->GetLightParamValue(id, HdTokens->transform).Get<GfMatrix4d>());
+#endif
+ _light->set_tfm(tfm);
+
+ _light->set_co(transform_get_column(&tfm, 3));
+ _light->set_dir(-transform_get_column(&tfm, 2));
+
+ if (_lightType == HdPrimTypeTokens->diskLight || _lightType == HdPrimTypeTokens->rectLight) {
+ _light->set_axisu(transform_get_column(&tfm, 0));
+ _light->set_axisv(transform_get_column(&tfm, 1));
+ }
+ }
+
+ if (*dirtyBits & DirtyBits::DirtyParams) {
+ float3 strength = make_float3(1.0f, 1.0f, 1.0f);
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->color);
+ if (!value.IsEmpty()) {
+ const auto color = value.Get<GfVec3f>();
+ strength = make_float3(color[0], color[1], color[2]);
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->exposure);
+ if (!value.IsEmpty()) {
+ strength *= exp2(value.Get<float>());
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->intensity);
+ if (!value.IsEmpty()) {
+ strength *= value.Get<float>();
+ }
+
+ // Cycles lights are normalized by default, so need to scale intensity if Hydra light is not
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->normalize);
+ const bool normalize = value.IsHolding<bool>() && value.UncheckedGet<bool>();
+
+ value = sceneDelegate->GetLightParamValue(id, _tokens->visibleInPrimaryRay);
+ if (!value.IsEmpty()) {
+ _light->set_use_camera(value.Get<bool>());
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shadowEnable);
+ if (!value.IsEmpty()) {
+ _light->set_cast_shadow(value.Get<bool>());
+ }
+
+ if (_lightType == HdPrimTypeTokens->distantLight) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->angle);
+ if (!value.IsEmpty()) {
+ _light->set_angle(GfDegreesToRadians(value.Get<float>()));
+ }
+ }
+ else if (_lightType == HdPrimTypeTokens->diskLight) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius);
+ if (!value.IsEmpty()) {
+ const float size = value.Get<float>() * 2.0f;
+ _light->set_sizeu(size);
+ _light->set_sizev(size);
+ }
+
+ if (!normalize) {
+ const float radius = _light->get_sizeu() * 0.5f;
+ strength *= M_PI * radius * radius;
+ }
+ }
+ else if (_lightType == HdPrimTypeTokens->rectLight) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->width);
+ if (!value.IsEmpty()) {
+ _light->set_sizeu(value.Get<float>());
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->height);
+ if (!value.IsEmpty()) {
+ _light->set_sizev(value.Get<float>());
+ }
+
+ if (!normalize) {
+ strength *= _light->get_sizeu() * _light->get_sizeu();
+ }
+ }
+ else if (_lightType == HdPrimTypeTokens->sphereLight) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius);
+ if (!value.IsEmpty()) {
+ _light->set_size(value.Get<float>());
+ }
+
+ bool shaping = false;
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingConeAngle);
+ if (!value.IsEmpty()) {
+ _light->set_spot_angle(GfDegreesToRadians(value.Get<float>()) * 2.0f);
+ shaping = true;
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingConeSoftness);
+ if (!value.IsEmpty()) {
+ _light->set_spot_smooth(value.Get<float>());
+ shaping = true;
+ }
+
+ _light->set_light_type(shaping ? LIGHT_SPOT : LIGHT_POINT);
+
+ if (!normalize) {
+ const float radius = _light->get_size();
+ strength *= M_PI * radius * radius * 4.0f;
+ }
+ }
+
+ const bool visible = sceneDelegate->GetVisible(id);
+ // Disable invisible lights by zeroing the strength
+ // So 'LightManager::test_enabled_lights' updates the enabled flag correctly
+ if (!visible) {
+ strength = zero_float3();
+ }
+
+ _light->set_strength(strength);
+ _light->set_is_enabled(visible);
+
+ PopulateShaderGraph(sceneDelegate);
+ }
+ // Need to update shader graph when transform changes in case transform was baked into it
+ else if (_light->tfm_is_modified() && (_lightType == HdPrimTypeTokens->domeLight ||
+ _light->get_shader()->has_surface_spatial_varying)) {
+ PopulateShaderGraph(sceneDelegate);
+ }
+
+ if (_light->is_modified()) {
+ _light->tag_update(lock.scene);
+ }
+
+ *dirtyBits = DirtyBits::Clean;
+}
+
+void HdCyclesLight::PopulateShaderGraph(HdSceneDelegate *sceneDelegate)
+{
+ auto graph = new ShaderGraph();
+ ShaderNode *outputNode = nullptr;
+
+ if (_lightType == HdPrimTypeTokens->domeLight) {
+ BackgroundNode *bgNode = graph->create_node<BackgroundNode>();
+ // Bake strength into shader graph, since only the shader is used for background lights
+ bgNode->set_color(_light->get_strength());
+ graph->add(bgNode);
+
+ graph->connect(bgNode->output("Background"), graph->output()->input("Surface"));
+
+ outputNode = bgNode;
+ }
+ else {
+ EmissionNode *emissionNode = graph->create_node<EmissionNode>();
+ emissionNode->set_color(one_float3());
+ emissionNode->set_strength(1.0f);
+ graph->add(emissionNode);
+
+ graph->connect(emissionNode->output("Emission"), graph->output()->input("Surface"));
+
+ outputNode = emissionNode;
+ }
+
+ VtValue value;
+ const SdfPath &id = GetId();
+ bool hasSpatialVarying = false;
+ bool hasColorTemperature = false;
+
+ if (sceneDelegate != nullptr) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->enableColorTemperature);
+ const bool enableColorTemperature = value.IsHolding<bool>() && value.UncheckedGet<bool>();
+
+ if (enableColorTemperature) {
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->colorTemperature);
+ if (value.IsHolding<float>()) {
+ BlackbodyNode *blackbodyNode = graph->create_node<BlackbodyNode>();
+ blackbodyNode->set_temperature(value.UncheckedGet<float>());
+ graph->add(blackbodyNode);
+
+ if (_lightType == HdPrimTypeTokens->domeLight) {
+ VectorMathNode *mathNode = graph->create_node<VectorMathNode>();
+ mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY);
+ mathNode->set_vector2(_light->get_strength());
+ graph->add(mathNode);
+
+ graph->connect(blackbodyNode->output("Color"), mathNode->input("Vector1"));
+ graph->connect(mathNode->output("Vector"), outputNode->input("Color"));
+ }
+ else {
+ graph->connect(blackbodyNode->output("Color"), outputNode->input("Color"));
+ }
+
+ hasColorTemperature = true;
+ }
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingIesFile);
+ if (value.IsHolding<SdfAssetPath>()) {
+ std::string filename = value.UncheckedGet<SdfAssetPath>().GetResolvedPath();
+ if (filename.empty()) {
+ filename = value.UncheckedGet<SdfAssetPath>().GetAssetPath();
+ }
+
+ TextureCoordinateNode *coordNode = graph->create_node<TextureCoordinateNode>();
+ coordNode->set_ob_tfm(_light->get_tfm());
+ coordNode->set_use_transform(true);
+ graph->add(coordNode);
+
+ IESLightNode *iesNode = graph->create_node<IESLightNode>();
+ iesNode->set_filename(ustring(filename));
+
+ graph->connect(coordNode->output("Normal"), iesNode->input("Vector"));
+ graph->connect(iesNode->output("Fac"), outputNode->input("Strength"));
+
+ hasSpatialVarying = true;
+ }
+
+ value = sceneDelegate->GetLightParamValue(id, HdLightTokens->textureFile);
+ if (value.IsHolding<SdfAssetPath>()) {
+ std::string filename = value.UncheckedGet<SdfAssetPath>().GetResolvedPath();
+ if (filename.empty()) {
+ filename = value.UncheckedGet<SdfAssetPath>().GetAssetPath();
+ }
+
+ ImageSlotTextureNode *textureNode = nullptr;
+ if (_lightType == HdPrimTypeTokens->domeLight) {
+ Transform tfm = _light->get_tfm();
+ transform_set_column(&tfm, 3, zero_float3()); // Remove translation
+
+ TextureCoordinateNode *coordNode = graph->create_node<TextureCoordinateNode>();
+ coordNode->set_ob_tfm(tfm);
+ coordNode->set_use_transform(true);
+ graph->add(coordNode);
+
+ textureNode = graph->create_node<EnvironmentTextureNode>();
+ static_cast<EnvironmentTextureNode *>(textureNode)->set_filename(ustring(filename));
+ graph->add(textureNode);
+
+ graph->connect(coordNode->output("Object"), textureNode->input("Vector"));
+
+ hasSpatialVarying = true;
+ }
+ else {
+ GeometryNode *coordNode = graph->create_node<GeometryNode>();
+ graph->add(coordNode);
+
+ textureNode = graph->create_node<ImageTextureNode>();
+ static_cast<ImageTextureNode *>(textureNode)->set_filename(ustring(filename));
+ graph->add(textureNode);
+
+ graph->connect(coordNode->output("Parametric"), textureNode->input("Vector"));
+ }
+
+ if (hasColorTemperature) {
+ VectorMathNode *mathNode = graph->create_node<VectorMathNode>();
+ mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY);
+ graph->add(mathNode);
+
+ graph->connect(textureNode->output("Color"), mathNode->input("Vector1"));
+ ShaderInput *const outputNodeInput = outputNode->input("Color");
+ graph->connect(outputNodeInput->link, mathNode->input("Vector2"));
+ graph->disconnect(outputNodeInput);
+ graph->connect(mathNode->output("Vector"), outputNodeInput);
+ }
+ else if (_lightType == HdPrimTypeTokens->domeLight) {
+ VectorMathNode *mathNode = graph->create_node<VectorMathNode>();
+ mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY);
+ mathNode->set_vector2(_light->get_strength());
+ graph->add(mathNode);
+
+ graph->connect(textureNode->output("Color"), mathNode->input("Vector1"));
+ graph->connect(mathNode->output("Vector"), outputNode->input("Color"));
+ }
+ else {
+ graph->connect(textureNode->output("Color"), outputNode->input("Color"));
+ }
+ }
+ }
+
+ Shader *const shader = _light->get_shader();
+ shader->set_graph(graph);
+ shader->tag_update((Scene *)_light->get_owner());
+
+ shader->has_surface_spatial_varying = hasSpatialVarying;
+}
+
+void HdCyclesLight::Finalize(HdRenderParam *renderParam)
+{
+ if (!_light) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ lock.scene->delete_node(_light);
+ _light = nullptr;
+}
+
+void HdCyclesLight::Initialize(HdRenderParam *renderParam)
+{
+ if (_light) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ _light = lock.scene->create_node<Light>();
+ _light->name = GetId().GetString();
+
+ _light->set_random_id(hash_uint2(hash_string(_light->name.c_str()), 0));
+
+ if (_lightType == HdPrimTypeTokens->domeLight) {
+ _light->set_light_type(LIGHT_BACKGROUND);
+ }
+ else if (_lightType == HdPrimTypeTokens->distantLight) {
+ _light->set_light_type(LIGHT_DISTANT);
+ }
+ else if (_lightType == HdPrimTypeTokens->diskLight) {
+ _light->set_light_type(LIGHT_AREA);
+ _light->set_round(true);
+ _light->set_size(1.0f);
+ }
+ else if (_lightType == HdPrimTypeTokens->rectLight) {
+ _light->set_light_type(LIGHT_AREA);
+ _light->set_round(false);
+ _light->set_size(1.0f);
+ }
+ else if (_lightType == HdPrimTypeTokens->sphereLight) {
+ _light->set_light_type(LIGHT_POINT);
+ _light->set_size(1.0f);
+ }
+
+ _light->set_use_mis(true);
+ _light->set_use_camera(false);
+
+ Shader *const shader = lock.scene->create_node<Shader>();
+ _light->set_shader(shader);
+
+ // Create default shader graph
+ PopulateShaderGraph(nullptr);
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/light.h b/intern/cycles/hydra/light.h
new file mode 100644
index 00000000000..9230bc5730e
--- /dev/null
+++ b/intern/cycles/hydra/light.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/light.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesLight final : public PXR_NS::HdLight {
+ public:
+ HdCyclesLight(const PXR_NS::SdfPath &sprimId, const PXR_NS::TfToken &lightType);
+ ~HdCyclesLight() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits) override;
+
+ void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ private:
+ void Initialize(PXR_NS::HdRenderParam *renderParam);
+
+ void PopulateShaderGraph(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ CCL_NS::Light *_light = nullptr;
+ PXR_NS::TfToken _lightType;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/material.cpp b/intern/cycles/hydra/material.cpp
new file mode 100644
index 00000000000..a595102a605
--- /dev/null
+++ b/intern/cycles/hydra/material.cpp
@@ -0,0 +1,589 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/material.h"
+#include "hydra/node_util.h"
+#include "hydra/session.h"
+#include "scene/scene.h"
+#include "scene/shader.h"
+#include "scene/shader_graph.h"
+#include "scene/shader_nodes.h"
+
+#include <pxr/imaging/hd/sceneDelegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+// clang-format off
+TF_DEFINE_PRIVATE_TOKENS(CyclesMaterialTokens,
+ ((cyclesSurface, "cycles:surface"))
+ ((cyclesDisplacement, "cycles:displacement"))
+ ((cyclesVolume, "cycles:volume"))
+ (UsdPreviewSurface)
+ (UsdUVTexture)
+ (UsdPrimvarReader_float)
+ (UsdPrimvarReader_float2)
+ (UsdPrimvarReader_float3)
+ (UsdPrimvarReader_float4)
+ (UsdPrimvarReader_int)
+ (UsdTransform2d)
+ (a)
+ (rgb)
+ (r)
+ (g)
+ (b)
+ (result)
+ (st)
+ (wrapS)
+ (wrapT)
+ (periodic)
+);
+// clang-format on
+
+namespace {
+
+// Simple class to handle remapping of USDPreviewSurface nodes and parameters to Cycles equivalents
+class UsdToCyclesMapping {
+ using ParamMap = std::unordered_map<TfToken, ustring, TfToken::HashFunctor>;
+
+ public:
+ UsdToCyclesMapping(const char *nodeType, ParamMap paramMap)
+ : _nodeType(nodeType), _paramMap(std::move(paramMap))
+ {
+ }
+
+ ustring nodeType() const
+ {
+ return _nodeType;
+ }
+
+ virtual std::string parameterName(const TfToken &name,
+ const ShaderInput *inputConnection,
+ VtValue *value = nullptr) const
+ {
+ // UsdNode.name -> Node.input
+ // These all follow a simple pattern that we can just remap
+ // based on the name or 'Node.input' type
+ if (inputConnection) {
+ if (name == CyclesMaterialTokens->a) {
+ return "alpha";
+ }
+ if (name == CyclesMaterialTokens->rgb) {
+ return "color";
+ }
+ // TODO: Is there a better mapping than 'color'?
+ if (name == CyclesMaterialTokens->r || name == CyclesMaterialTokens->g ||
+ name == CyclesMaterialTokens->b) {
+ return "color";
+ }
+
+ if (name == CyclesMaterialTokens->result) {
+ switch (inputConnection->socket_type.type) {
+ case SocketType::BOOLEAN:
+ case SocketType::FLOAT:
+ case SocketType::INT:
+ case SocketType::UINT:
+ return "alpha";
+ case SocketType::COLOR:
+ case SocketType::VECTOR:
+ case SocketType::POINT:
+ case SocketType::NORMAL:
+ default:
+ return "color";
+ }
+ }
+ }
+
+ // Simple mapping case
+ const auto it = _paramMap.find(name);
+ return it != _paramMap.end() ? it->second.string() : name.GetString();
+ }
+
+ private:
+ const ustring _nodeType;
+ ParamMap _paramMap;
+};
+
+class UsdToCyclesTexture : public UsdToCyclesMapping {
+ public:
+ using UsdToCyclesMapping::UsdToCyclesMapping;
+
+ std::string parameterName(const TfToken &name,
+ const ShaderInput *inputConnection,
+ VtValue *value) const override
+ {
+ if (value) {
+ // Remap UsdUVTexture.wrapS and UsdUVTexture.wrapT to cycles_image_texture.extension
+ if (name == CyclesMaterialTokens->wrapS || name == CyclesMaterialTokens->wrapT) {
+ std::string valueString = VtValue::Cast<std::string>(*value).Get<std::string>();
+
+ // A value of 'repeat' in USD is equivalent to 'periodic' in Cycles
+ if (valueString == "repeat") {
+ *value = VtValue(CyclesMaterialTokens->periodic);
+ }
+
+ return "extension";
+ }
+ }
+
+ return UsdToCyclesMapping::parameterName(name, inputConnection, value);
+ }
+};
+
+class UsdToCycles {
+ const UsdToCyclesMapping UsdPreviewSurface = {
+ "principled_bsdf",
+ {
+ {TfToken("diffuseColor"), ustring("base_color")},
+ {TfToken("emissiveColor"), ustring("emission")},
+ {TfToken("specularColor"), ustring("specular")},
+ {TfToken("clearcoatRoughness"), ustring("clearcoat_roughness")},
+ {TfToken("opacity"), ustring("alpha")},
+ // opacityThreshold
+ // occlusion
+ // displacement
+ }};
+ const UsdToCyclesTexture UsdUVTexture = {
+ "image_texture",
+ {
+ {CyclesMaterialTokens->st, ustring("vector")},
+ {CyclesMaterialTokens->wrapS, ustring("extension")},
+ {CyclesMaterialTokens->wrapT, ustring("extension")},
+ {TfToken("file"), ustring("filename")},
+ {TfToken("sourceColorSpace"), ustring("colorspace")},
+ }};
+ const UsdToCyclesMapping UsdPrimvarReader = {"attribute",
+ {{TfToken("varname"), ustring("attribute")}}};
+
+ public:
+ const UsdToCyclesMapping *findUsd(const TfToken &usdNodeType)
+ {
+ if (usdNodeType == CyclesMaterialTokens->UsdPreviewSurface) {
+ return &UsdPreviewSurface;
+ }
+ if (usdNodeType == CyclesMaterialTokens->UsdUVTexture) {
+ return &UsdUVTexture;
+ }
+ if (usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float2 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float3 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_float4 ||
+ usdNodeType == CyclesMaterialTokens->UsdPrimvarReader_int) {
+ return &UsdPrimvarReader;
+ }
+
+ return nullptr;
+ }
+ const UsdToCyclesMapping *findCycles(const ustring &cyclesNodeType)
+ {
+ return nullptr;
+ }
+};
+TfStaticData<UsdToCycles> sUsdToCyles;
+
+} // namespace
+
+struct HdCyclesMaterial::NodeDesc {
+ ShaderNode *node;
+ const UsdToCyclesMapping *mapping;
+};
+
+HdCyclesMaterial::HdCyclesMaterial(const SdfPath &sprimId) : HdMaterial(sprimId)
+{
+}
+
+HdCyclesMaterial::~HdCyclesMaterial()
+{
+}
+
+HdDirtyBits HdCyclesMaterial::GetInitialDirtyBitsMask() const
+{
+ return DirtyBits::DirtyResource | DirtyBits::DirtyParams;
+}
+
+void HdCyclesMaterial::Sync(HdSceneDelegate *sceneDelegate,
+ HdRenderParam *renderParam,
+ HdDirtyBits *dirtyBits)
+{
+ if (*dirtyBits == DirtyBits::Clean) {
+ return;
+ }
+
+ Initialize(renderParam);
+
+ const SceneLock lock(renderParam);
+
+ const bool dirtyParams = (*dirtyBits & DirtyBits::DirtyParams);
+ const bool dirtyResource = (*dirtyBits & DirtyBits::DirtyResource);
+
+ VtValue value;
+ const SdfPath &id = GetId();
+
+ if (dirtyResource || dirtyParams) {
+ value = sceneDelegate->GetMaterialResource(id);
+
+#if 1
+ const HdMaterialNetwork2 *network = nullptr;
+ std::unique_ptr<HdMaterialNetwork2> networkConverted;
+ if (value.IsHolding<HdMaterialNetwork2>()) {
+ network = &value.UncheckedGet<HdMaterialNetwork2>();
+ }
+ else if (value.IsHolding<HdMaterialNetworkMap>()) {
+ const auto &networkOld = value.UncheckedGet<HdMaterialNetworkMap>();
+ // In the case of only parameter updates, there is no need to waste time converting to a
+ // HdMaterialNetwork2, as supporting HdMaterialNetworkMap for parameters only is trivial.
+ if (!_nodes.empty() && !dirtyResource) {
+ for (const auto &networkEntry : networkOld.map) {
+ UpdateParameters(networkEntry.second);
+ }
+ _shader->tag_modified();
+ }
+ else {
+ networkConverted = std::make_unique<HdMaterialNetwork2>();
+ HdMaterialNetwork2ConvertFromHdMaterialNetworkMap(networkOld, networkConverted.get());
+ network = networkConverted.get();
+ }
+ }
+ else {
+ TF_RUNTIME_ERROR("Could not get a HdMaterialNetwork2.");
+ }
+
+ if (network) {
+ if (!_nodes.empty() && !dirtyResource) {
+ UpdateParameters(*network);
+ _shader->tag_modified();
+ }
+ else {
+ PopulateShaderGraph(*network);
+ }
+ }
+#endif
+ }
+
+ if (_shader->is_modified()) {
+ _shader->tag_update(lock.scene);
+ }
+
+ *dirtyBits = DirtyBits::Clean;
+}
+
+void HdCyclesMaterial::UpdateParameters(NodeDesc &nodeDesc,
+ const std::map<TfToken, VtValue> &parameters,
+ const SdfPath &nodePath)
+{
+ for (const std::pair<TfToken, VtValue> &param : parameters) {
+ VtValue value = param.second;
+
+ // See if the parameter name is in USDPreviewSurface terms, and needs to be converted
+ const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
+ const std::string inputName = inputMapping ?
+ inputMapping->parameterName(param.first, nullptr, &value) :
+ param.first.GetString();
+
+ // Find the input to write the parameter value to
+ const SocketType *input = nullptr;
+ for (const SocketType &socket : nodeDesc.node->type->inputs) {
+ if (string_iequals(socket.name.string(), inputName) || socket.ui_name == inputName) {
+ input = &socket;
+ break;
+ }
+ }
+
+ if (!input) {
+ TF_WARN("Could not find parameter '%s' on node '%s' ('%s')",
+ param.first.GetText(),
+ nodePath.GetText(),
+ nodeDesc.node->name.c_str());
+ continue;
+ }
+
+ SetNodeValue(nodeDesc.node, *input, value);
+ }
+}
+
+void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork &network)
+{
+ for (const HdMaterialNode &nodeEntry : network.nodes) {
+ const SdfPath &nodePath = nodeEntry.path;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
+ continue;
+ }
+
+ UpdateParameters(nodeIt->second, nodeEntry.parameters, nodePath);
+ }
+}
+
+void HdCyclesMaterial::UpdateParameters(const HdMaterialNetwork2 &network)
+{
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : network.nodes) {
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not update parameters on missing node '%s'", nodePath.GetText());
+ continue;
+ }
+
+ UpdateParameters(nodeIt->second, nodeEntry.second.parameters, nodePath);
+ }
+}
+
+void HdCyclesMaterial::UpdateConnections(NodeDesc &nodeDesc,
+ const HdMaterialNode2 &matNode,
+ const SdfPath &nodePath,
+ ShaderGraph *shaderGraph)
+{
+ for (const std::pair<TfToken, std::vector<HdMaterialConnection2>> &connection :
+ matNode.inputConnections) {
+ const TfToken &dstSocketName = connection.first;
+
+ const UsdToCyclesMapping *inputMapping = nodeDesc.mapping;
+ const std::string inputName = inputMapping ?
+ inputMapping->parameterName(dstSocketName, nullptr) :
+ dstSocketName.GetString();
+
+ // Find the input to connect to on the passed in node
+ ShaderInput *input = nullptr;
+ for (ShaderInput *in : nodeDesc.node->inputs) {
+ if (string_iequals(in->socket_type.name.string(), inputName)) {
+ input = in;
+ break;
+ }
+ }
+
+ if (!input) {
+ TF_WARN("Ignoring connection on '%s.%s', input '%s' was not found",
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ dstSocketName.GetText());
+ continue;
+ }
+
+ // Now find the output to connect from
+ const auto &connectedNodes = connection.second;
+ if (connectedNodes.empty()) {
+ continue;
+ }
+
+ // TODO: Hydra allows multiple connections of the same input
+ // Unsure how to handle this in Cycles, so just use the first
+ if (connectedNodes.size() > 1) {
+ TF_WARN(
+ "Ignoring multiple connections to '%s.%s'", nodePath.GetText(), dstSocketName.GetText());
+ }
+
+ const SdfPath &upstreamNodePath = connectedNodes.front().upstreamNode;
+ const TfToken &upstreamOutputName = connectedNodes.front().upstreamOutputName;
+
+ const auto srcNodeIt = _nodes.find(upstreamNodePath);
+ if (srcNodeIt == _nodes.end()) {
+ TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', node '%s' was not found",
+ upstreamNodePath.GetText(),
+ upstreamOutputName.GetText(),
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ upstreamNodePath.GetText());
+ continue;
+ }
+
+ const UsdToCyclesMapping *outputMapping = srcNodeIt->second.mapping;
+ const std::string outputName = outputMapping ?
+ outputMapping->parameterName(upstreamOutputName, input) :
+ upstreamOutputName.GetString();
+
+ ShaderOutput *output = nullptr;
+ for (ShaderOutput *out : srcNodeIt->second.node->outputs) {
+ if (string_iequals(out->socket_type.name.string(), outputName)) {
+ output = out;
+ break;
+ }
+ }
+
+ if (!output) {
+ TF_WARN("Ignoring connection from '%s.%s' to '%s.%s', output '%s' was not found",
+ upstreamNodePath.GetText(),
+ upstreamOutputName.GetText(),
+ nodePath.GetText(),
+ dstSocketName.GetText(),
+ upstreamOutputName.GetText());
+ continue;
+ }
+
+ shaderGraph->connect(output, input);
+ }
+}
+
+void HdCyclesMaterial::PopulateShaderGraph(const HdMaterialNetwork2 &networkMap)
+{
+ _nodes.clear();
+
+ auto graph = new ShaderGraph();
+
+ // Iterate all the nodes first and build a complete but unconnected graph with parameters set
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : networkMap.nodes) {
+ NodeDesc nodeDesc = {};
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ // Create new node only if it does not exist yet
+ if (nodeIt != _nodes.end()) {
+ nodeDesc = nodeIt->second;
+ }
+ else {
+ // E.g. cycles_principled_bsdf or UsdPreviewSurface
+ const std::string &nodeTypeId = nodeEntry.second.nodeTypeId.GetString();
+
+ ustring cyclesType(nodeTypeId);
+ // Interpret a node type ID prefixed with cycles_<type> or cycles:<type> as a node of <type>
+ if (nodeTypeId.rfind("cycles", 0) == 0) {
+ cyclesType = nodeTypeId.substr(7);
+ nodeDesc.mapping = sUsdToCyles->findCycles(cyclesType);
+ }
+ else {
+ // Check if any remapping is needed (e.g. for USDPreviewSurface to Cycles nodes)
+ nodeDesc.mapping = sUsdToCyles->findUsd(nodeEntry.second.nodeTypeId);
+ if (nodeDesc.mapping) {
+ cyclesType = nodeDesc.mapping->nodeType();
+ }
+ }
+
+ // If it's a native Cycles' node-type, just do the lookup now.
+ if (const NodeType *nodeType = NodeType::find(cyclesType)) {
+ nodeDesc.node = static_cast<ShaderNode *>(nodeType->create(nodeType));
+ nodeDesc.node->set_owner(graph);
+
+ graph->add(nodeDesc.node);
+
+ _nodes.emplace(nodePath, nodeDesc);
+ }
+ else {
+ TF_RUNTIME_ERROR("Could not create node '%s'", nodePath.GetText());
+ continue;
+ }
+ }
+
+ UpdateParameters(nodeDesc, nodeEntry.second.parameters, nodePath);
+ }
+
+ // Now that all nodes have been constructed, iterate the network again and build up any
+ // connections between nodes
+ for (const std::pair<SdfPath, HdMaterialNode2> &nodeEntry : networkMap.nodes) {
+ const SdfPath &nodePath = nodeEntry.first;
+
+ const auto nodeIt = _nodes.find(nodePath);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not find node '%s' to connect", nodePath.GetText());
+ continue;
+ }
+
+ UpdateConnections(nodeIt->second, nodeEntry.second, nodePath, graph);
+ }
+
+ // Finally connect the terminals to the graph output (Surface, Volume, Displacement)
+ for (const std::pair<TfToken, HdMaterialConnection2> &terminalEntry : networkMap.terminals) {
+ const TfToken &terminalName = terminalEntry.first;
+ const HdMaterialConnection2 &connection = terminalEntry.second;
+
+ const auto nodeIt = _nodes.find(connection.upstreamNode);
+ if (nodeIt == _nodes.end()) {
+ TF_RUNTIME_ERROR("Could not find terminal node '%s'", connection.upstreamNode.GetText());
+ continue;
+ }
+
+ ShaderNode *const node = nodeIt->second.node;
+
+ const char *inputName = nullptr;
+ const char *outputName = nullptr;
+ if (terminalName == HdMaterialTerminalTokens->surface ||
+ terminalName == CyclesMaterialTokens->cyclesSurface) {
+ inputName = "Surface";
+ // Find default output name based on the node if none is provided
+ if (node->type->name == "add_closure" || node->type->name == "mix_closure") {
+ outputName = "Closure";
+ }
+ else if (node->type->name == "emission") {
+ outputName = "Emission";
+ }
+ else {
+ outputName = "BSDF";
+ }
+ }
+ else if (terminalName == HdMaterialTerminalTokens->displacement ||
+ terminalName == CyclesMaterialTokens->cyclesDisplacement) {
+ inputName = outputName = "Displacement";
+ }
+ else if (terminalName == HdMaterialTerminalTokens->volume ||
+ terminalName == CyclesMaterialTokens->cyclesVolume) {
+ inputName = outputName = "Volume";
+ }
+
+ if (!connection.upstreamOutputName.IsEmpty()) {
+ outputName = connection.upstreamOutputName.GetText();
+ }
+
+ ShaderInput *const input = inputName ? graph->output()->input(inputName) : nullptr;
+ if (!input) {
+ TF_RUNTIME_ERROR("Could not find terminal input '%s.%s'",
+ connection.upstreamNode.GetText(),
+ inputName ? inputName : "<null>");
+ continue;
+ }
+
+ ShaderOutput *const output = outputName ? node->output(outputName) : nullptr;
+ if (!output) {
+ TF_RUNTIME_ERROR("Could not find terminal output '%s.%s'",
+ connection.upstreamNode.GetText(),
+ outputName ? outputName : "<null>");
+ continue;
+ }
+
+ graph->connect(output, input);
+ }
+
+ // Create the instanceId AOV output
+ {
+ const ustring instanceId(HdAovTokens->instanceId.GetString());
+
+ OutputAOVNode *aovNode = graph->create_node<OutputAOVNode>();
+ aovNode->set_name(instanceId);
+ graph->add(aovNode);
+
+ AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
+ instanceIdNode->set_attribute(instanceId);
+ graph->add(instanceIdNode);
+
+ graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
+ }
+
+ _shader->set_graph(graph);
+}
+
+void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
+{
+ if (!_shader) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ _nodes.clear();
+
+ lock.scene->delete_node(_shader);
+ _shader = nullptr;
+}
+
+void HdCyclesMaterial::Initialize(HdRenderParam *renderParam)
+{
+ if (_shader) {
+ return;
+ }
+
+ const SceneLock lock(renderParam);
+
+ _shader = lock.scene->create_node<Shader>();
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/material.h b/intern/cycles/hydra/material.h
new file mode 100644
index 00000000000..15925671bb8
--- /dev/null
+++ b/intern/cycles/hydra/material.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/material.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesMaterial final : public PXR_NS::HdMaterial {
+ public:
+ HdCyclesMaterial(const PXR_NS::SdfPath &sprimId);
+ ~HdCyclesMaterial() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ void Sync(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdRenderParam *renderParam,
+ PXR_NS::HdDirtyBits *dirtyBits) override;
+
+#if PXR_VERSION < 2011
+ void Reload() override
+ {
+ }
+#endif
+
+ void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ CCL_NS::Shader *GetCyclesShader() const
+ {
+ return _shader;
+ }
+
+ struct NodeDesc;
+
+ private:
+ void Initialize(PXR_NS::HdRenderParam *renderParam);
+
+ void UpdateParameters(NodeDesc &nodeDesc,
+ const std::map<PXR_NS::TfToken, PXR_NS::VtValue> &parameters,
+ const PXR_NS::SdfPath &nodePath);
+
+ void UpdateParameters(const PXR_NS::HdMaterialNetwork &network);
+ void UpdateParameters(const PXR_NS::HdMaterialNetwork2 &network);
+
+ void UpdateConnections(NodeDesc &nodeDesc,
+ const PXR_NS::HdMaterialNode2 &matNode,
+ const PXR_NS::SdfPath &nodePath,
+ CCL_NS::ShaderGraph *shaderGraph);
+
+ void PopulateShaderGraph(const PXR_NS::HdMaterialNetwork2 &network);
+
+ CCL_NS::Shader *_shader = nullptr;
+ std::unordered_map<PXR_NS::SdfPath, NodeDesc, PXR_NS::SdfPath::Hash> _nodes;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/mesh.cpp b/intern/cycles/hydra/mesh.cpp
new file mode 100644
index 00000000000..155843458ef
--- /dev/null
+++ b/intern/cycles/hydra/mesh.cpp
@@ -0,0 +1,524 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/mesh.h"
+#include "hydra/geometry.inl"
+#include "scene/mesh.h"
+
+#include <pxr/base/gf/vec2f.h>
+#include <pxr/imaging/hd/extComputationUtils.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+namespace {
+
+template<typename T>
+VtValue ComputeTriangulatedUniformPrimvar(VtValue value, const VtIntArray &primitiveParams)
+{
+ T output;
+ output.reserve(primitiveParams.size());
+ const T &input = value.Get<T>();
+
+ for (size_t i = 0; i < primitiveParams.size(); ++i) {
+ const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(primitiveParams[i]);
+
+ output.push_back(input[faceIndex]);
+ }
+
+ return VtValue(output);
+}
+
+VtValue ComputeTriangulatedUniformPrimvar(VtValue value,
+ const HdType valueType,
+ const VtIntArray &primitiveParams)
+{
+ switch (valueType) {
+ case HdTypeFloat:
+ return ComputeTriangulatedUniformPrimvar<VtFloatArray>(value, primitiveParams);
+ case HdTypeFloatVec2:
+ return ComputeTriangulatedUniformPrimvar<VtVec2fArray>(value, primitiveParams);
+ case HdTypeFloatVec3:
+ return ComputeTriangulatedUniformPrimvar<VtVec3fArray>(value, primitiveParams);
+ case HdTypeFloatVec4:
+ return ComputeTriangulatedUniformPrimvar<VtVec4fArray>(value, primitiveParams);
+ default:
+ TF_RUNTIME_ERROR("Unsupported attribute type %d", static_cast<int>(valueType));
+ return VtValue();
+ }
+}
+
+VtValue ComputeTriangulatedFaceVaryingPrimvar(VtValue value,
+ const HdType valueType,
+ HdMeshUtil &meshUtil)
+{
+ if (meshUtil.ComputeTriangulatedFaceVaryingPrimvar(
+ HdGetValueData(value), value.GetArraySize(), valueType, &value)) {
+ return value;
+ }
+
+ return VtValue();
+}
+
+} // namespace
+
+Transform convert_transform(const GfMatrix4d &matrix)
+{
+ return make_transform(matrix[0][0],
+ matrix[1][0],
+ matrix[2][0],
+ matrix[3][0],
+ matrix[0][1],
+ matrix[1][1],
+ matrix[2][1],
+ matrix[3][1],
+ matrix[0][2],
+ matrix[1][2],
+ matrix[2][2],
+ matrix[3][2]);
+}
+
+HdCyclesMesh::HdCyclesMesh(const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+ )
+ : HdCyclesGeometry(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ ),
+ _util(&_topology, rprimId)
+{
+}
+
+HdCyclesMesh::~HdCyclesMesh()
+{
+}
+
+HdDirtyBits HdCyclesMesh::GetInitialDirtyBitsMask() const
+{
+ HdDirtyBits bits = HdCyclesGeometry::GetInitialDirtyBitsMask();
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
+ HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyTopology |
+ HdChangeTracker::DirtyDisplayStyle | HdChangeTracker::DirtySubdivTags;
+ return bits;
+}
+
+HdDirtyBits HdCyclesMesh::_PropagateDirtyBits(HdDirtyBits bits) const
+{
+ if (bits & (HdChangeTracker::DirtyMaterialId)) {
+ // Update used shaders from geometry subsets if any exist in the topology
+ bits |= HdChangeTracker::DirtyTopology;
+ }
+
+ if (bits & (HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
+ HdChangeTracker::DirtySubdivTags)) {
+ // Do full topology update when display style or subdivision changes
+ bits |= HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
+ HdChangeTracker::DirtySubdivTags;
+ }
+
+ if (bits & (HdChangeTracker::DirtyTopology)) {
+ // Changing topology clears the geometry, so need to populate everything again
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
+ HdChangeTracker::DirtyPrimvar;
+ }
+
+ return bits;
+}
+
+void HdCyclesMesh::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
+{
+ if (HdChangeTracker::IsTopologyDirty(dirtyBits, GetId())) {
+ PopulateTopology(sceneDelegate);
+ }
+
+ if (dirtyBits & HdChangeTracker::DirtyPoints) {
+ PopulatePoints(sceneDelegate);
+ }
+
+ // Must happen after topology update, so that normals attribute size can be calculated
+ if (dirtyBits & HdChangeTracker::DirtyNormals) {
+ PopulateNormals(sceneDelegate);
+ }
+
+ // Must happen after topology update, so that appropriate attribute set can be selected
+ if (dirtyBits & HdChangeTracker::DirtyPrimvar) {
+ PopulatePrimvars(sceneDelegate);
+ }
+
+ rebuild = (_geom->triangles_is_modified()) || (_geom->subd_start_corner_is_modified()) ||
+ (_geom->subd_num_corners_is_modified()) || (_geom->subd_shader_is_modified()) ||
+ (_geom->subd_smooth_is_modified()) || (_geom->subd_ptex_offset_is_modified()) ||
+ (_geom->subd_face_corners_is_modified());
+}
+
+void HdCyclesMesh::PopulatePoints(HdSceneDelegate *sceneDelegate)
+{
+ VtValue value;
+
+ for (const HdExtComputationPrimvarDescriptor &desc :
+ sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(), HdInterpolationVertex)) {
+ if (desc.name == HdTokens->points) {
+ auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
+ const auto valueStoreIt = valueStore.find(desc.name);
+ if (valueStoreIt != valueStore.end()) {
+ value = std::move(valueStoreIt->second);
+ }
+ break;
+ }
+ }
+
+ if (value.IsEmpty()) {
+ value = GetPoints(sceneDelegate);
+ }
+
+ if (!value.IsHolding<VtVec3fArray>()) {
+ TF_WARN("Invalid points data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &points = value.UncheckedGet<VtVec3fArray>();
+
+ TF_VERIFY(points.size() >= static_cast<size_t>(_topology.GetNumPoints()));
+
+ array<float3> pointsDataCycles;
+ pointsDataCycles.reserve(points.size());
+ for (const GfVec3f &point : points) {
+ pointsDataCycles.push_back_reserved(make_float3(point[0], point[1], point[2]));
+ }
+
+ _geom->set_verts(pointsDataCycles);
+}
+
+void HdCyclesMesh::PopulateNormals(HdSceneDelegate *sceneDelegate)
+{
+ _geom->attributes.remove(ATTR_STD_FACE_NORMAL);
+ _geom->attributes.remove(ATTR_STD_VERTEX_NORMAL);
+
+ // Authored normals should only exist on triangle meshes
+ if (_geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE) {
+ return;
+ }
+
+ VtValue value;
+ HdInterpolation interpolation = HdInterpolationCount;
+
+ for (int i = 0; i < HdInterpolationCount && interpolation == HdInterpolationCount; ++i) {
+ for (const HdExtComputationPrimvarDescriptor &desc :
+ sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(),
+ static_cast<HdInterpolation>(i))) {
+ if (desc.name == HdTokens->normals) {
+ auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
+ const auto valueStoreIt = valueStore.find(desc.name);
+ if (valueStoreIt != valueStore.end()) {
+ value = std::move(valueStoreIt->second);
+ interpolation = static_cast<HdInterpolation>(i);
+ }
+ break;
+ }
+ }
+ }
+
+ if (value.IsEmpty()) {
+ interpolation = GetPrimvarInterpolation(sceneDelegate, HdTokens->normals);
+ if (interpolation == HdInterpolationCount) {
+ return; // Ignore missing normals
+ }
+
+ value = GetNormals(sceneDelegate);
+ }
+
+ if (!value.IsHolding<VtVec3fArray>()) {
+ TF_WARN("Invalid normals data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &normals = value.UncheckedGet<VtVec3fArray>();
+
+ if (interpolation == HdInterpolationConstant) {
+ TF_VERIFY(normals.size() == 1);
+
+ const GfVec3f constantNormal = normals[0];
+
+ float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
+ for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
+ N[i] = make_float3(constantNormal[0], constantNormal[1], constantNormal[2]);
+ }
+ }
+ else if (interpolation == HdInterpolationUniform) {
+ TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaces()));
+
+ float3 *const N = _geom->attributes.add(ATTR_STD_FACE_NORMAL)->data_float3();
+ for (size_t i = 0; i < _geom->num_triangles(); ++i) {
+ const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(_primitiveParams[i]);
+
+ N[i] = make_float3(normals[faceIndex][0], normals[faceIndex][1], normals[faceIndex][2]);
+ }
+ }
+ else if (interpolation == HdInterpolationVertex || interpolation == HdInterpolationVarying) {
+ TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumPoints()) &&
+ static_cast<size_t>(_topology.GetNumPoints()) == _geom->get_verts().size());
+
+ float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
+ for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
+ N[i] = make_float3(normals[i][0], normals[i][1], normals[i][2]);
+ }
+ }
+ else if (interpolation == HdInterpolationFaceVarying) {
+ TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaceVaryings()));
+
+ if (!_util.ComputeTriangulatedFaceVaryingPrimvar(
+ normals.data(), normals.size(), HdTypeFloatVec3, &value)) {
+ return;
+ }
+
+ const auto &normalsTriangulated = value.UncheckedGet<VtVec3fArray>();
+
+ // Cycles has no standard attribute for face-varying normals, so this is a lossy transformation
+ float3 *const N = _geom->attributes.add(ATTR_STD_FACE_NORMAL)->data_float3();
+ for (size_t i = 0; i < _geom->num_triangles(); ++i) {
+ GfVec3f averageNormal = normalsTriangulated[i * 3] + normalsTriangulated[i * 3 + 1] +
+ normalsTriangulated[i * 3 + 2];
+ GfNormalize(&averageNormal);
+
+ N[i] = make_float3(averageNormal[0], averageNormal[1], averageNormal[2]);
+ }
+ }
+}
+
+void HdCyclesMesh::PopulatePrimvars(HdSceneDelegate *sceneDelegate)
+{
+ Scene *const scene = (Scene *)_geom->get_owner();
+
+ const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
+ AttributeSet &attributes = subdivision ? _geom->subd_attributes : _geom->attributes;
+
+ const std::pair<HdInterpolation, AttributeElement> interpolations[] = {
+ std::make_pair(HdInterpolationFaceVarying, ATTR_ELEMENT_CORNER),
+ std::make_pair(HdInterpolationUniform, ATTR_ELEMENT_FACE),
+ std::make_pair(HdInterpolationVertex, ATTR_ELEMENT_VERTEX),
+ std::make_pair(HdInterpolationVarying, ATTR_ELEMENT_VERTEX),
+ std::make_pair(HdInterpolationConstant, ATTR_ELEMENT_OBJECT),
+ };
+
+ for (const auto &interpolation : interpolations) {
+ for (const HdPrimvarDescriptor &desc :
+ GetPrimvarDescriptors(sceneDelegate, interpolation.first)) {
+ // Skip special primvars that are handled separately
+ if (desc.name == HdTokens->points || desc.name == HdTokens->normals) {
+ continue;
+ }
+
+ VtValue value = GetPrimvar(sceneDelegate, desc.name);
+ if (value.IsEmpty()) {
+ continue;
+ }
+
+ const ustring name(desc.name.GetString());
+
+ AttributeStandard std = ATTR_STD_NONE;
+ if (desc.role == HdPrimvarRoleTokens->textureCoordinate) {
+ std = ATTR_STD_UV;
+ }
+ else if (interpolation.first == HdInterpolationVertex) {
+ if (desc.name == HdTokens->displayColor || desc.role == HdPrimvarRoleTokens->color) {
+ std = ATTR_STD_VERTEX_COLOR;
+ }
+ else if (desc.name == HdTokens->normals) {
+ std = ATTR_STD_VERTEX_NORMAL;
+ }
+ }
+ else if (desc.name == HdTokens->displayColor &&
+ interpolation.first == HdInterpolationConstant) {
+ if (value.IsHolding<VtVec3fArray>() && value.GetArraySize() == 1) {
+ const GfVec3f color = value.UncheckedGet<VtVec3fArray>()[0];
+ _instances[0]->set_color(make_float3(color[0], color[1], color[2]));
+ }
+ }
+
+ // Skip attributes that are not needed
+ if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
+ _geom->need_attribute(scene, name)) {
+ const HdType valueType = HdGetValueTupleType(value).type;
+
+ if (!subdivision) {
+ // Adjust attributes for polygons that were triangulated
+ if (interpolation.first == HdInterpolationUniform) {
+ value = ComputeTriangulatedUniformPrimvar(value, valueType, _primitiveParams);
+ if (value.IsEmpty()) {
+ continue;
+ }
+ }
+ else if (interpolation.first == HdInterpolationFaceVarying) {
+ value = ComputeTriangulatedFaceVaryingPrimvar(value, valueType, _util);
+ if (value.IsEmpty()) {
+ continue;
+ }
+ }
+ }
+
+ ApplyPrimvars(attributes, name, value, interpolation.second, std);
+ }
+ }
+ }
+}
+
+void HdCyclesMesh::PopulateTopology(HdSceneDelegate *sceneDelegate)
+{
+ // Clear geometry before populating it again with updated topology
+ _geom->clear(true);
+
+ const HdDisplayStyle displayStyle = GetDisplayStyle(sceneDelegate);
+ _topology = HdMeshTopology(GetMeshTopology(sceneDelegate), displayStyle.refineLevel);
+
+ const TfToken subdivScheme = _topology.GetScheme();
+ if (subdivScheme == PxOsdOpenSubdivTokens->bilinear && _topology.GetRefineLevel() > 0) {
+ _geom->set_subdivision_type(Mesh::SUBDIVISION_LINEAR);
+ }
+ else if (subdivScheme == PxOsdOpenSubdivTokens->catmullClark && _topology.GetRefineLevel() > 0) {
+ _geom->set_subdivision_type(Mesh::SUBDIVISION_CATMULL_CLARK);
+ }
+ else {
+ _geom->set_subdivision_type(Mesh::SUBDIVISION_NONE);
+ }
+
+ const bool smooth = !displayStyle.flatShadingEnabled;
+ const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
+
+ // Initialize lookup table from polygon face to material shader index
+ VtIntArray faceShaders(_topology.GetNumFaces(), 0);
+
+ HdGeomSubsets const &geomSubsets = _topology.GetGeomSubsets();
+ if (!geomSubsets.empty()) {
+ array<Node *> usedShaders = std::move(_geom->get_used_shaders());
+ // Remove any previous materials except for the material assigned to the prim
+ usedShaders.resize(1);
+
+ std::unordered_map<SdfPath, int, SdfPath::Hash> materials;
+
+ for (const HdGeomSubset &geomSubset : geomSubsets) {
+ TF_VERIFY(geomSubset.type == HdGeomSubset::TypeFaceSet);
+
+ int shader = 0;
+ const auto it = materials.find(geomSubset.materialId);
+ if (it != materials.end()) {
+ shader = it->second;
+ }
+ else {
+ const auto material = static_cast<const HdCyclesMaterial *>(
+ sceneDelegate->GetRenderIndex().GetSprim(HdPrimTypeTokens->material,
+ geomSubset.materialId));
+
+ if (material && material->GetCyclesShader()) {
+ shader = static_cast<int>(usedShaders.size());
+ usedShaders.push_back_slow(material->GetCyclesShader());
+
+ materials.emplace(geomSubset.materialId, shader);
+ }
+ }
+
+ for (int face : geomSubset.indices) {
+ faceShaders[face] = shader;
+ }
+ }
+
+ _geom->set_used_shaders(usedShaders);
+ }
+
+ const VtIntArray vertIndx = _topology.GetFaceVertexIndices();
+ const VtIntArray vertCounts = _topology.GetFaceVertexCounts();
+
+ if (!subdivision) {
+ VtVec3iArray triangles;
+ _util.ComputeTriangleIndices(&triangles, &_primitiveParams);
+
+ _geom->reserve_mesh(_topology.GetNumPoints(), triangles.size());
+
+ for (size_t i = 0; i < _primitiveParams.size(); ++i) {
+ const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(_primitiveParams[i]);
+
+ const GfVec3i triangle = triangles[i];
+ _geom->add_triangle(triangle[0], triangle[1], triangle[2], faceShaders[faceIndex], smooth);
+ }
+ }
+ else {
+ PxOsdSubdivTags subdivTags = GetSubdivTags(sceneDelegate);
+ _topology.SetSubdivTags(subdivTags);
+
+ size_t numNgons = 0;
+ size_t numCorners = 0;
+ for (int vertCount : vertCounts) {
+ numNgons += (vertCount == 4) ? 0 : 1;
+ numCorners += vertCount;
+ }
+
+ _geom->reserve_subd_faces(_topology.GetNumFaces(), numNgons, numCorners);
+
+ // TODO: Handle hole indices
+ size_t faceIndex = 0;
+ size_t indexOffset = 0;
+ for (int vertCount : vertCounts) {
+ _geom->add_subd_face(&vertIndx[indexOffset], vertCount, faceShaders[faceIndex], smooth);
+
+ faceIndex++;
+ indexOffset += vertCount;
+ }
+
+ const VtIntArray creaseLengths = subdivTags.GetCreaseLengths();
+ if (!creaseLengths.empty()) {
+ size_t numCreases = 0;
+ for (int creaseLength : creaseLengths) {
+ numCreases += creaseLength - 1;
+ }
+
+ _geom->reserve_subd_creases(numCreases);
+
+ const VtIntArray creaseIndices = subdivTags.GetCreaseIndices();
+ const VtFloatArray creaseWeights = subdivTags.GetCreaseWeights();
+
+ indexOffset = 0;
+ size_t creaseLengthOffset = 0;
+ size_t createWeightOffset = 0;
+ for (int creaseLength : creaseLengths) {
+ for (int j = 0; j < creaseLength - 1; ++j, ++createWeightOffset) {
+ const int v0 = creaseIndices[indexOffset + j];
+ const int v1 = creaseIndices[indexOffset + j + 1];
+
+ float weight = creaseWeights.size() == creaseLengths.size() ?
+ creaseWeights[creaseLengthOffset] :
+ creaseWeights[createWeightOffset];
+
+ _geom->add_edge_crease(v0, v1, weight);
+ }
+
+ indexOffset += creaseLength;
+ creaseLengthOffset++;
+ }
+
+ const VtIntArray cornerIndices = subdivTags.GetCornerIndices();
+ const VtFloatArray cornerWeights = subdivTags.GetCornerWeights();
+
+ for (size_t i = 0; i < cornerIndices.size(); ++i) {
+ _geom->add_vertex_crease(cornerIndices[i], cornerWeights[i]);
+ }
+ }
+
+ _geom->set_subd_dicing_rate(1.0f);
+ _geom->set_subd_max_level(_topology.GetRefineLevel());
+ _geom->set_subd_objecttoworld(_instances[0]->get_tfm());
+ }
+}
+
+void HdCyclesMesh::Finalize(PXR_NS::HdRenderParam *renderParam)
+{
+ _topology = HdMeshTopology();
+ _primitiveParams.clear();
+
+ HdCyclesGeometry<PXR_NS::HdMesh, Mesh>::Finalize(renderParam);
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/mesh.h b/intern/cycles/hydra/mesh.h
new file mode 100644
index 00000000000..e7aa2e4fa94
--- /dev/null
+++ b/intern/cycles/hydra/mesh.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "hydra/geometry.h"
+
+#include <pxr/imaging/hd/mesh.h>
+#include <pxr/imaging/hd/meshUtil.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesMesh final : public HdCyclesGeometry<PXR_NS::HdMesh, CCL_NS::Mesh> {
+ public:
+ HdCyclesMesh(const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId = {}
+#endif
+ );
+ ~HdCyclesMesh() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ private:
+ PXR_NS::HdDirtyBits _PropagateDirtyBits(PXR_NS::HdDirtyBits bits) const override;
+
+ void Populate(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdDirtyBits dirtyBits,
+ bool &rebuild) override;
+
+ void PopulatePoints(PXR_NS::HdSceneDelegate *sceneDelegate);
+ void PopulateNormals(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ void PopulatePrimvars(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ void PopulateTopology(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ PXR_NS::HdMeshUtil _util;
+ PXR_NS::HdMeshTopology _topology;
+ PXR_NS::VtIntArray _primitiveParams;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/node_util.cpp b/intern/cycles/hydra/node_util.cpp
new file mode 100644
index 00000000000..c7e49688f5c
--- /dev/null
+++ b/intern/cycles/hydra/node_util.cpp
@@ -0,0 +1,561 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/node_util.h"
+#include "util/transform.h"
+
+#include <pxr/base/gf/matrix3d.h>
+#include <pxr/base/gf/matrix3f.h>
+#include <pxr/base/gf/matrix4d.h>
+#include <pxr/base/gf/matrix4f.h>
+#include <pxr/base/gf/vec2f.h>
+#include <pxr/base/gf/vec3f.h>
+#include <pxr/base/vt/array.h>
+#include <pxr/usd/sdf/assetPath.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+namespace {
+
+template<typename DstType> DstType convertToCycles(const VtValue &value)
+{
+ if (value.IsHolding<DstType>()) {
+ return value.UncheckedGet<DstType>();
+ }
+
+ VtValue castedValue = VtValue::Cast<DstType>(value);
+ if (castedValue.IsHolding<DstType>()) {
+ return castedValue.UncheckedGet<DstType>();
+ }
+
+ TF_WARN("Could not convert VtValue to Cycles type");
+ return DstType(0);
+}
+
+template<> float2 convertToCycles<float2>(const VtValue &value)
+{
+ const GfVec2f convertedValue = convertToCycles<GfVec2f>(value);
+ return make_float2(convertedValue[0], convertedValue[1]);
+}
+
+template<> float3 convertToCycles<float3>(const VtValue &value)
+{
+ if (value.IsHolding<GfVec3f>()) {
+ const GfVec3f convertedValue = value.UncheckedGet<GfVec3f>();
+ return make_float3(convertedValue[0], convertedValue[1], convertedValue[2]);
+ }
+ if (value.IsHolding<GfVec4f>()) {
+ const GfVec4f convertedValue = value.UncheckedGet<GfVec4f>();
+ return make_float3(convertedValue[0], convertedValue[1], convertedValue[2]);
+ }
+
+ if (value.CanCast<GfVec3f>()) {
+ const GfVec3f convertedValue = VtValue::Cast<GfVec3f>(value).UncheckedGet<GfVec3f>();
+ return make_float3(convertedValue[0], convertedValue[1], convertedValue[2]);
+ }
+ if (value.CanCast<GfVec4f>()) {
+ const GfVec4f convertedValue = VtValue::Cast<GfVec4f>(value).UncheckedGet<GfVec4f>();
+ return make_float3(convertedValue[0], convertedValue[1], convertedValue[2]);
+ }
+
+ TF_WARN("Could not convert VtValue to float3");
+ return zero_float3();
+}
+
+template<> ustring convertToCycles<ustring>(const VtValue &value)
+{
+ if (value.IsHolding<TfToken>()) {
+ return ustring(value.UncheckedGet<TfToken>().GetString());
+ }
+ if (value.IsHolding<std::string>()) {
+ return ustring(value.UncheckedGet<std::string>());
+ }
+ if (value.IsHolding<SdfAssetPath>()) {
+ const SdfAssetPath &path = value.UncheckedGet<SdfAssetPath>();
+ return ustring(path.GetResolvedPath());
+ }
+
+ if (value.CanCast<TfToken>()) {
+ return convertToCycles<ustring>(VtValue::Cast<TfToken>(value));
+ }
+ if (value.CanCast<std::string>()) {
+ return convertToCycles<ustring>(VtValue::Cast<std::string>(value));
+ }
+ if (value.CanCast<SdfAssetPath>()) {
+ return convertToCycles<ustring>(VtValue::Cast<SdfAssetPath>(value));
+ }
+
+ TF_WARN("Could not convert VtValue to ustring");
+ return ustring();
+}
+
+template<typename Matrix>
+Transform convertMatrixToCycles(
+ const typename std::enable_if<Matrix::numRows == 3 && Matrix::numColumns == 3, Matrix>::type
+ &matrix)
+{
+ return make_transform(matrix[0][0],
+ matrix[1][0],
+ matrix[2][0],
+ 0,
+ matrix[0][1],
+ matrix[1][1],
+ matrix[2][1],
+ 0,
+ matrix[0][2],
+ matrix[1][2],
+ matrix[2][2],
+ 0);
+}
+
+template<typename Matrix>
+Transform convertMatrixToCycles(
+ const typename std::enable_if<Matrix::numRows == 4 && Matrix::numColumns == 4, Matrix>::type
+ &matrix)
+{
+ return make_transform(matrix[0][0],
+ matrix[1][0],
+ matrix[2][0],
+ matrix[3][0],
+ matrix[0][1],
+ matrix[1][1],
+ matrix[2][1],
+ matrix[3][1],
+ matrix[0][2],
+ matrix[1][2],
+ matrix[2][2],
+ matrix[3][2]);
+}
+
+template<> Transform convertToCycles<Transform>(const VtValue &value)
+{
+ if (value.IsHolding<GfMatrix4f>()) {
+ return convertMatrixToCycles<GfMatrix4f>(value.UncheckedGet<GfMatrix4f>());
+ }
+ if (value.IsHolding<GfMatrix3f>()) {
+ return convertMatrixToCycles<GfMatrix3f>(value.UncheckedGet<GfMatrix3f>());
+ }
+ if (value.IsHolding<GfMatrix4d>()) {
+ return convertMatrixToCycles<GfMatrix4d>(value.UncheckedGet<GfMatrix4d>());
+ }
+ if (value.IsHolding<GfMatrix3d>()) {
+ return convertMatrixToCycles<GfMatrix3d>(value.UncheckedGet<GfMatrix3d>());
+ }
+
+ if (value.CanCast<GfMatrix4f>()) {
+ return convertToCycles<Transform>(VtValue::Cast<GfMatrix4f>(value));
+ }
+ if (value.CanCast<GfMatrix3f>()) {
+ return convertToCycles<Transform>(VtValue::Cast<GfMatrix3f>(value));
+ }
+ if (value.CanCast<GfMatrix4d>()) {
+ return convertToCycles<Transform>(VtValue::Cast<GfMatrix4d>(value));
+ }
+ if (value.CanCast<GfMatrix3d>()) {
+ return convertToCycles<Transform>(VtValue::Cast<GfMatrix3d>(value));
+ }
+
+ TF_WARN("Could not convert VtValue to Transform");
+ return transform_identity();
+}
+
+template<typename DstType, typename SrcType = DstType>
+array<DstType> convertToCyclesArray(const VtValue &value)
+{
+ static_assert(sizeof(DstType) == sizeof(SrcType),
+ "Size mismatch between VtArray and array base type");
+
+ using SrcArray = VtArray<SrcType>;
+
+ if (value.IsHolding<SrcArray>()) {
+ const auto &valueData = value.UncheckedGet<SrcArray>();
+ array<DstType> cyclesArray;
+ cyclesArray.resize(valueData.size());
+ std::memcpy(cyclesArray.data(), valueData.data(), valueData.size() * sizeof(DstType));
+ return cyclesArray;
+ }
+
+ if (value.CanCast<SrcArray>()) {
+ VtValue castedValue = VtValue::Cast<SrcArray>(value);
+ const auto &valueData = castedValue.UncheckedGet<SrcArray>();
+ array<DstType> cyclesArray;
+ cyclesArray.resize(valueData.size());
+ std::memcpy(cyclesArray.data(), valueData.data(), valueData.size() * sizeof(DstType));
+ return cyclesArray;
+ }
+
+ return array<DstType>();
+}
+
+template<> array<float3> convertToCyclesArray<float3, GfVec3f>(const VtValue &value)
+{
+ if (value.IsHolding<VtVec3fArray>()) {
+ const auto &valueData = value.UncheckedGet<VtVec3fArray>();
+ array<float3> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const GfVec3f &vec : valueData) {
+ cyclesArray.push_back_reserved(make_float3(vec[0], vec[1], vec[2]));
+ }
+ return cyclesArray;
+ }
+ if (value.IsHolding<VtVec4fArray>()) {
+ const auto &valueData = value.UncheckedGet<VtVec4fArray>();
+ array<float3> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const GfVec4f &vec : valueData) {
+ cyclesArray.push_back_reserved(make_float3(vec[0], vec[1], vec[2]));
+ }
+ return cyclesArray;
+ }
+
+ if (value.CanCast<VtVec3fArray>()) {
+ return convertToCyclesArray<float3, GfVec3f>(VtValue::Cast<VtVec3fArray>(value));
+ }
+ if (value.CanCast<VtVec4fArray>()) {
+ return convertToCyclesArray<float3, GfVec3f>(VtValue::Cast<VtVec4fArray>(value));
+ }
+
+ return array<float3>();
+}
+
+template<> array<ustring> convertToCyclesArray<ustring, void>(const VtValue &value)
+{
+ using SdfPathArray = VtArray<SdfAssetPath>;
+
+ if (value.IsHolding<VtStringArray>()) {
+ const auto &valueData = value.UncheckedGet<VtStringArray>();
+ array<ustring> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const auto &element : valueData) {
+ cyclesArray.push_back_reserved(ustring(element));
+ }
+ return cyclesArray;
+ }
+ if (value.IsHolding<VtTokenArray>()) {
+ const auto &valueData = value.UncheckedGet<VtTokenArray>();
+ array<ustring> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const auto &element : valueData) {
+ cyclesArray.push_back_reserved(ustring(element.GetString()));
+ }
+ return cyclesArray;
+ }
+ if (value.IsHolding<SdfPathArray>()) {
+ const auto &valueData = value.UncheckedGet<SdfPathArray>();
+ array<ustring> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const auto &element : valueData) {
+ cyclesArray.push_back_reserved(ustring(element.GetResolvedPath()));
+ }
+ return cyclesArray;
+ }
+
+ if (value.CanCast<VtStringArray>()) {
+ return convertToCyclesArray<ustring, void>(VtValue::Cast<VtStringArray>(value));
+ }
+ if (value.CanCast<VtTokenArray>()) {
+ return convertToCyclesArray<ustring, void>(VtValue::Cast<VtTokenArray>(value));
+ }
+ if (value.CanCast<SdfPathArray>()) {
+ return convertToCyclesArray<ustring, void>(VtValue::Cast<SdfPathArray>(value));
+ }
+
+ TF_WARN("Could not convert VtValue to array<ustring>");
+ return array<ustring>();
+}
+
+template<typename MatrixArray> array<Transform> convertToCyclesTransformArray(const VtValue &value)
+{
+ assert(value.IsHolding<MatrixArray>());
+
+ const auto &valueData = value.UncheckedGet<MatrixArray>();
+ array<Transform> cyclesArray;
+ cyclesArray.reserve(valueData.size());
+ for (const auto &element : valueData) {
+ cyclesArray.push_back_reserved(convertMatrixToCycles<MatrixArray::value_type>(element));
+ }
+ return cyclesArray;
+}
+
+template<> array<Transform> convertToCyclesArray<Transform, void>(const VtValue &value)
+{
+ if (value.IsHolding<VtMatrix4fArray>()) {
+ return convertToCyclesTransformArray<VtMatrix4fArray>(value);
+ }
+ if (value.IsHolding<VtMatrix3fArray>()) {
+ return convertToCyclesTransformArray<VtMatrix3fArray>(value);
+ }
+ if (value.IsHolding<VtMatrix4dArray>()) {
+ return convertToCyclesTransformArray<VtMatrix4dArray>(value);
+ }
+ if (value.IsHolding<VtMatrix3dArray>()) {
+ return convertToCyclesTransformArray<VtMatrix3dArray>(value);
+ }
+
+ if (value.CanCast<VtMatrix4fArray>()) {
+ return convertToCyclesTransformArray<VtMatrix4fArray>(VtValue::Cast<VtMatrix4fArray>(value));
+ }
+ if (value.CanCast<VtMatrix3fArray>()) {
+ return convertToCyclesTransformArray<VtMatrix3fArray>(VtValue::Cast<VtMatrix3fArray>(value));
+ }
+ if (value.CanCast<VtMatrix4dArray>()) {
+ return convertToCyclesTransformArray<VtMatrix4dArray>(VtValue::Cast<VtMatrix4dArray>(value));
+ }
+ if (value.CanCast<VtMatrix3dArray>()) {
+ return convertToCyclesTransformArray<VtMatrix3dArray>(VtValue::Cast<VtMatrix3dArray>(value));
+ }
+
+ TF_WARN("Could not convert VtValue to array<Transform>");
+ return array<Transform>();
+}
+
+template<typename SrcType> VtValue convertFromCycles(const SrcType &value)
+{
+ return VtValue(value);
+}
+
+template<> VtValue convertFromCycles<float2>(const float2 &value)
+{
+ const GfVec2f convertedValue(value.x, value.y);
+ return VtValue(convertedValue);
+}
+
+template<> VtValue convertFromCycles<float3>(const float3 &value)
+{
+ const GfVec3f convertedValue(value.x, value.y, value.z);
+ return VtValue(convertedValue);
+}
+
+template<> VtValue convertFromCycles<ustring>(const ustring &value)
+{
+ return VtValue(value.string());
+}
+
+GfMatrix4f convertMatrixFromCycles(const Transform &matrix)
+{
+ return GfMatrix4f(matrix[0][0],
+ matrix[1][0],
+ matrix[2][0],
+ 0.0f,
+ matrix[0][1],
+ matrix[1][1],
+ matrix[2][1],
+ 0.0f,
+ matrix[0][2],
+ matrix[1][2],
+ matrix[2][2],
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f);
+}
+
+template<> VtValue convertFromCycles<Transform>(const Transform &value)
+{
+ return VtValue(convertMatrixFromCycles(value));
+}
+
+template<typename SrcType, typename DstType = SrcType>
+VtValue convertFromCyclesArray(const array<SrcType> &value)
+{
+ static_assert(sizeof(DstType) == sizeof(SrcType),
+ "Size mismatch between VtArray and array base type");
+
+ VtArray<DstType> convertedValue;
+ convertedValue.resize(value.size());
+ std::memcpy(convertedValue.data(), value.data(), value.size() * sizeof(SrcType));
+ return VtValue(convertedValue);
+}
+
+template<> VtValue convertFromCyclesArray<float3, GfVec3f>(const array<float3> &value)
+{
+ VtVec3fArray convertedValue;
+ convertedValue.reserve(value.size());
+ for (const auto &element : value) {
+ convertedValue.push_back(GfVec3f(element.x, element.y, element.z));
+ }
+ return VtValue(convertedValue);
+}
+
+template<> VtValue convertFromCyclesArray<ustring, void>(const array<ustring> &value)
+{
+ VtStringArray convertedValue;
+ convertedValue.reserve(value.size());
+ for (const auto &element : value) {
+ convertedValue.push_back(element.string());
+ }
+ return VtValue(convertedValue);
+}
+
+template<> VtValue convertFromCyclesArray<Transform, void>(const array<Transform> &value)
+{
+ VtMatrix4fArray convertedValue;
+ convertedValue.reserve(value.size());
+ for (const auto &element : value) {
+ convertedValue.push_back(convertMatrixFromCycles(element));
+ }
+ return VtValue(convertedValue);
+}
+
+} // namespace
+
+void SetNodeValue(Node *node, const SocketType &socket, const VtValue &value)
+{
+ switch (socket.type) {
+ default:
+ case SocketType::UNDEFINED:
+ TF_RUNTIME_ERROR("Unexpected conversion: SocketType::UNDEFINED");
+ break;
+
+ case SocketType::BOOLEAN:
+ node->set(socket, convertToCycles<bool>(value));
+ break;
+ case SocketType::FLOAT:
+ node->set(socket, convertToCycles<float>(value));
+ break;
+ case SocketType::INT:
+ node->set(socket, convertToCycles<int>(value));
+ break;
+ case SocketType::UINT:
+ node->set(socket, convertToCycles<unsigned int>(value));
+ break;
+ case SocketType::COLOR:
+ case SocketType::VECTOR:
+ case SocketType::POINT:
+ case SocketType::NORMAL:
+ node->set(socket, convertToCycles<float3>(value));
+ break;
+ case SocketType::POINT2:
+ node->set(socket, convertToCycles<float2>(value));
+ break;
+ case SocketType::CLOSURE:
+ // Handled by node connections
+ break;
+ case SocketType::STRING:
+ node->set(socket, convertToCycles<ustring>(value));
+ break;
+ case SocketType::ENUM:
+ // Enum's can accept a string or an int
+ if (value.IsHolding<TfToken>() || value.IsHolding<std::string>()) {
+ node->set(socket, convertToCycles<ustring>(value));
+ }
+ else {
+ node->set(socket, convertToCycles<int>(value));
+ }
+ break;
+ case SocketType::TRANSFORM:
+ node->set(socket, convertToCycles<Transform>(value));
+ break;
+ case SocketType::NODE:
+ // TODO: renderIndex->GetRprim()->cycles_node ?
+ TF_WARN("Unimplemented conversion: SocketType::NODE");
+ break;
+
+ case SocketType::BOOLEAN_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<bool>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::FLOAT_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<float>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::INT_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<int>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::COLOR_ARRAY:
+ case SocketType::VECTOR_ARRAY:
+ case SocketType::POINT_ARRAY:
+ case SocketType::NORMAL_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<float3, GfVec3f>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::POINT2_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<float2, GfVec2f>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::STRING_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<ustring, void>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::TRANSFORM_ARRAY: {
+ auto cyclesArray = convertToCyclesArray<Transform, void>(value);
+ node->set(socket, cyclesArray);
+ break;
+ }
+ case SocketType::NODE_ARRAY: {
+ // TODO: renderIndex->GetRprim()->cycles_node ?
+ TF_WARN("Unimplemented conversion: SocketType::NODE_ARRAY");
+ break;
+ }
+ }
+}
+
+VtValue GetNodeValue(const Node *node, const SocketType &socket)
+{
+ switch (socket.type) {
+ default:
+ case SocketType::UNDEFINED:
+ TF_RUNTIME_ERROR("Unexpected conversion: SocketType::UNDEFINED");
+ return VtValue();
+
+ case SocketType::BOOLEAN:
+ return convertFromCycles(node->get_bool(socket));
+ case SocketType::FLOAT:
+ return convertFromCycles(node->get_float(socket));
+ case SocketType::INT:
+ return convertFromCycles(node->get_int(socket));
+ case SocketType::UINT:
+ return convertFromCycles(node->get_uint(socket));
+ case SocketType::COLOR:
+ case SocketType::VECTOR:
+ case SocketType::POINT:
+ case SocketType::NORMAL:
+ return convertFromCycles(node->get_float3(socket));
+ case SocketType::POINT2:
+ return convertFromCycles(node->get_float2(socket));
+ case SocketType::CLOSURE:
+ return VtValue();
+ case SocketType::STRING:
+ return convertFromCycles(node->get_string(socket));
+ case SocketType::ENUM:
+ return convertFromCycles(node->get_int(socket));
+ case SocketType::TRANSFORM:
+ return convertFromCycles(node->get_transform(socket));
+ case SocketType::NODE:
+ TF_WARN("Unimplemented conversion: SocketType::NODE");
+ return VtValue();
+
+ case SocketType::BOOLEAN_ARRAY:
+ return convertFromCyclesArray(node->get_bool_array(socket));
+ case SocketType::FLOAT_ARRAY:
+ return convertFromCyclesArray(node->get_float_array(socket));
+ case SocketType::INT_ARRAY:
+ return convertFromCyclesArray(node->get_int_array(socket));
+ case SocketType::COLOR_ARRAY:
+ case SocketType::VECTOR_ARRAY:
+ case SocketType::POINT_ARRAY:
+ case SocketType::NORMAL_ARRAY:
+ return convertFromCyclesArray<float3, GfVec3f>(node->get_float3_array(socket));
+ case SocketType::POINT2_ARRAY:
+ return convertFromCyclesArray<float2, GfVec2f>(node->get_float2_array(socket));
+ case SocketType::STRING_ARRAY:
+ return convertFromCyclesArray<ustring, void>(node->get_string_array(socket));
+ case SocketType::TRANSFORM_ARRAY:
+ return convertFromCyclesArray<Transform, void>(node->get_transform_array(socket));
+ case SocketType::NODE_ARRAY: {
+ TF_WARN("Unimplemented conversion: SocketType::NODE_ARRAY");
+ return VtValue();
+ }
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/node_util.h b/intern/cycles/hydra/node_util.h
new file mode 100644
index 00000000000..e91f554cc45
--- /dev/null
+++ b/intern/cycles/hydra/node_util.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "graph/node.h"
+
+#include <pxr/base/vt/value.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+void SetNodeValue(CCL_NS::Node *node, const CCL_NS::SocketType &socket, const VtValue &value);
+
+VtValue GetNodeValue(const CCL_NS::Node *node, const CCL_NS::SocketType &socket);
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/output_driver.cpp b/intern/cycles/hydra/output_driver.cpp
new file mode 100644
index 00000000000..c5f64ac1c18
--- /dev/null
+++ b/intern/cycles/hydra/output_driver.cpp
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/output_driver.h"
+#include "hydra/render_buffer.h"
+#include "hydra/session.h"
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesOutputDriver::HdCyclesOutputDriver(HdCyclesSession *renderParam)
+ : _renderParam(renderParam)
+{
+}
+
+void HdCyclesOutputDriver::write_render_tile(const Tile &tile)
+{
+ update_render_tile(tile);
+
+ // Update convergence state of all render buffers
+ for (const HdRenderPassAovBinding &aovBinding : _renderParam->GetAovBindings()) {
+ if (const auto renderBuffer = static_cast<HdCyclesRenderBuffer *>(aovBinding.renderBuffer)) {
+ renderBuffer->SetConverged(true);
+ }
+ }
+}
+
+bool HdCyclesOutputDriver::update_render_tile(const Tile &tile)
+{
+ std::vector<float> pixels;
+
+ for (const HdRenderPassAovBinding &aovBinding : _renderParam->GetAovBindings()) {
+ if (aovBinding == _renderParam->GetDisplayAovBinding()) {
+ continue; // Display AOV binding is already updated by Cycles display driver
+ }
+
+ if (const auto renderBuffer = static_cast<HdCyclesRenderBuffer *>(aovBinding.renderBuffer)) {
+ const HdFormat format = renderBuffer->GetFormat();
+ if (format == HdFormatInvalid) {
+ continue; // Skip invalid AOV bindings
+ }
+
+ const size_t channels = HdGetComponentCount(format);
+ // Avoid extra copy by mapping render buffer directly when dimensions/format match the tile
+ if (tile.offset.x == 0 && tile.offset.y == 0 && tile.size.x == renderBuffer->GetWidth() &&
+ tile.size.y == renderBuffer->GetHeight() &&
+ (format >= HdFormatFloat32 && format <= HdFormatFloat32Vec4)) {
+ float *const data = static_cast<float *>(renderBuffer->Map());
+ TF_VERIFY(tile.get_pass_pixels(aovBinding.aovName.GetString(), channels, data));
+ renderBuffer->Unmap();
+ }
+ else {
+ pixels.resize(channels * tile.size.x * tile.size.y);
+ if (tile.get_pass_pixels(aovBinding.aovName.GetString(), channels, pixels.data())) {
+ const bool isId = aovBinding.aovName == HdAovTokens->primId ||
+ aovBinding.aovName == HdAovTokens->elementId ||
+ aovBinding.aovName == HdAovTokens->instanceId;
+
+ renderBuffer->WritePixels(pixels.data(),
+ GfVec2i(tile.offset.x, tile.offset.y),
+ GfVec2i(tile.size.x, tile.size.y),
+ channels,
+ isId);
+ }
+ else {
+ // Do not warn on missing elementId, which is a standard AOV but is not implememted
+ if (aovBinding.aovName != HdAovTokens->elementId) {
+ TF_RUNTIME_ERROR("Could not find pass for AOV '%s'", aovBinding.aovName.GetText());
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/output_driver.h b/intern/cycles/hydra/output_driver.h
new file mode 100644
index 00000000000..f10adfccf43
--- /dev/null
+++ b/intern/cycles/hydra/output_driver.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "session/output_driver.h"
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesOutputDriver final : public CCL_NS::OutputDriver {
+ public:
+ HdCyclesOutputDriver(HdCyclesSession *renderParam);
+
+ private:
+ void write_render_tile(const Tile &tile) override;
+ bool update_render_tile(const Tile &tile) override;
+
+ HdCyclesSession *const _renderParam;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/plugInfo.json b/intern/cycles/hydra/plugInfo.json
new file mode 100644
index 00000000000..2e20f3d66a7
--- /dev/null
+++ b/intern/cycles/hydra/plugInfo.json
@@ -0,0 +1,3 @@
+{
+ "Includes": [ "*/resources/" ]
+}
diff --git a/intern/cycles/hydra/plugin.cpp b/intern/cycles/hydra/plugin.cpp
new file mode 100644
index 00000000000..8caca3068df
--- /dev/null
+++ b/intern/cycles/hydra/plugin.cpp
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/plugin.h"
+#include "hydra/render_delegate.h"
+#include "util/log.h"
+#include "util/path.h"
+
+#include <pxr/base/arch/filesystem.h>
+#include <pxr/base/plug/plugin.h>
+#include <pxr/base/plug/thisPlugin.h>
+#include <pxr/base/tf/envSetting.h>
+#include <pxr/imaging/hd/rendererPluginRegistry.h>
+
+PXR_NAMESPACE_OPEN_SCOPE
+
+#ifdef WITH_CYCLES_LOGGING
+TF_DEFINE_ENV_SETTING(CYCLES_LOGGING, false, "Enable Cycles logging")
+TF_DEFINE_ENV_SETTING(CYCLES_LOGGING_SEVERITY, 1, "Cycles logging verbosity")
+#endif
+
+HdCyclesPlugin::HdCyclesPlugin()
+{
+ const PlugPluginPtr plugin = PLUG_THIS_PLUGIN;
+ // Initialize Cycles paths relative to the plugin resource path
+ std::string rootPath = PXR_NS::ArchAbsPath(plugin->GetResourcePath());
+ CCL_NS::path_init(std::move(rootPath));
+
+#ifdef WITH_CYCLES_LOGGING
+ if (TfGetEnvSetting(CYCLES_LOGGING)) {
+ CCL_NS::util_logging_start();
+ CCL_NS::util_logging_verbosity_set(TfGetEnvSetting(CYCLES_LOGGING_SEVERITY));
+ }
+#endif
+}
+
+HdCyclesPlugin::~HdCyclesPlugin()
+{
+}
+
+bool HdCyclesPlugin::IsSupported() const
+{
+ return true;
+}
+
+HdRenderDelegate *HdCyclesPlugin::CreateRenderDelegate()
+{
+ return CreateRenderDelegate({});
+}
+
+HdRenderDelegate *HdCyclesPlugin::CreateRenderDelegate(const HdRenderSettingsMap &settingsMap)
+{
+ return new HD_CYCLES_NS::HdCyclesDelegate(settingsMap);
+}
+
+void HdCyclesPlugin::DeleteRenderDelegate(HdRenderDelegate *renderDelegate)
+{
+ delete renderDelegate;
+}
+
+// USD's type system accounts for namespace, so we'd have to register our name as
+// HdCycles::HdCyclesPlugin in plugInfo.json, which isn't all that bad for JSON,
+// but those colons may cause issues for any USD specific tooling. So just put our
+// plugin class in the pxr namespace (which USD's type system will elide).
+TF_REGISTRY_FUNCTION(TfType)
+{
+ HdRendererPluginRegistry::Define<PXR_NS::HdCyclesPlugin>();
+}
+
+PXR_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/plugin.h b/intern/cycles/hydra/plugin.h
new file mode 100644
index 00000000000..0a76c827fda
--- /dev/null
+++ b/intern/cycles/hydra/plugin.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include <pxr/imaging/hd/rendererPlugin.h>
+
+PXR_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesPlugin final : public PXR_NS::HdRendererPlugin {
+ public:
+ HdCyclesPlugin();
+ ~HdCyclesPlugin() override;
+
+ bool IsSupported() const override;
+
+ PXR_NS::HdRenderDelegate *CreateRenderDelegate() override;
+ PXR_NS::HdRenderDelegate *CreateRenderDelegate(const PXR_NS::HdRenderSettingsMap &) override;
+
+ void DeleteRenderDelegate(PXR_NS::HdRenderDelegate *) override;
+};
+
+PXR_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/pointcloud.cpp b/intern/cycles/hydra/pointcloud.cpp
new file mode 100644
index 00000000000..8d43fd8bfcd
--- /dev/null
+++ b/intern/cycles/hydra/pointcloud.cpp
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/pointcloud.h"
+#include "hydra/geometry.inl"
+#include "scene/pointcloud.h"
+
+#include <pxr/imaging/hd/extComputationUtils.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesPoints::HdCyclesPoints(const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+ )
+ : HdCyclesGeometry(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ )
+{
+}
+
+HdCyclesPoints::~HdCyclesPoints()
+{
+}
+
+HdDirtyBits HdCyclesPoints::GetInitialDirtyBitsMask() const
+{
+ HdDirtyBits bits = HdCyclesGeometry::GetInitialDirtyBitsMask();
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths |
+ HdChangeTracker::DirtyPrimvar;
+ return bits;
+}
+
+HdDirtyBits HdCyclesPoints::_PropagateDirtyBits(HdDirtyBits bits) const
+{
+ // Points and widths always have to be updated together
+ if (bits & (HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths)) {
+ bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths;
+ }
+
+ return bits;
+}
+
+void HdCyclesPoints::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
+{
+ if (dirtyBits & (HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyWidths)) {
+ const size_t numPoints = _geom->num_points();
+
+ PopulatePoints(sceneDelegate);
+ PopulateWidths(sceneDelegate);
+
+ rebuild = _geom->num_points() != numPoints;
+
+ array<int> shaders;
+ shaders.reserve(_geom->num_points());
+ for (size_t i = 0; i < _geom->num_points(); ++i) {
+ shaders.push_back_reserved(0);
+ }
+
+ _geom->set_shader(shaders);
+ }
+
+ if (dirtyBits & HdChangeTracker::DirtyPrimvar) {
+ PopulatePrimvars(sceneDelegate);
+ }
+}
+
+void HdCyclesPoints::PopulatePoints(HdSceneDelegate *sceneDelegate)
+{
+ VtValue value;
+
+ for (const HdExtComputationPrimvarDescriptor &desc :
+ sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(), HdInterpolationVertex)) {
+ if (desc.name == HdTokens->points) {
+ auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
+ const auto valueStoreIt = valueStore.find(desc.name);
+ if (valueStoreIt != valueStore.end()) {
+ value = std::move(valueStoreIt->second);
+ }
+ break;
+ }
+ }
+
+ if (value.IsEmpty()) {
+ value = GetPrimvar(sceneDelegate, HdTokens->points);
+ }
+
+ if (!value.IsHolding<VtVec3fArray>()) {
+ TF_WARN("Invalid points data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &points = value.UncheckedGet<VtVec3fArray>();
+
+ array<float3> pointsDataCycles;
+ pointsDataCycles.reserve(points.size());
+
+ for (const GfVec3f &point : points) {
+ pointsDataCycles.push_back_reserved(make_float3(point[0], point[1], point[2]));
+ }
+
+ _geom->set_points(pointsDataCycles);
+}
+
+void HdCyclesPoints::PopulateWidths(HdSceneDelegate *sceneDelegate)
+{
+ VtValue value = GetPrimvar(sceneDelegate, HdTokens->widths);
+ const HdInterpolation interpolation = GetPrimvarInterpolation(sceneDelegate, HdTokens->widths);
+
+ if (!value.IsHolding<VtFloatArray>()) {
+ TF_WARN("Invalid widths data for %s", GetId().GetText());
+ return;
+ }
+
+ const auto &widths = value.UncheckedGet<VtFloatArray>();
+
+ array<float> radiusDataCycles;
+ radiusDataCycles.reserve(_geom->num_points());
+
+ if (interpolation == HdInterpolationConstant) {
+ TF_VERIFY(widths.size() == 1);
+
+ const float constantRadius = widths[0] * 0.5f;
+
+ for (size_t i = 0; i < _geom->num_points(); ++i) {
+ radiusDataCycles.push_back_reserved(constantRadius);
+ }
+ }
+ else if (interpolation == HdInterpolationVertex) {
+ TF_VERIFY(widths.size() == _geom->num_points());
+
+ for (size_t i = 0; i < _geom->num_points(); ++i) {
+ radiusDataCycles.push_back_reserved(widths[i] * 0.5f);
+ }
+ }
+
+ _geom->set_radius(radiusDataCycles);
+}
+
+void HdCyclesPoints::PopulatePrimvars(HdSceneDelegate *sceneDelegate)
+{
+ Scene *const scene = (Scene *)_geom->get_owner();
+
+ const std::pair<HdInterpolation, AttributeElement> interpolations[] = {
+ std::make_pair(HdInterpolationVertex, ATTR_ELEMENT_VERTEX),
+ std::make_pair(HdInterpolationConstant, ATTR_ELEMENT_OBJECT),
+ };
+
+ for (const auto &interpolation : interpolations) {
+ for (const HdPrimvarDescriptor &desc :
+ GetPrimvarDescriptors(sceneDelegate, interpolation.first)) {
+ // Skip special primvars that are handled separately
+ if (desc.name == HdTokens->points || desc.name == HdTokens->widths) {
+ continue;
+ }
+
+ VtValue value = GetPrimvar(sceneDelegate, desc.name);
+ if (value.IsEmpty()) {
+ continue;
+ }
+
+ const ustring name(desc.name.GetString());
+
+ AttributeStandard std = ATTR_STD_NONE;
+ if (desc.role == HdPrimvarRoleTokens->textureCoordinate) {
+ std = ATTR_STD_UV;
+ }
+ else if (interpolation.first == HdInterpolationVertex) {
+ if (desc.name == HdTokens->displayColor || desc.role == HdPrimvarRoleTokens->color) {
+ std = ATTR_STD_VERTEX_COLOR;
+ }
+ else if (desc.name == HdTokens->normals) {
+ std = ATTR_STD_VERTEX_NORMAL;
+ }
+ }
+ else if (desc.name == HdTokens->displayColor &&
+ interpolation.first == HdInterpolationConstant) {
+ if (value.IsHolding<VtVec3fArray>() && value.GetArraySize() == 1) {
+ const GfVec3f color = value.UncheckedGet<VtVec3fArray>()[0];
+ _instances[0]->set_color(make_float3(color[0], color[1], color[2]));
+ }
+ }
+
+ // Skip attributes that are not needed
+ if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
+ _geom->need_attribute(scene, name)) {
+ ApplyPrimvars(_geom->attributes, name, value, interpolation.second, std);
+ }
+ }
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/pointcloud.h b/intern/cycles/hydra/pointcloud.h
new file mode 100644
index 00000000000..aa1768e807f
--- /dev/null
+++ b/intern/cycles/hydra/pointcloud.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "hydra/geometry.h"
+
+#include <pxr/imaging/hd/points.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesPoints final : public HdCyclesGeometry<PXR_NS::HdPoints, CCL_NS::PointCloud> {
+ public:
+ HdCyclesPoints(const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId = {}
+#endif
+ );
+ ~HdCyclesPoints() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ private:
+ PXR_NS::HdDirtyBits _PropagateDirtyBits(PXR_NS::HdDirtyBits bits) const override;
+
+ void Populate(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdDirtyBits dirtyBits,
+ bool &rebuild) override;
+
+ void PopulatePoints(PXR_NS::HdSceneDelegate *sceneDelegate);
+ void PopulateWidths(PXR_NS::HdSceneDelegate *sceneDelegate);
+
+ void PopulatePrimvars(PXR_NS::HdSceneDelegate *sceneDelegate);
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_buffer.cpp b/intern/cycles/hydra/render_buffer.cpp
new file mode 100644
index 00000000000..4867def0624
--- /dev/null
+++ b/intern/cycles/hydra/render_buffer.cpp
@@ -0,0 +1,276 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/render_buffer.h"
+#include "hydra/session.h"
+#include "util/half.h"
+
+#include <pxr/base/gf/vec3i.h>
+#include <pxr/base/gf/vec4f.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesRenderBuffer::HdCyclesRenderBuffer(const SdfPath &bprimId) : HdRenderBuffer(bprimId)
+{
+}
+
+HdCyclesRenderBuffer::~HdCyclesRenderBuffer()
+{
+}
+
+void HdCyclesRenderBuffer::Finalize(HdRenderParam *renderParam)
+{
+ // Remove this render buffer from AOV bindings
+ // This ensures that 'OutputDriver' does not attempt to write to it anymore
+ static_cast<HdCyclesSession *>(renderParam)->RemoveAovBinding(this);
+
+ HdRenderBuffer::Finalize(renderParam);
+}
+
+bool HdCyclesRenderBuffer::Allocate(const GfVec3i &dimensions, HdFormat format, bool multiSampled)
+{
+ if (dimensions[2] != 1) {
+ TF_RUNTIME_ERROR("HdCyclesRenderBuffer::Allocate called with dimensions that are not 2D.");
+ return false;
+ }
+
+ const size_t oldSize = _data.size();
+ const size_t newSize = dimensions[0] * dimensions[1] * HdDataSizeOfFormat(format);
+ if (oldSize == newSize) {
+ return true;
+ }
+
+ if (IsMapped()) {
+ TF_RUNTIME_ERROR("HdCyclesRenderBuffer::Allocate called while buffer is mapped.");
+ return false;
+ }
+
+ _width = dimensions[0];
+ _height = dimensions[1];
+ _format = format;
+
+ _data.resize(newSize);
+
+ return true;
+}
+
+void HdCyclesRenderBuffer::_Deallocate()
+{
+ _width = 0u;
+ _height = 0u;
+ _format = HdFormatInvalid;
+
+ _data.clear();
+ _data.shrink_to_fit();
+
+ _resource = VtValue();
+}
+
+void *HdCyclesRenderBuffer::Map()
+{
+ // Mapping is not implemented when a resource is set
+ if (!_resource.IsEmpty()) {
+ return nullptr;
+ }
+
+ ++_mapped;
+
+ return _data.data();
+}
+
+void HdCyclesRenderBuffer::Unmap()
+{
+ --_mapped;
+}
+
+bool HdCyclesRenderBuffer::IsMapped() const
+{
+ return _mapped != 0;
+}
+
+void HdCyclesRenderBuffer::Resolve()
+{
+}
+
+bool HdCyclesRenderBuffer::IsConverged() const
+{
+ return _converged;
+}
+
+void HdCyclesRenderBuffer::SetConverged(bool converged)
+{
+ _converged = converged;
+}
+
+VtValue HdCyclesRenderBuffer::GetResource(bool multiSampled) const
+{
+ TF_UNUSED(multiSampled);
+
+ return _resource;
+}
+
+void HdCyclesRenderBuffer::SetResource(const VtValue &resource)
+{
+ _resource = resource;
+}
+
+namespace {
+
+struct SimpleConversion {
+ static float convert(float value)
+ {
+ return value;
+ }
+};
+struct IdConversion {
+ static int32_t convert(float value)
+ {
+ return static_cast<int32_t>(value) - 1;
+ }
+};
+struct UInt8Conversion {
+ static uint8_t convert(float value)
+ {
+ return static_cast<uint8_t>(value * 255.f);
+ }
+};
+struct SInt8Conversion {
+ static int8_t convert(float value)
+ {
+ return static_cast<int8_t>(value * 127.f);
+ }
+};
+struct HalfConversion {
+ static half convert(float value)
+ {
+ return float_to_half_image(value);
+ }
+};
+
+template<typename SrcT, typename DstT, typename Convertor = SimpleConversion>
+void writePixels(const SrcT *srcPtr,
+ const GfVec2i &srcSize,
+ int srcChannelCount,
+ DstT *dstPtr,
+ const GfVec2i &dstSize,
+ int dstChannelCount,
+ const Convertor &convertor = {})
+{
+ const auto writeSize = GfVec2i(GfMin(srcSize[0], dstSize[0]), GfMin(srcSize[1], dstSize[1]));
+ const auto writeChannelCount = GfMin(srcChannelCount, dstChannelCount);
+
+ for (int y = 0; y < writeSize[1]; ++y) {
+ for (int x = 0; x < writeSize[0]; ++x) {
+ for (int c = 0; c < writeChannelCount; ++c) {
+ dstPtr[x * dstChannelCount + c] = convertor.convert(srcPtr[x * srcChannelCount + c]);
+ }
+ }
+ srcPtr += srcSize[0] * srcChannelCount;
+ dstPtr += dstSize[0] * dstChannelCount;
+ }
+}
+
+} // namespace
+
+void HdCyclesRenderBuffer::WritePixels(const float *srcPixels,
+ const PXR_NS::GfVec2i &srcOffset,
+ const GfVec2i &srcDims,
+ int srcChannels,
+ bool isId)
+{
+ uint8_t *dstPixels = _data.data();
+
+ const size_t formatSize = HdDataSizeOfFormat(_format);
+ dstPixels += srcOffset[1] * (formatSize * _width) + srcOffset[0] * formatSize;
+
+ switch (_format) {
+ case HdFormatUNorm8:
+ case HdFormatUNorm8Vec2:
+ case HdFormatUNorm8Vec3:
+ case HdFormatUNorm8Vec4:
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ dstPixels,
+ GfVec2i(_width, _height),
+ 1 + (_format - HdFormatUNorm8),
+ UInt8Conversion());
+ break;
+
+ case HdFormatSNorm8:
+ case HdFormatSNorm8Vec2:
+ case HdFormatSNorm8Vec3:
+ case HdFormatSNorm8Vec4:
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ dstPixels,
+ GfVec2i(_width, _height),
+ 1 + (_format - HdFormatSNorm8),
+ SInt8Conversion());
+ break;
+
+ case HdFormatFloat16:
+ case HdFormatFloat16Vec2:
+ case HdFormatFloat16Vec3:
+ case HdFormatFloat16Vec4:
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ reinterpret_cast<half *>(dstPixels),
+ GfVec2i(_width, _height),
+ 1 + (_format - HdFormatFloat16),
+ HalfConversion());
+ break;
+
+ case HdFormatFloat32:
+ case HdFormatFloat32Vec2:
+ case HdFormatFloat32Vec3:
+ case HdFormatFloat32Vec4:
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ reinterpret_cast<float *>(dstPixels),
+ GfVec2i(_width, _height),
+ 1 + (_format - HdFormatFloat32));
+ break;
+
+ case HdFormatInt32:
+ // Special case for ID AOVs (see 'HdCyclesMesh::Sync')
+ if (isId) {
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ reinterpret_cast<int *>(dstPixels),
+ GfVec2i(_width, _height),
+ 1,
+ IdConversion());
+ }
+ else {
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ reinterpret_cast<int *>(dstPixels),
+ GfVec2i(_width, _height),
+ 1);
+ }
+ break;
+ case HdFormatInt32Vec2:
+ case HdFormatInt32Vec3:
+ case HdFormatInt32Vec4:
+ writePixels(srcPixels,
+ srcDims,
+ srcChannels,
+ reinterpret_cast<int *>(dstPixels),
+ GfVec2i(_width, _height),
+ 1 + (_format - HdFormatInt32));
+ break;
+
+ default:
+ TF_RUNTIME_ERROR("HdCyclesRenderBuffer::WritePixels called with unsupported format.");
+ break;
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_buffer.h b/intern/cycles/hydra/render_buffer.h
new file mode 100644
index 00000000000..8eb874f0068
--- /dev/null
+++ b/intern/cycles/hydra/render_buffer.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/renderBuffer.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesRenderBuffer final : public PXR_NS::HdRenderBuffer {
+ public:
+ HdCyclesRenderBuffer(const PXR_NS::SdfPath &bprimId);
+ ~HdCyclesRenderBuffer() override;
+
+ void Finalize(PXR_NS::HdRenderParam *renderParam) override;
+
+ bool Allocate(const PXR_NS::GfVec3i &dimensions,
+ PXR_NS::HdFormat format,
+ bool multiSampled) override;
+
+ unsigned int GetWidth() const override
+ {
+ return _width;
+ }
+
+ unsigned int GetHeight() const override
+ {
+ return _height;
+ }
+
+ unsigned int GetDepth() const override
+ {
+ return 1u;
+ }
+
+ PXR_NS::HdFormat GetFormat() const override
+ {
+ return _format;
+ }
+
+ bool IsMultiSampled() const override
+ {
+ return false;
+ }
+
+ void *Map() override;
+
+ void Unmap() override;
+
+ bool IsMapped() const override;
+
+ void Resolve() override;
+
+ bool IsConverged() const override;
+
+ void SetConverged(bool converged);
+
+ PXR_NS::VtValue GetResource(bool multiSampled = false) const override;
+
+ void SetResource(const PXR_NS::VtValue &resource);
+
+ void WritePixels(const float *pixels,
+ const PXR_NS::GfVec2i &offset,
+ const PXR_NS::GfVec2i &dims,
+ int channels,
+ bool isId = false);
+
+ private:
+ void _Deallocate() override;
+
+ unsigned int _width = 0u;
+ unsigned int _height = 0u;
+ PXR_NS::HdFormat _format = PXR_NS::HdFormatInvalid;
+
+ std::vector<uint8_t> _data;
+ PXR_NS::VtValue _resource;
+
+ std::atomic_int _mapped = 0;
+ std::atomic_bool _converged = false;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_delegate.cpp b/intern/cycles/hydra/render_delegate.cpp
new file mode 100644
index 00000000000..748b6a66e1e
--- /dev/null
+++ b/intern/cycles/hydra/render_delegate.cpp
@@ -0,0 +1,514 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/render_delegate.h"
+#include "hydra/camera.h"
+#include "hydra/curves.h"
+#include "hydra/field.h"
+#include "hydra/instancer.h"
+#include "hydra/light.h"
+#include "hydra/material.h"
+#include "hydra/mesh.h"
+#include "hydra/node_util.h"
+#include "hydra/pointcloud.h"
+#include "hydra/render_buffer.h"
+#include "hydra/render_pass.h"
+#include "hydra/session.h"
+#include "hydra/volume.h"
+#include "scene/integrator.h"
+#include "scene/scene.h"
+#include "session/session.h"
+
+#include <pxr/base/tf/getenv.h>
+#include <pxr/imaging/hd/extComputation.h>
+#include <pxr/imaging/hgi/tokens.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+// clang-format off
+TF_DEFINE_PRIVATE_TOKENS(_tokens,
+ (cycles)
+ (openvdbAsset)
+);
+
+TF_DEFINE_PRIVATE_TOKENS(HdCyclesRenderSettingsTokens,
+ ((device, "cycles:device"))
+ ((threads, "cycles:threads"))
+ ((time_limit, "cycles:time_limit"))
+ ((samples, "cycles:samples"))
+ ((sample_offset, "cycles:sample_offset"))
+);
+// clang-format on
+
+namespace {
+
+const TfTokenVector kSupportedRPrimTypes = {
+ HdPrimTypeTokens->basisCurves,
+ HdPrimTypeTokens->mesh,
+ HdPrimTypeTokens->points,
+#ifdef WITH_OPENVDB
+ HdPrimTypeTokens->volume,
+#endif
+};
+
+const TfTokenVector kSupportedSPrimTypes = {
+ HdPrimTypeTokens->camera,
+ HdPrimTypeTokens->material,
+ HdPrimTypeTokens->diskLight,
+ HdPrimTypeTokens->distantLight,
+ HdPrimTypeTokens->domeLight,
+ HdPrimTypeTokens->rectLight,
+ HdPrimTypeTokens->sphereLight,
+ HdPrimTypeTokens->extComputation,
+};
+
+const TfTokenVector kSupportedBPrimTypes = {
+ HdPrimTypeTokens->renderBuffer,
+#ifdef WITH_OPENVDB
+ _tokens->openvdbAsset,
+#endif
+};
+
+SessionParams GetSessionParams(const HdRenderSettingsMap &settings)
+{
+ SessionParams params;
+ params.threads = 0;
+ params.background = false;
+ params.use_resolution_divider = false;
+
+ HdRenderSettingsMap::const_iterator it;
+
+ // Pull all setting that contribute to device creation first
+ it = settings.find(HdCyclesRenderSettingsTokens->threads);
+ if (it != settings.end()) {
+ params.threads = VtValue::Cast<int>(it->second).GetWithDefault(params.threads);
+ }
+
+ // Get the Cycles device from settings or environment, falling back to CPU
+ std::string deviceType = Device::string_from_type(DEVICE_CPU);
+ it = settings.find(HdCyclesRenderSettingsTokens->device);
+ if (it != settings.end()) {
+ deviceType = VtValue::Cast<std::string>(it->second).GetWithDefault(deviceType);
+ }
+ else {
+ const std::string deviceTypeEnv = TfGetenv("CYCLES_DEVICE");
+ if (!deviceTypeEnv.empty()) {
+ deviceType = deviceTypeEnv;
+ }
+ }
+
+ // Move to all uppercase for Device::type_from_string
+ std::transform(deviceType.begin(), deviceType.end(), deviceType.begin(), ::toupper);
+
+ vector<DeviceInfo> devices = Device::available_devices(
+ DEVICE_MASK(Device::type_from_string(deviceType.c_str())));
+ if (devices.empty()) {
+ devices = Device::available_devices(DEVICE_MASK_CPU);
+ if (!devices.empty()) {
+ params.device = devices.front();
+ }
+ }
+ else {
+ params.device = Device::get_multi_device(devices, params.threads, params.background);
+ }
+
+ return params;
+}
+
+} // namespace
+
+HdCyclesDelegate::HdCyclesDelegate(const HdRenderSettingsMap &settingsMap, Session *session_)
+ : HdRenderDelegate()
+{
+ _renderParam = session_ ? std::make_unique<HdCyclesSession>(session_) :
+ std::make_unique<HdCyclesSession>(GetSessionParams(settingsMap));
+
+ // If the delegate owns the session, pull any remaining settings
+ if (!session_) {
+ for (const auto &setting : settingsMap) {
+ // Skip over the settings known to be used for initialization only
+ if (setting.first == HdCyclesRenderSettingsTokens->device ||
+ setting.first == HdCyclesRenderSettingsTokens->threads) {
+ continue;
+ }
+
+ SetRenderSetting(setting.first, setting.second);
+ }
+ }
+}
+
+HdCyclesDelegate::~HdCyclesDelegate()
+{
+}
+
+void HdCyclesDelegate::SetDrivers(const HdDriverVector &drivers)
+{
+ for (HdDriver *hdDriver : drivers) {
+ if (hdDriver->name == HgiTokens->renderDriver && hdDriver->driver.IsHolding<Hgi *>()) {
+ _hgi = hdDriver->driver.UncheckedGet<Hgi *>();
+ break;
+ }
+ }
+}
+
+bool HdCyclesDelegate::IsDisplaySupported() const
+{
+#ifdef _WIN32
+ return _hgi && _hgi->GetAPIName() == HgiTokens->OpenGL;
+#else
+ return false;
+#endif
+}
+
+const TfTokenVector &HdCyclesDelegate::GetSupportedRprimTypes() const
+{
+ return kSupportedRPrimTypes;
+}
+
+const TfTokenVector &HdCyclesDelegate::GetSupportedSprimTypes() const
+{
+ return kSupportedSPrimTypes;
+}
+
+const TfTokenVector &HdCyclesDelegate::GetSupportedBprimTypes() const
+{
+ return kSupportedBPrimTypes;
+}
+
+HdRenderParam *HdCyclesDelegate::GetRenderParam() const
+{
+ return _renderParam.get();
+}
+
+HdResourceRegistrySharedPtr HdCyclesDelegate::GetResourceRegistry() const
+{
+ return HdResourceRegistrySharedPtr();
+}
+
+bool HdCyclesDelegate::IsPauseSupported() const
+{
+ return true;
+}
+
+bool HdCyclesDelegate::Pause()
+{
+ _renderParam->session->set_pause(true);
+ return true;
+}
+
+bool HdCyclesDelegate::Resume()
+{
+ _renderParam->session->set_pause(false);
+ return true;
+}
+
+HdRenderPassSharedPtr HdCyclesDelegate::CreateRenderPass(HdRenderIndex *index,
+ const HdRprimCollection &collection)
+{
+ return HdRenderPassSharedPtr(new HdCyclesRenderPass(index, collection, _renderParam.get()));
+}
+
+HdInstancer *HdCyclesDelegate::CreateInstancer(HdSceneDelegate *delegate,
+ const SdfPath &instancerId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &parentId
+#endif
+)
+{
+ return new HdCyclesInstancer(delegate,
+ instancerId
+#if PXR_VERSION < 2102
+ ,
+ parentId
+#endif
+ );
+}
+
+void HdCyclesDelegate::DestroyInstancer(HdInstancer *instancer)
+{
+ delete instancer;
+}
+
+HdRprim *HdCyclesDelegate::CreateRprim(const TfToken &typeId,
+ const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+)
+{
+ if (typeId == HdPrimTypeTokens->mesh) {
+ return new HdCyclesMesh(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ );
+ }
+ if (typeId == HdPrimTypeTokens->basisCurves) {
+ return new HdCyclesCurves(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ );
+ }
+ if (typeId == HdPrimTypeTokens->points) {
+ return new HdCyclesPoints(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ );
+ }
+#ifdef WITH_OPENVDB
+ if (typeId == HdPrimTypeTokens->volume) {
+ return new HdCyclesVolume(rprimId
+# if PXR_VERSION < 2102
+ ,
+ instancerId
+# endif
+ );
+ }
+#endif
+
+ TF_CODING_ERROR("Unknown Rprim type %s", typeId.GetText());
+ return nullptr;
+}
+
+void HdCyclesDelegate::DestroyRprim(HdRprim *rPrim)
+{
+ delete rPrim;
+}
+
+HdSprim *HdCyclesDelegate::CreateSprim(const TfToken &typeId, const SdfPath &sprimId)
+{
+ if (typeId == HdPrimTypeTokens->camera) {
+ return new HdCyclesCamera(sprimId);
+ }
+ if (typeId == HdPrimTypeTokens->material) {
+ return new HdCyclesMaterial(sprimId);
+ }
+ if (typeId == HdPrimTypeTokens->diskLight || typeId == HdPrimTypeTokens->distantLight ||
+ typeId == HdPrimTypeTokens->domeLight || typeId == HdPrimTypeTokens->rectLight ||
+ typeId == HdPrimTypeTokens->sphereLight) {
+ return new HdCyclesLight(sprimId, typeId);
+ }
+ if (typeId == HdPrimTypeTokens->extComputation) {
+ return new HdExtComputation(sprimId);
+ }
+
+ TF_CODING_ERROR("Unknown Sprim type %s", typeId.GetText());
+ return nullptr;
+}
+
+HdSprim *HdCyclesDelegate::CreateFallbackSprim(const TfToken &typeId)
+{
+ return CreateSprim(typeId, SdfPath::EmptyPath());
+}
+
+void HdCyclesDelegate::DestroySprim(HdSprim *sPrim)
+{
+ delete sPrim;
+}
+
+HdBprim *HdCyclesDelegate::CreateBprim(const TfToken &typeId, const SdfPath &bprimId)
+{
+ if (typeId == HdPrimTypeTokens->renderBuffer) {
+ return new HdCyclesRenderBuffer(bprimId);
+ }
+#ifdef WITH_OPENVDB
+ if (typeId == _tokens->openvdbAsset) {
+ return new HdCyclesField(bprimId, typeId);
+ }
+#endif
+
+ TF_RUNTIME_ERROR("Unknown Bprim type %s", typeId.GetText());
+ return nullptr;
+}
+
+HdBprim *HdCyclesDelegate::CreateFallbackBprim(const TfToken &typeId)
+{
+ return CreateBprim(typeId, SdfPath::EmptyPath());
+}
+
+void HdCyclesDelegate::DestroyBprim(HdBprim *bPrim)
+{
+ delete bPrim;
+}
+
+void HdCyclesDelegate::CommitResources(HdChangeTracker *tracker)
+{
+ TF_UNUSED(tracker);
+
+ const SceneLock lock(_renderParam.get());
+
+ _renderParam->UpdateScene();
+}
+
+TfToken HdCyclesDelegate::GetMaterialBindingPurpose() const
+{
+ return HdTokens->full;
+}
+
+#if HD_API_VERSION < 41
+TfToken HdCyclesDelegate::GetMaterialNetworkSelector() const
+{
+ return _tokens->cycles;
+}
+#else
+TfTokenVector HdCyclesDelegate::GetMaterialRenderContexts() const
+{
+ return {_tokens->cycles};
+}
+#endif
+
+VtDictionary HdCyclesDelegate::GetRenderStats() const
+{
+ const Stats &stats = _renderParam->session->stats;
+ const Progress &progress = _renderParam->session->progress;
+
+ double totalTime, renderTime;
+ progress.get_time(totalTime, renderTime);
+ double fractionDone = progress.get_progress();
+
+ std::string status, substatus;
+ progress.get_status(status, substatus);
+ if (!substatus.empty()) {
+ status += " | " + substatus;
+ }
+
+ return {{"rendererName", VtValue("Cycles")},
+ {"rendererVersion", VtValue(GfVec3i(0, 0, 0))},
+ {"percentDone", VtValue(floor_to_int(fractionDone * 100))},
+ {"fractionDone", VtValue(fractionDone)},
+ {"loadClockTime", VtValue(totalTime - renderTime)},
+ {"peakMemory", VtValue(stats.mem_peak)},
+ {"totalClockTime", VtValue(totalTime)},
+ {"totalMemory", VtValue(stats.mem_used)},
+ {"renderProgressAnnotation", VtValue(status)}};
+}
+
+HdAovDescriptor HdCyclesDelegate::GetDefaultAovDescriptor(const TfToken &name) const
+{
+ if (name == HdAovTokens->color) {
+ HdFormat colorFormat = HdFormatFloat32Vec4;
+ if (IsDisplaySupported()) {
+ // Can use Cycles 'DisplayDriver' in OpenGL, but it only supports 'half4' format
+ colorFormat = HdFormatFloat16Vec4;
+ }
+
+ return HdAovDescriptor(colorFormat, false, VtValue(GfVec4f(0.0f)));
+ }
+ if (name == HdAovTokens->depth) {
+ return HdAovDescriptor(HdFormatFloat32, false, VtValue(1.0f));
+ }
+ if (name == HdAovTokens->normal) {
+ return HdAovDescriptor(HdFormatFloat32Vec3, false, VtValue(GfVec3f(0.0f)));
+ }
+ if (name == HdAovTokens->primId || name == HdAovTokens->instanceId ||
+ name == HdAovTokens->elementId) {
+ return HdAovDescriptor(HdFormatInt32, false, VtValue(-1));
+ }
+
+ return HdAovDescriptor();
+}
+
+HdRenderSettingDescriptorList HdCyclesDelegate::GetRenderSettingDescriptors() const
+{
+ Scene *const scene = _renderParam->session->scene;
+
+ HdRenderSettingDescriptorList descriptors;
+
+ descriptors.push_back({
+ "Time Limit",
+ HdCyclesRenderSettingsTokens->time_limit,
+ VtValue(0.0),
+ });
+ descriptors.push_back({
+ "Sample Count",
+ HdCyclesRenderSettingsTokens->samples,
+ VtValue(1024),
+ });
+ descriptors.push_back({
+ "Sample Offset",
+ HdCyclesRenderSettingsTokens->sample_offset,
+ VtValue(0),
+ });
+
+ for (const SocketType &socket : scene->integrator->type->inputs) {
+ descriptors.push_back({socket.ui_name.string(),
+ TfToken("cycles:integrator:" + socket.name.string()),
+ GetNodeValue(scene->integrator, socket)});
+ }
+
+ return descriptors;
+}
+
+void HdCyclesDelegate::SetRenderSetting(const PXR_NS::TfToken &key, const PXR_NS::VtValue &value)
+{
+ Scene *const scene = _renderParam->session->scene;
+ Session *const session = _renderParam->session;
+
+ if (key == HdCyclesRenderSettingsTokens->time_limit) {
+ session->set_time_limit(
+ VtValue::Cast<double>(value).GetWithDefault(session->params.time_limit));
+ }
+ else if (key == HdCyclesRenderSettingsTokens->samples) {
+ int samples = VtValue::Cast<int>(value).GetWithDefault(session->params.samples);
+ samples = std::min(std::max(1, samples), Integrator::MAX_SAMPLES);
+ session->set_samples(samples);
+ }
+ else if (key == HdCyclesRenderSettingsTokens->sample_offset) {
+ session->params.sample_offset = VtValue::Cast<int>(value).GetWithDefault(
+ session->params.sample_offset);
+ ++_settingsVersion;
+ }
+ else {
+ const std::string &keyString = key.GetString();
+ if (keyString.rfind("cycles:integrator:", 0) == 0) {
+ ustring socketName(keyString, sizeof("cycles:integrator:") - 1);
+ if (const SocketType *socket = scene->integrator->type->find_input(socketName)) {
+ SetNodeValue(scene->integrator, *socket, value);
+ ++_settingsVersion;
+ }
+ }
+ }
+}
+
+VtValue HdCyclesDelegate::GetRenderSetting(const TfToken &key) const
+{
+ Scene *const scene = _renderParam->session->scene;
+ Session *const session = _renderParam->session;
+
+ if (key == HdCyclesRenderSettingsTokens->device) {
+ return VtValue(TfToken(Device::string_from_type(session->params.device.type)));
+ }
+ else if (key == HdCyclesRenderSettingsTokens->threads) {
+ return VtValue(session->params.threads);
+ }
+ else if (key == HdCyclesRenderSettingsTokens->time_limit) {
+ return VtValue(session->params.time_limit);
+ }
+ else if (key == HdCyclesRenderSettingsTokens->samples) {
+ return VtValue(session->params.samples);
+ }
+ else if (key == HdCyclesRenderSettingsTokens->sample_offset) {
+ return VtValue(session->params.sample_offset);
+ }
+ else {
+ const std::string &keyString = key.GetString();
+ if (keyString.rfind("cycles:integrator:", 0) == 0) {
+ ustring socketName(keyString, sizeof("cycles:integrator:") - 1);
+ if (const SocketType *socket = scene->integrator->type->find_input(socketName)) {
+ return GetNodeValue(scene->integrator, *socket);
+ }
+ }
+ }
+
+ return VtValue();
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_delegate.h b/intern/cycles/hydra/render_delegate.h
new file mode 100644
index 00000000000..9c15c8d5281
--- /dev/null
+++ b/intern/cycles/hydra/render_delegate.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/renderDelegate.h>
+#include <pxr/imaging/hgi/hgi.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesDelegate final : public PXR_NS::HdRenderDelegate {
+ public:
+ HdCyclesDelegate(const PXR_NS::HdRenderSettingsMap &settingsMap,
+ CCL_NS::Session *session_ = nullptr);
+ ~HdCyclesDelegate() override;
+
+ void SetDrivers(const PXR_NS::HdDriverVector &drivers) override;
+
+ bool IsDisplaySupported() const;
+
+ PXR_NS::Hgi *GetHgi() const
+ {
+ return _hgi;
+ }
+
+ const PXR_NS::TfTokenVector &GetSupportedRprimTypes() const override;
+ const PXR_NS::TfTokenVector &GetSupportedSprimTypes() const override;
+ const PXR_NS::TfTokenVector &GetSupportedBprimTypes() const override;
+
+ PXR_NS::HdRenderParam *GetRenderParam() const override;
+
+ PXR_NS::HdResourceRegistrySharedPtr GetResourceRegistry() const override;
+
+ PXR_NS::HdRenderSettingDescriptorList GetRenderSettingDescriptors() const override;
+
+ bool IsPauseSupported() const override;
+
+ bool Pause() override;
+ bool Resume() override;
+
+ PXR_NS::HdRenderPassSharedPtr CreateRenderPass(
+ PXR_NS::HdRenderIndex *index, const PXR_NS::HdRprimCollection &collection) override;
+
+ PXR_NS::HdInstancer *CreateInstancer(PXR_NS::HdSceneDelegate *delegate,
+ const PXR_NS::SdfPath &id
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId
+#endif
+ ) override;
+ void DestroyInstancer(PXR_NS::HdInstancer *instancer) override;
+
+ PXR_NS::HdRprim *CreateRprim(const PXR_NS::TfToken &typeId,
+ const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId
+#endif
+ ) override;
+ void DestroyRprim(PXR_NS::HdRprim *rPrim) override;
+
+ PXR_NS::HdSprim *CreateSprim(const PXR_NS::TfToken &typeId,
+ const PXR_NS::SdfPath &sprimId) override;
+ PXR_NS::HdSprim *CreateFallbackSprim(const PXR_NS::TfToken &typeId) override;
+ void DestroySprim(PXR_NS::HdSprim *sPrim) override;
+
+ PXR_NS::HdBprim *CreateBprim(const PXR_NS::TfToken &typeId,
+ const PXR_NS::SdfPath &bprimId) override;
+ PXR_NS::HdBprim *CreateFallbackBprim(const PXR_NS::TfToken &typeId) override;
+ void DestroyBprim(PXR_NS::HdBprim *bPrim) override;
+
+ void CommitResources(PXR_NS::HdChangeTracker *tracker) override;
+
+ PXR_NS::TfToken GetMaterialBindingPurpose() const override;
+
+#if HD_API_VERSION < 41
+ PXR_NS::TfToken GetMaterialNetworkSelector() const override;
+#else
+ PXR_NS::TfTokenVector GetMaterialRenderContexts() const override;
+#endif
+
+ PXR_NS::VtDictionary GetRenderStats() const override;
+
+ PXR_NS::HdAovDescriptor GetDefaultAovDescriptor(const PXR_NS::TfToken &name) const override;
+
+ void SetRenderSetting(const PXR_NS::TfToken &key, const PXR_NS::VtValue &value) override;
+
+ PXR_NS::VtValue GetRenderSetting(const PXR_NS::TfToken &key) const override;
+
+ private:
+ PXR_NS::Hgi *_hgi = nullptr;
+ std::unique_ptr<HdCyclesSession> _renderParam;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_pass.cpp b/intern/cycles/hydra/render_pass.cpp
new file mode 100644
index 00000000000..9d47dfc5c8d
--- /dev/null
+++ b/intern/cycles/hydra/render_pass.cpp
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/render_pass.h"
+#include "hydra/camera.h"
+#include "hydra/display_driver.h"
+#include "hydra/output_driver.h"
+#include "hydra/render_buffer.h"
+#include "hydra/render_delegate.h"
+#include "hydra/session.h"
+#include "scene/camera.h"
+#include "scene/integrator.h"
+#include "scene/scene.h"
+#include "session/session.h"
+
+#include <pxr/imaging/hd/renderPassState.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+HdCyclesRenderPass::HdCyclesRenderPass(HdRenderIndex *index,
+ HdRprimCollection const &collection,
+ HdCyclesSession *renderParam)
+ : HdRenderPass(index, collection), _renderParam(renderParam)
+{
+ Session *const session = _renderParam->session;
+ // Reset cancel state so session thread can continue rendering
+ session->progress.reset();
+
+ session->set_output_driver(make_unique<HdCyclesOutputDriver>(renderParam));
+
+ const auto renderDelegate = static_cast<const HdCyclesDelegate *>(
+ GetRenderIndex()->GetRenderDelegate());
+ if (renderDelegate->IsDisplaySupported()) {
+ session->set_display_driver(
+ make_unique<HdCyclesDisplayDriver>(renderParam, renderDelegate->GetHgi()));
+ }
+}
+
+HdCyclesRenderPass::~HdCyclesRenderPass()
+{
+ Session *const session = _renderParam->session;
+ session->cancel(true);
+}
+
+bool HdCyclesRenderPass::IsConverged() const
+{
+ for (const HdRenderPassAovBinding &aovBinding : _renderParam->GetAovBindings()) {
+ if (aovBinding.renderBuffer && !aovBinding.renderBuffer->IsConverged()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void HdCyclesRenderPass::ResetConverged()
+{
+ for (const HdRenderPassAovBinding &aovBinding : _renderParam->GetAovBindings()) {
+ if (const auto renderBuffer = static_cast<HdCyclesRenderBuffer *>(aovBinding.renderBuffer)) {
+ renderBuffer->SetConverged(false);
+ }
+ }
+}
+
+void HdCyclesRenderPass::_Execute(const HdRenderPassStateSharedPtr &renderPassState,
+ const TfTokenVector &renderTags)
+{
+ Scene *const scene = _renderParam->session->scene;
+ Session *const session = _renderParam->session;
+
+ if (session->progress.get_cancel()) {
+ return; // Something went wrong and cannot continue without recreating the session
+ }
+
+ if (scene->mutex.try_lock()) {
+ const auto renderDelegate = static_cast<HdCyclesDelegate *>(
+ GetRenderIndex()->GetRenderDelegate());
+
+ const unsigned int settingsVersion = renderDelegate->GetRenderSettingsVersion();
+
+ // Update requested AOV bindings
+ const HdRenderPassAovBindingVector &aovBindings = renderPassState->GetAovBindings();
+ if (_renderParam->GetAovBindings() != aovBindings ||
+ // Need to resync passes when denoising is enabled or disabled to update the pass mode
+ (settingsVersion != _lastSettingsVersion &&
+ scene->integrator->use_denoise_is_modified())) {
+ _renderParam->SyncAovBindings(aovBindings);
+
+ if (renderDelegate->IsDisplaySupported()) {
+ // Update display pass to the first requested color AOV
+ HdRenderPassAovBinding displayAovBinding = !aovBindings.empty() ? aovBindings.front() :
+ HdRenderPassAovBinding();
+ if (displayAovBinding.aovName == HdAovTokens->color && displayAovBinding.renderBuffer) {
+ _renderParam->SetDisplayAovBinding(displayAovBinding);
+ }
+ else {
+ _renderParam->SetDisplayAovBinding(HdRenderPassAovBinding());
+ }
+ }
+ }
+
+ // Update camera dimensions to the viewport size
+#if PXR_VERSION >= 2102
+ CameraUtilFraming framing = renderPassState->GetFraming();
+ if (!framing.IsValid()) {
+ const GfVec4f vp = renderPassState->GetViewport();
+ framing = CameraUtilFraming(GfRect2i(GfVec2i(0), int(vp[2]), int(vp[3])));
+ }
+
+ scene->camera->set_full_width(framing.dataWindow.GetWidth());
+ scene->camera->set_full_height(framing.dataWindow.GetHeight());
+#else
+ const GfVec4f vp = renderPassState->GetViewport();
+ scene->camera->set_full_width(int(vp[2]));
+ scene->camera->set_full_height(int(vp[3]));
+#endif
+
+ if (const auto camera = static_cast<const HdCyclesCamera *>(renderPassState->GetCamera())) {
+ camera->ApplyCameraSettings(scene->camera);
+ }
+ else {
+ HdCyclesCamera::ApplyCameraSettings(renderPassState->GetWorldToViewMatrix(),
+ renderPassState->GetProjectionMatrix(),
+ renderPassState->GetClipPlanes(),
+ scene->camera);
+ }
+
+ // Reset session if the session, scene, camera or AOV bindings changed
+ if (scene->need_reset() || settingsVersion != _lastSettingsVersion) {
+ _lastSettingsVersion = settingsVersion;
+
+ // Reset convergence state of all render buffers
+ ResetConverged();
+
+ BufferParams buffer_params;
+#if PXR_VERSION >= 2102
+ buffer_params.full_x = static_cast<int>(framing.displayWindow.GetMin()[0]);
+ buffer_params.full_y = static_cast<int>(framing.displayWindow.GetMin()[1]);
+ buffer_params.full_width = static_cast<int>(framing.displayWindow.GetSize()[0]);
+ buffer_params.full_height = static_cast<int>(framing.displayWindow.GetSize()[1]);
+
+ buffer_params.window_x = framing.dataWindow.GetMinX() - buffer_params.full_x;
+ buffer_params.window_y = framing.dataWindow.GetMinY() - buffer_params.full_y;
+ buffer_params.window_width = framing.dataWindow.GetWidth();
+ buffer_params.window_height = framing.dataWindow.GetHeight();
+
+ buffer_params.width = buffer_params.window_width;
+ buffer_params.height = buffer_params.window_height;
+#else
+ buffer_params.width = static_cast<int>(vp[2]);
+ buffer_params.height = static_cast<int>(vp[3]);
+ buffer_params.full_width = buffer_params.width;
+ buffer_params.full_height = buffer_params.height;
+ buffer_params.window_width = buffer_params.width;
+ buffer_params.window_height = buffer_params.height;
+#endif
+
+ session->reset(session->params, buffer_params);
+ }
+
+ scene->mutex.unlock();
+
+ // Start Cycles render thread if not already running
+ session->start();
+ }
+
+ session->draw();
+}
+
+void HdCyclesRenderPass::_MarkCollectionDirty()
+{
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/render_pass.h b/intern/cycles/hydra/render_pass.h
new file mode 100644
index 00000000000..f04c97097a6
--- /dev/null
+++ b/intern/cycles/hydra/render_pass.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include <pxr/imaging/hd/renderPass.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesRenderPass final : public PXR_NS::HdRenderPass {
+ public:
+ HdCyclesRenderPass(PXR_NS::HdRenderIndex *index,
+ const PXR_NS::HdRprimCollection &collection,
+ HdCyclesSession *renderParam);
+ ~HdCyclesRenderPass() override;
+
+ bool IsConverged() const override;
+
+ private:
+ void ResetConverged();
+
+ void _Execute(const PXR_NS::HdRenderPassStateSharedPtr &renderPassState,
+ const PXR_NS::TfTokenVector &renderTags) override;
+
+ void _MarkCollectionDirty() override;
+
+ HdCyclesSession *_renderParam;
+ unsigned int _lastSettingsVersion = 0;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/resources/plugInfo.json b/intern/cycles/hydra/resources/plugInfo.json
new file mode 100644
index 00000000000..94fe778ea44
--- /dev/null
+++ b/intern/cycles/hydra/resources/plugInfo.json
@@ -0,0 +1,22 @@
+{
+ "Plugins": [
+ {
+ "Info": {
+ "Types": {
+ "HdCyclesPlugin": {
+ "bases": [
+ "HdRendererPlugin"
+ ],
+ "displayName": "Cycles",
+ "priority": 0
+ }
+ }
+ },
+ "LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
+ "Name": "hdCycles",
+ "ResourcePath": "@PLUG_INFO_RESOURCE_PATH@",
+ "Root": "@PLUG_INFO_ROOT@",
+ "Type": "library"
+ }
+ ]
+}
diff --git a/intern/cycles/hydra/session.cpp b/intern/cycles/hydra/session.cpp
new file mode 100644
index 00000000000..f6865bdedd1
--- /dev/null
+++ b/intern/cycles/hydra/session.cpp
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/session.h"
+#include "scene/shader.h"
+// Have to include shader.h before background.h so that 'set_shader' uses the correct 'set'
+// overload taking a 'Node *', rather than the one taking a 'bool'
+#include "scene/background.h"
+#include "scene/light.h"
+#include "scene/shader_graph.h"
+#include "scene/shader_nodes.h"
+#include "session/session.h"
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+namespace {
+
+const std::unordered_map<TfToken, PassType, TfToken::HashFunctor> kAovToPass = {
+ {HdAovTokens->color, PASS_COMBINED},
+ {HdAovTokens->depth, PASS_DEPTH},
+ {HdAovTokens->normal, PASS_NORMAL},
+ {HdAovTokens->primId, PASS_OBJECT_ID},
+ {HdAovTokens->instanceId, PASS_AOV_VALUE},
+};
+
+} // namespace
+
+SceneLock::SceneLock(const HdRenderParam *renderParam)
+ : scene(static_cast<const HdCyclesSession *>(renderParam)->session->scene),
+ sceneLock(scene->mutex)
+{
+}
+
+SceneLock::~SceneLock()
+{
+}
+
+HdCyclesSession::HdCyclesSession(Session *session_) : session(session_), _ownCyclesSession(false)
+{
+}
+
+HdCyclesSession::HdCyclesSession(const SessionParams &params)
+ : session(new Session(params, SceneParams())), _ownCyclesSession(true)
+{
+ Scene *const scene = session->scene;
+
+ // Create background with ambient light
+ {
+ ShaderGraph *graph = new ShaderGraph();
+
+ BackgroundNode *bgNode = graph->create_node<BackgroundNode>();
+ bgNode->set_color(one_float3());
+ graph->add(bgNode);
+
+ graph->connect(bgNode->output("Background"), graph->output()->input("Surface"));
+
+ scene->default_background->set_graph(graph);
+ scene->default_background->tag_update(scene);
+ }
+
+ // Wire up object color in default surface material
+ {
+ ShaderGraph *graph = new ShaderGraph();
+
+ ObjectInfoNode *objectNode = graph->create_node<ObjectInfoNode>();
+ graph->add(objectNode);
+
+ DiffuseBsdfNode *diffuseNode = graph->create_node<DiffuseBsdfNode>();
+ graph->add(diffuseNode);
+
+ graph->connect(objectNode->output("Color"), diffuseNode->input("Color"));
+ graph->connect(diffuseNode->output("BSDF"), graph->output()->input("Surface"));
+
+#if 1
+ // Create the instanceId AOV output
+ const ustring instanceId(HdAovTokens->instanceId.GetString());
+
+ OutputAOVNode *aovNode = graph->create_node<OutputAOVNode>();
+ aovNode->set_name(instanceId);
+ graph->add(aovNode);
+
+ AttributeNode *instanceIdNode = graph->create_node<AttributeNode>();
+ instanceIdNode->set_attribute(instanceId);
+ graph->add(instanceIdNode);
+
+ graph->connect(instanceIdNode->output("Fac"), aovNode->input("Value"));
+#endif
+
+ scene->default_surface->set_graph(graph);
+ scene->default_surface->tag_update(scene);
+ }
+}
+
+HdCyclesSession::~HdCyclesSession()
+{
+ if (_ownCyclesSession) {
+ delete session;
+ }
+}
+
+void HdCyclesSession::UpdateScene()
+{
+ Scene *const scene = session->scene;
+
+ // Update background depending on presence of a background light
+ if (scene->light_manager->need_update()) {
+ Light *background_light = nullptr;
+ for (Light *light : scene->lights) {
+ if (light->get_light_type() == LIGHT_BACKGROUND) {
+ background_light = light;
+ break;
+ }
+ }
+
+ if (!background_light) {
+ scene->background->set_shader(scene->default_background);
+ scene->background->set_transparent(true);
+ }
+ else {
+ scene->background->set_shader(background_light->get_shader());
+ scene->background->set_transparent(false);
+ }
+
+ scene->background->tag_update(scene);
+ }
+}
+
+void HdCyclesSession::SyncAovBindings(const HdRenderPassAovBindingVector &aovBindings)
+{
+ Scene *const scene = session->scene;
+
+ // Delete all existing passes
+ scene->delete_nodes(set<Pass *>(scene->passes.begin(), scene->passes.end()));
+
+ // Update passes with requested AOV bindings
+ _aovBindings = aovBindings;
+ for (const HdRenderPassAovBinding &aovBinding : aovBindings) {
+ const auto cyclesAov = kAovToPass.find(aovBinding.aovName);
+ if (cyclesAov == kAovToPass.end()) {
+ // TODO: Use PASS_AOV_COLOR and PASS_AOV_VALUE for these?
+ TF_WARN("Unknown pass %s", aovBinding.aovName.GetText());
+ continue;
+ }
+
+ const PassType type = cyclesAov->second;
+ const PassMode mode = PassMode::DENOISED;
+
+ Pass *pass = scene->create_node<Pass>();
+ pass->set_type(type);
+ pass->set_mode(mode);
+ pass->set_name(ustring(aovBinding.aovName.GetString()));
+ }
+}
+
+void HdCyclesSession::RemoveAovBinding(HdRenderBuffer *renderBuffer)
+{
+ for (HdRenderPassAovBinding &aovBinding : _aovBindings) {
+ if (renderBuffer == aovBinding.renderBuffer) {
+ aovBinding.renderBuffer = nullptr;
+ break;
+ }
+ }
+
+ if (renderBuffer == _displayAovBinding.renderBuffer) {
+ _displayAovBinding.renderBuffer = nullptr;
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/session.h b/intern/cycles/hydra/session.h
new file mode 100644
index 00000000000..7e649c1847a
--- /dev/null
+++ b/intern/cycles/hydra/session.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "util/thread.h"
+
+#include <pxr/imaging/hd/renderDelegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+struct SceneLock {
+ SceneLock(const PXR_NS::HdRenderParam *renderParam);
+ ~SceneLock();
+
+ CCL_NS::Scene *scene;
+
+ private:
+ CCL_NS::thread_scoped_lock sceneLock;
+};
+
+class HdCyclesSession final : public PXR_NS::HdRenderParam {
+ public:
+ HdCyclesSession(CCL_NS::Session *session_);
+ HdCyclesSession(const CCL_NS::SessionParams &params);
+ ~HdCyclesSession() override;
+
+ void UpdateScene();
+
+ PXR_NS::HdRenderPassAovBinding GetDisplayAovBinding() const
+ {
+ return _displayAovBinding;
+ }
+
+ void SetDisplayAovBinding(const PXR_NS::HdRenderPassAovBinding &aovBinding)
+ {
+ _displayAovBinding = aovBinding;
+ }
+
+ const PXR_NS::HdRenderPassAovBindingVector &GetAovBindings() const
+ {
+ return _aovBindings;
+ }
+
+ void SyncAovBindings(const PXR_NS::HdRenderPassAovBindingVector &aovBindings);
+
+ void RemoveAovBinding(PXR_NS::HdRenderBuffer *renderBuffer);
+
+ CCL_NS::Session *session;
+
+ private:
+ const bool _ownCyclesSession;
+ PXR_NS::HdRenderPassAovBindingVector _aovBindings;
+ PXR_NS::HdRenderPassAovBinding _displayAovBinding;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/volume.cpp b/intern/cycles/hydra/volume.cpp
new file mode 100644
index 00000000000..7b965c613ed
--- /dev/null
+++ b/intern/cycles/hydra/volume.cpp
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#include "hydra/volume.h"
+#include "hydra/field.h"
+#include "hydra/geometry.inl"
+#include "scene/volume.h"
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+// clang-format off
+TF_DEFINE_PRIVATE_TOKENS(_tokens,
+ (openvdbAsset)
+);
+// clang-format on
+
+HdCyclesVolume::HdCyclesVolume(const SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const SdfPath &instancerId
+#endif
+ )
+ : HdCyclesGeometry(rprimId
+#if PXR_VERSION < 2102
+ ,
+ instancerId
+#endif
+ )
+{
+}
+
+HdCyclesVolume::~HdCyclesVolume()
+{
+}
+
+HdDirtyBits HdCyclesVolume::GetInitialDirtyBitsMask() const
+{
+ HdDirtyBits bits = HdCyclesGeometry::GetInitialDirtyBitsMask();
+ bits |= HdChangeTracker::DirtyVolumeField;
+ return bits;
+}
+
+void HdCyclesVolume::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
+{
+ Scene *const scene = (Scene *)_geom->get_owner();
+
+ if (dirtyBits & HdChangeTracker::DirtyVolumeField) {
+ for (const HdVolumeFieldDescriptor &field :
+ sceneDelegate->GetVolumeFieldDescriptors(GetId())) {
+ if (const auto openvdbAsset = static_cast<HdCyclesField *>(
+ sceneDelegate->GetRenderIndex().GetBprim(_tokens->openvdbAsset, field.fieldId))) {
+ const ustring name(field.fieldName.GetString());
+
+ AttributeStandard std = ATTR_STD_NONE;
+ if (name == Attribute::standard_name(ATTR_STD_VOLUME_DENSITY)) {
+ std = ATTR_STD_VOLUME_DENSITY;
+ }
+ else if (name == Attribute::standard_name(ATTR_STD_VOLUME_COLOR)) {
+ std = ATTR_STD_VOLUME_COLOR;
+ }
+ else if (name == Attribute::standard_name(ATTR_STD_VOLUME_FLAME)) {
+ std = ATTR_STD_VOLUME_FLAME;
+ }
+ else if (name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT)) {
+ std = ATTR_STD_VOLUME_HEAT;
+ }
+ else if (name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE)) {
+ std = ATTR_STD_VOLUME_TEMPERATURE;
+ }
+ else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY)) {
+ std = ATTR_STD_VOLUME_VELOCITY;
+ }
+
+ // Skip attributes that are not needed
+ if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
+ _geom->need_attribute(scene, name)) {
+ Attribute *const attr = (std != ATTR_STD_NONE) ?
+ _geom->attributes.add(std) :
+ _geom->attributes.add(
+ name, TypeDesc::TypeFloat, ATTR_ELEMENT_VOXEL);
+ attr->data_voxel() = openvdbAsset->GetImageHandle();
+ }
+ }
+ }
+
+ rebuild = true;
+ }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/volume.h b/intern/cycles/hydra/volume.h
new file mode 100644
index 00000000000..775a7cf069e
--- /dev/null
+++ b/intern/cycles/hydra/volume.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2022 NVIDIA Corporation
+ * Copyright 2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+#include "hydra/geometry.h"
+
+#include <pxr/imaging/hd/volume.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesVolume final : public HdCyclesGeometry<PXR_NS::HdVolume, CCL_NS::Volume> {
+ public:
+ HdCyclesVolume(const PXR_NS::SdfPath &rprimId
+#if PXR_VERSION < 2102
+ ,
+ const PXR_NS::SdfPath &instancerId = {}
+#endif
+ );
+ ~HdCyclesVolume() override;
+
+ PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override;
+
+ private:
+ void Populate(PXR_NS::HdSceneDelegate *sceneDelegate,
+ PXR_NS::HdDirtyBits dirtyBits,
+ bool &rebuild) override;
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp
index 90a5a01320b..a75662c90d8 100644
--- a/intern/cycles/integrator/render_scheduler.cpp
+++ b/intern/cycles/integrator/render_scheduler.cpp
@@ -20,7 +20,7 @@ RenderScheduler::RenderScheduler(TileManager &tile_manager, const SessionParams
background_(params.background),
pixel_size_(params.pixel_size),
tile_manager_(tile_manager),
- default_start_resolution_divider_(pixel_size_ * 8)
+ default_start_resolution_divider_(params.use_resolution_divider ? pixel_size_ * 8 : 0)
{
use_progressive_noise_floor_ = !background_;
}
@@ -119,7 +119,7 @@ void RenderScheduler::reset(const BufferParams &buffer_params, int num_samples,
/* In background mode never do lower resolution render preview, as it is not really supported
* by the software. */
- if (background_) {
+ if (background_ || start_resolution_divider_ == 0) {
state_.resolution_divider = 1;
}
else {
@@ -1050,6 +1050,10 @@ bool RenderScheduler::work_need_rebalance()
void RenderScheduler::update_start_resolution_divider()
{
+ if (default_start_resolution_divider_ == 0) {
+ return;
+ }
+
if (start_resolution_divider_ == 0) {
/* Resolution divider has never been calculated before: use default resolution, so that we have
* somewhat good initial behavior, giving a chance to collect real numbers. */
diff --git a/intern/cycles/kernel/svm/vertex_color.h b/intern/cycles/kernel/svm/vertex_color.h
index 231c388618c..bf8d5677114 100644
--- a/intern/cycles/kernel/svm/vertex_color.h
+++ b/intern/cycles/kernel/svm/vertex_color.h
@@ -14,9 +14,16 @@ ccl_device_noinline void svm_node_vertex_color(KernelGlobals kg,
{
AttributeDescriptor descriptor = find_attribute(kg, sd, layer_id);
if (descriptor.offset != ATTR_STD_NOT_FOUND) {
- float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, NULL, NULL);
- stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
- stack_store_float(stack, alpha_offset, vertex_color.w);
+ if (descriptor.type == NODE_ATTR_FLOAT4 || descriptor.type == NODE_ATTR_RGBA) {
+ float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, NULL, NULL);
+ stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
+ stack_store_float(stack, alpha_offset, vertex_color.w);
+ }
+ else {
+ float3 vertex_color = primitive_surface_attribute_float3(kg, sd, descriptor, NULL, NULL);
+ stack_store_float3(stack, color_offset, vertex_color);
+ stack_store_float(stack, alpha_offset, 1.0f);
+ }
}
else {
stack_store_float3(stack, color_offset, make_float3(0.0f, 0.0f, 0.0f));
@@ -33,11 +40,20 @@ ccl_device_noinline void svm_node_vertex_color_bump_dx(KernelGlobals kg,
{
AttributeDescriptor descriptor = find_attribute(kg, sd, layer_id);
if (descriptor.offset != ATTR_STD_NOT_FOUND) {
- float4 dx;
- float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, &dx, NULL);
- vertex_color += dx;
- stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
- stack_store_float(stack, alpha_offset, vertex_color.w);
+ if (descriptor.type == NODE_ATTR_FLOAT4 || descriptor.type == NODE_ATTR_RGBA) {
+ float4 dx;
+ float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, &dx, NULL);
+ vertex_color += dx;
+ stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
+ stack_store_float(stack, alpha_offset, vertex_color.w);
+ }
+ else {
+ float3 dx;
+ float3 vertex_color = primitive_surface_attribute_float3(kg, sd, descriptor, &dx, NULL);
+ vertex_color += dx;
+ stack_store_float3(stack, color_offset, vertex_color);
+ stack_store_float(stack, alpha_offset, 1.0f);
+ }
}
else {
stack_store_float3(stack, color_offset, make_float3(0.0f, 0.0f, 0.0f));
@@ -54,11 +70,20 @@ ccl_device_noinline void svm_node_vertex_color_bump_dy(KernelGlobals kg,
{
AttributeDescriptor descriptor = find_attribute(kg, sd, layer_id);
if (descriptor.offset != ATTR_STD_NOT_FOUND) {
- float4 dy;
- float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, NULL, &dy);
- vertex_color += dy;
- stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
- stack_store_float(stack, alpha_offset, vertex_color.w);
+ if (descriptor.type == NODE_ATTR_FLOAT4 || descriptor.type == NODE_ATTR_RGBA) {
+ float4 dy;
+ float4 vertex_color = primitive_surface_attribute_float4(kg, sd, descriptor, NULL, &dy);
+ vertex_color += dy;
+ stack_store_float3(stack, color_offset, float4_to_float3(vertex_color));
+ stack_store_float(stack, alpha_offset, vertex_color.w);
+ }
+ else {
+ float3 dy;
+ float3 vertex_color = primitive_surface_attribute_float3(kg, sd, descriptor, NULL, &dy);
+ vertex_color += dy;
+ stack_store_float3(stack, color_offset, vertex_color);
+ stack_store_float(stack, alpha_offset, 1.0f);
+ }
}
else {
stack_store_float3(stack, color_offset, make_float3(0.0f, 0.0f, 0.0f));
diff --git a/intern/cycles/scene/integrator.cpp b/intern/cycles/scene/integrator.cpp
index 64c95538f82..755fbb9542b 100644
--- a/intern/cycles/scene/integrator.cpp
+++ b/intern/cycles/scene/integrator.cpp
@@ -110,7 +110,7 @@ NODE_DEFINE(Integrator)
SOCKET_BOOLEAN(use_denoise_pass_albedo, "Use Albedo Pass for Denoiser", true);
SOCKET_BOOLEAN(use_denoise_pass_normal, "Use Normal Pass for Denoiser", true);
SOCKET_ENUM(
- denoiser_prefilter, "Denoiser Type", denoiser_prefilter_enum, DENOISER_PREFILTER_ACCURATE);
+ denoiser_prefilter, "Denoiser Prefilter", denoiser_prefilter_enum, DENOISER_PREFILTER_ACCURATE);
return type;
}
diff --git a/intern/cycles/scene/mesh.cpp b/intern/cycles/scene/mesh.cpp
index a459195efee..05024a7790e 100644
--- a/intern/cycles/scene/mesh.cpp
+++ b/intern/cycles/scene/mesh.cpp
@@ -142,8 +142,8 @@ NODE_DEFINE(Mesh)
SOCKET_INT(num_ngons, "NGons Number", 0);
/* Subdivisions parameters */
- SOCKET_FLOAT(subd_dicing_rate, "Subdivision Dicing Rate", 0.0f)
- SOCKET_INT(subd_max_level, "Subdivision Dicing Rate", 0);
+ SOCKET_FLOAT(subd_dicing_rate, "Subdivision Dicing Rate", 1.0f)
+ SOCKET_INT(subd_max_level, "Max Subdivision Level", 1);
SOCKET_TRANSFORM(subd_objecttoworld, "Subdivision Object Transform", transform_identity());
return type;
@@ -357,7 +357,7 @@ void Mesh::add_triangle(int v0, int v1, int v2, int shader_, bool smooth_)
}
}
-void Mesh::add_subd_face(int *corners, int num_corners, int shader_, bool smooth_)
+void Mesh::add_subd_face(const int *corners, int num_corners, int shader_, bool smooth_)
{
int start_corner = subd_face_corners.size();
@@ -411,8 +411,6 @@ void Mesh::add_edge_crease(int v0, int v1, float weight)
void Mesh::add_vertex_crease(int v, float weight)
{
- assert(v < verts.size());
-
subd_vert_creases.push_back_slow(v);
subd_vert_creases_weight.push_back_slow(weight);
diff --git a/intern/cycles/scene/mesh.h b/intern/cycles/scene/mesh.h
index 4e6991a3960..a09f192d969 100644
--- a/intern/cycles/scene/mesh.h
+++ b/intern/cycles/scene/mesh.h
@@ -199,7 +199,7 @@ class Mesh : public Geometry {
void add_vertex(float3 P);
void add_vertex_slow(float3 P);
void add_triangle(int v0, int v1, int v2, int shader, bool smooth);
- void add_subd_face(int *corners, int num_corners, int shader_, bool smooth_);
+ void add_subd_face(const int *corners, int num_corners, int shader_, bool smooth_);
void add_edge_crease(int v0, int v1, float weight);
void add_vertex_crease(int v, float weight);
diff --git a/intern/cycles/session/session.h b/intern/cycles/session/session.h
index 3e90b41e3e9..d431c61a5fc 100644
--- a/intern/cycles/session/session.h
+++ b/intern/cycles/session/session.h
@@ -54,6 +54,8 @@ class SessionParams {
bool use_auto_tile;
int tile_size;
+ bool use_resolution_divider;
+
ShadingSystem shadingsystem;
/* Session-specific temporary directory to store in-progress EXR files in. */
@@ -76,6 +78,8 @@ class SessionParams {
use_auto_tile = true;
tile_size = 2048;
+ use_resolution_divider = true;
+
shadingsystem = SHADINGSYSTEM_SVM;
}
diff --git a/intern/cycles/util/tbb.h b/intern/cycles/util/tbb.h
index 2c26c3a5170..7105ddda0f8 100644
--- a/intern/cycles/util/tbb.h
+++ b/intern/cycles/util/tbb.h
@@ -16,6 +16,7 @@
#if TBB_INTERFACE_VERSION_MAJOR >= 10
# define WITH_TBB_GLOBAL_CONTROL
+# define TBB_PREVIEW_GLOBAL_CONTROL 1
# include <tbb/global_control.h>
#endif