diff options
Diffstat (limited to 'intern/cycles/hydra')
42 files changed, 6036 insertions, 0 deletions
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 ¶ms, + 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 ¶ms) +{ + 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 ¶ms, 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 ¶ms) 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> ¶meters, + const SdfPath &nodePath) +{ + for (const std::pair<TfToken, VtValue> ¶m : 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> ¶meters, + 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 ¶ms) + : 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 ¶ms); + ~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 |