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:
-rw-r--r--CMakeLists.txt8
-rw-r--r--build_files/cmake/Modules/FindUSD.cmake2
-rw-r--r--build_files/cmake/macros.cmake2
-rw-r--r--build_files/cmake/platform/platform_win32.cmake74
-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, 6155 insertions, 65 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d31a0c4a63d..bf40347e2ef 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)
@@ -742,9 +744,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()
@@ -1907,14 +1910,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 a5370fe24b3..75b5df9e196 100644
--- a/build_files/cmake/Modules/FindUSD.cmake
+++ b/build_files/cmake/Modules/FindUSD.cmake
@@ -36,7 +36,7 @@ FIND_PATH(USD_INCLUDE_DIR
# See https://github.com/PixarAnimationStudios/USD/blob/release/CHANGELOG.md#2111---2021-11-01
FIND_LIBRARY(USD_LIBRARY
NAMES
- usd_usd_m usd_usd_ms usd_m usd_ms
+ usd_usd_m usd_usd_ms usd_m usd_ms ${USD_LIBRARY_PREFIX}usd
NAMES_PER_DIR
HINTS
${_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..ca4af2274e6 100644
--- a/build_files/cmake/platform/platform_win32.cmake
+++ b/build_files/cmake/platform/platform_win32.cmake
@@ -39,12 +39,12 @@ 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()
-if(NOT WITH_PYTHON_MODULE)
+if(WITH_BLENDER AND NOT WITH_PYTHON_MODULE)
set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT blender)
endif()
@@ -238,7 +238,6 @@ else()
endif()
if(NOT DEFINED LIBDIR)
-
# Setup 64bit and 64bit windows systems
if(CMAKE_CL_64)
message(STATUS "64 bit compiler detected.")
@@ -252,6 +251,9 @@ 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)
endif()
else()
message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}")
@@ -300,9 +302,8 @@ set(ZLIB_INCLUDE_DIR ${LIBDIR}/zlib/include)
set(ZLIB_LIBRARY ${LIBDIR}/zlib/lib/libz_st.lib)
set(ZLIB_DIR ${LIBDIR}/zlib)
-windows_find_package(zlib) # we want to find before finding things that depend on it like png
-windows_find_package(png)
-
+windows_find_package(ZLIB) # we want to find before finding things that depend on it like png
+windows_find_package(PNG)
if(NOT PNG_FOUND)
warn_hardcoded_paths(libpng)
set(PNG_PNG_INCLUDE_DIR ${LIBDIR}/png/include)
@@ -313,9 +314,9 @@ if(NOT PNG_FOUND)
endif()
set(JPEG_NAMES ${JPEG_NAMES} libjpeg)
-windows_find_package(jpeg REQUIRED)
+windows_find_package(JPEG REQUIRED)
if(NOT JPEG_FOUND)
- warn_hardcoded_paths(jpeg)
+ warn_hardcoded_paths(libjpeg)
set(JPEG_INCLUDE_DIR ${LIBDIR}/jpeg/include)
set(JPEG_LIBRARIES ${LIBDIR}/jpeg/lib/libjpeg.lib)
endif()
@@ -333,7 +334,7 @@ set(FREETYPE_LIBRARIES
${LIBDIR}/brotli/lib/brotlidec-static.lib
${LIBDIR}/brotli/lib/brotlicommon-static.lib
)
-windows_find_package(freetype REQUIRED)
+windows_find_package(Freetype REQUIRED)
if(WITH_FFTW3)
set(FFTW3 ${LIBDIR}/fftw3)
@@ -389,9 +390,9 @@ if(WITH_CODEC_FFMPEG)
${LIBDIR}/ffmpeg/include
${LIBDIR}/ffmpeg/include/msvc
)
- windows_find_package(FFMPEG)
+ windows_find_package(FFmpeg)
if(NOT FFMPEG_FOUND)
- warn_hardcoded_paths(ffmpeg)
+ warn_hardcoded_paths(FFmpeg)
set(FFMPEG_LIBRARIES
${LIBDIR}/ffmpeg/lib/avcodec.lib
${LIBDIR}/ffmpeg/lib/avformat.lib
@@ -403,10 +404,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)
+ 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)
@@ -624,21 +625,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 +662,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()
diff --git a/intern/cycles/CMakeLists.txt b/intern/cycles/CMakeLists.txt
index ad2b5ab4d7b..1cc3dccf426 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(WITH_CYCLES_STANDALONE 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 WITH_USD)
+ 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..4ada4250780
--- /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..8b92ddb6f1f
--- /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;
+
+ _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..14cd9468761
--- /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 _handle;
+ }
+
+ private:
+ CCL_NS::ImageHandle _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