From cea4442546df3a2538d8bbb641b9602734e6ffd0 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 11 May 2026 17:39:58 +0200 Subject: [PATCH 01/15] Accept int64 VtValue for TYPE_INT with checked narrowing Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/ValueConverter.cc | 63 +++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/lib/hydramoonray/ValueConverter.cc b/lib/hydramoonray/ValueConverter.cc index 135069f..03357e9 100644 --- a/lib/hydramoonray/ValueConverter.cc +++ b/lib/hydramoonray/ValueConverter.cc @@ -5,6 +5,9 @@ #include +#include +#include + #include namespace hdMoonray { @@ -17,6 +20,39 @@ static void _clearBinding(SceneObject* sceneObj, const Attribute* attribute) sceneObj->setBinding(*attribute,nullptr); } +static bool _extractIntegral64(const pxr::VtValue& val, std::int64_t* out) +{ + if (val.IsHolding()) { + *out = static_cast(val.UncheckedGet()); + return true; + } + if (val.IsHolding()) { + *out = static_cast(val.UncheckedGet()); + return true; + } + if (val.IsHolding()) { + *out = static_cast(val.UncheckedGet()); + return true; + } + return false; +} + +static bool _narrowIntChecked(SceneObject* sceneObj, + const Attribute* attribute, + std::int64_t value, + Int* out) +{ + constexpr std::int64_t kMin = static_cast(std::numeric_limits::min()); + constexpr std::int64_t kMax = static_cast(std::numeric_limits::max()); + if (value < kMin || value > kMax) { + Logger::error(sceneObj->getName(), '.', attribute->getName(), + ": integer value ", value, " out of Int range [", kMin, ", ", kMax, "]"); + return false; + } + *out = static_cast(value); + return true; +} + template static void _set(SceneObject* sceneObj, const Attribute* attribute, const T& value) { sceneObj->set(AttributeKey(*attribute), value); @@ -144,10 +180,15 @@ ValueConverter::setAttribute(SceneObject* sceneObj, const Attribute* attribute, key = &(val.UncheckedGet().GetString()); } else if (val.IsHolding()) { key = &(val.UncheckedGet()); - } else if (val.IsHolding() || val.IsHolding()) { - const int intVal = val.IsHolding() ? - static_cast(val.UncheckedGet()) : - val.UncheckedGet(); + } else { + std::int64_t int64Val = 0; + if (!_extractIntegral64(val, &int64Val)) { + break; // go print normal error message + } + Int intVal = 0; + if (!_narrowIntChecked(sceneObj, attribute, int64Val, &intVal)) { + return; + } int index = 0; for (auto it = attribute->beginEnumValues(); it != attribute->endEnumValues(); ++it) { if (index == intVal) { @@ -157,8 +198,6 @@ ValueConverter::setAttribute(SceneObject* sceneObj, const Attribute* attribute, ++index; } break; - } else { - break; // go print normal error message } for (auto it = attribute->beginEnumValues(); it != attribute->endEnumValues(); ++it) { if (it->second == *key) { @@ -170,10 +209,16 @@ ValueConverter::setAttribute(SceneObject* sceneObj, const Attribute* attribute, Logger::error(sceneObj->getName(), '.', attribute->getName(), ": Invalid enum key '", *key, "'"); return; - } else if (val.IsHolding()) { - sceneObj->set(AttributeKey(*attribute), static_cast(val.UncheckedGet())); - return; } else { + std::int64_t int64Val = 0; + if (_extractIntegral64(val, &int64Val)) { + Int intVal = 0; + if (!_narrowIntChecked(sceneObj, attribute, int64Val, &intVal)) { + return; + } + sceneObj->set(AttributeKey(*attribute), intVal); + return; + } if (_setAttributeRef(sceneObj, attribute, val)) return; } break; From 77a673e0e0f2869ad883021485e88ba41af12245 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sun, 31 May 2026 20:06:16 +0200 Subject: [PATCH 02/15] Houdini 20.5: Solaris build and testing integration Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- CMakeLists.txt | 3 +- cmd/hd_cmd/hd_render/CMakeLists.txt | 12 + cmd/hd_cmd/hd_usd2rdl/CMakeLists.txt | 12 + docs/houdini20_5_hdMoonrayAdapters_compat.md | 264 +++++++++ lib/hydramoonray/CMakeLists.txt | 21 + lib/hydramoonray/Light.cc | 10 +- lib/hydramoonray/Light.h | 4 +- lib/hydramoonray/PrimTypeUtils.cc | 56 ++ lib/hydramoonray/PrimTypeUtils.h | 22 + lib/hydramoonray/Primvars.cc | 236 ++++---- lib/hydramoonray/RenderBuffer.cc | 50 +- lib/hydramoonray/RenderDelegate.cc | 56 +- lib/hydramoonray/RenderDelegate.h | 13 +- lib/hydramoonray/RenderPass.cc | 3 +- lib/hydramoonray/RenderSettings.cc | 48 ++ lib/hydramoonray/RenderSettings.h | 2 + lib/hydramoonray/ValueConverter.cc | 103 ++++ package.py | 4 +- plugin/adapters/CMakeLists.txt | 27 + .../pxr/usd/sdf/childrenProxy.h | 543 ++++++++++++++++++ plugin/houdini/UsdRenderers.json | 18 +- testSuite/README.md | 4 +- .../aov/render_var_source_metadata/scene.usd | 79 +++ .../aov/render_var_source_metadata/test.xml | 17 + testSuite/light/adapter_runtime/README.md | 247 ++++++++ .../moonray_h20_adapter_fixture.usda | 116 ++++ testSuite/light/env_schema_v1/scene.usd | 31 + testSuite/light/env_schema_v1/test.xml | 17 + .../h20_isolation/assets/dome_texture.ppm | 5 + .../bare_sphere_no_material.usda | 73 +++ .../bare_sphere_simple_material.usda | 84 +++ .../dome_constant_no_texture.usda | 77 +++ .../runtime/h20_isolation/dome_texture.usda | 78 +++ .../h20_isolation/dwa_material_no_dome.usda | 83 +++ .../generate_houdini_domelight_lop_fixture.py | 141 +++++ ...nerate_moonray_material_builder_fixture.py | 181 ++++++ 36 files changed, 2612 insertions(+), 128 deletions(-) create mode 100644 docs/houdini20_5_hdMoonrayAdapters_compat.md create mode 100644 lib/hydramoonray/PrimTypeUtils.cc create mode 100644 lib/hydramoonray/PrimTypeUtils.h create mode 100644 plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h create mode 100644 testSuite/aov/render_var_source_metadata/scene.usd create mode 100644 testSuite/aov/render_var_source_metadata/test.xml create mode 100644 testSuite/light/adapter_runtime/README.md create mode 100644 testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda create mode 100644 testSuite/light/env_schema_v1/scene.usd create mode 100644 testSuite/light/env_schema_v1/test.xml create mode 100644 testSuite/runtime/h20_isolation/assets/dome_texture.ppm create mode 100644 testSuite/runtime/h20_isolation/bare_sphere_no_material.usda create mode 100644 testSuite/runtime/h20_isolation/bare_sphere_simple_material.usda create mode 100644 testSuite/runtime/h20_isolation/dome_constant_no_texture.usda create mode 100644 testSuite/runtime/h20_isolation/dome_texture.usda create mode 100644 testSuite/runtime/h20_isolation/dwa_material_no_dome.usda create mode 100644 testSuite/runtime/h20_isolation/generate_houdini_domelight_lop_fixture.py create mode 100644 testSuite/runtime/h20_isolation/generate_moonray_material_builder_fixture.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f61a2a..73172a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ find_package(OpenImageIO REQUIRED) find_package(TBB REQUIRED) if (NOT DEFINED BOOST_PYTHON_COMPONENT_NAME) - # may need to be, e.g. python36, python39 + # may need to be, e.g. python36, python311 set(BOOST_PYTHON_COMPONENT_NAME python) endif() @@ -137,4 +137,3 @@ install( DESTINATION . USE_SOURCE_PERMISSIONS ) - diff --git a/cmd/hd_cmd/hd_render/CMakeLists.txt b/cmd/hd_cmd/hd_render/CMakeLists.txt index 6ac48cb..05c5f43 100644 --- a/cmd/hd_cmd/hd_render/CMakeLists.txt +++ b/cmd/hd_cmd/hd_render/CMakeLists.txt @@ -15,6 +15,18 @@ target_sources(${target} SceneDelegate.cc ) +# Dirty compatibility workaround: +# Houdini 20.5 ships USD 24.03 headers that fail to compile under +# Xcode 26 / AppleClang 21 because pxr/usd/sdf/childrenProxy.h contains an +# invalid _ValueProxy::operator= path that calls SdfChildrenProxy::_Set(), +# which does not exist. This target directly compiles Houdini USD/UsdImaging +# headers, so keep the workaround target-local and remove it when the +# Houdini/USD/toolchain combination compiles normally. +target_include_directories(${target} + BEFORE PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugin/adapters/compat/houdini20_5_usd24_xcode26 +) + if (NOT IsDarwinPlatform) set(PlatformSpecificLibs atomic) endif() diff --git a/cmd/hd_cmd/hd_usd2rdl/CMakeLists.txt b/cmd/hd_cmd/hd_usd2rdl/CMakeLists.txt index 83d5456..a59f3b2 100644 --- a/cmd/hd_cmd/hd_usd2rdl/CMakeLists.txt +++ b/cmd/hd_cmd/hd_usd2rdl/CMakeLists.txt @@ -14,6 +14,18 @@ target_sources(${target} SceneDelegate.cc ) +# Dirty compatibility workaround: +# Houdini 20.5 ships USD 24.03 headers that fail to compile under +# Xcode 26 / AppleClang 21 because pxr/usd/sdf/childrenProxy.h contains an +# invalid _ValueProxy::operator= path that calls SdfChildrenProxy::_Set(), +# which does not exist. This target directly compiles Houdini USD/UsdImaging +# headers, so keep the workaround target-local and remove it when the +# Houdini/USD/toolchain combination compiles normally. +target_include_directories(${target} + BEFORE PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugin/adapters/compat/houdini20_5_usd24_xcode26 +) + if (NOT IsDarwinPlatform) set(PlatformSpecificLibs atomic) endif() diff --git a/docs/houdini20_5_hdMoonrayAdapters_compat.md b/docs/houdini20_5_hdMoonrayAdapters_compat.md new file mode 100644 index 0000000..ea201d4 --- /dev/null +++ b/docs/houdini20_5_hdMoonrayAdapters_compat.md @@ -0,0 +1,264 @@ +# Houdini 20.5 hdMoonrayAdapters Compatibility Shim + +This note documents a dirty compatibility workaround for building +`hdMoonrayAdapters` against Houdini 20.5.684 on macOS with Xcode 26 / +AppleClang 21. + +This is not a MoonRay translation fix, a USD update, or a clean integration +solution. It is a target-local quarantine for a Houdini USD 24.03 header path +that prevents the existing MoonRay mesh-light and light-filter adapter +registration code from compiling under this toolchain. + +## Scope + +The shim is private to the `hdMoonrayAdapters` CMake target only: + +```cmake +target_include_directories(${component} + BEFORE PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/compat/houdini20_5_usd24_xcode26 +) +``` + +It must not be added to the global include path, exported includes, or any +other MoonRay, hdMoonray, USD, Hydra, scene_rdl2, or renderer plugin target. + +`hdMoonrayAdapters` is the only target in this pass that must compile the +Houdini `usdImaging` adapter base classes used by MoonRay's mesh-light and +light-filter adapter registration path. + +## Base Header + +Houdini base header: + +```text +/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources/toolkit/include/pxr/usd/sdf/childrenProxy.h +``` + +Original Houdini header SHA-256: + +```text +9932fe68aba068edf7be24ab7ad166935ab190590cadf4a4e173f6cd8bdf6fc9 +``` + +Shim header: + +```text +/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray/plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h +``` + +Shim header SHA-256: + +```text +a70408c2a77428b384bf593b7aafb2dd718c178e3698807cb720807cb60df08a +``` + +## Exact Diff From Houdini Header + +```diff +--- /Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources/toolkit/include/pxr/usd/sdf/childrenProxy.h 2026-05-30 18:56:16 ++++ /Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray/plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h 2026-05-31 00:58:58 +@@ -25,6 +25,23 @@ + #define PXR_USD_SDF_CHILDREN_PROXY_H + + /// \file sdf/childrenProxy.h ++ ++// Dirty compatibility workaround: ++// Houdini 20.5 ships USD 24.03 headers that fail to compile ++// through usdImaging lightAdapter/lightFilterAdapter under ++// Xcode 26 / AppleClang 21 because pxr/usd/sdf/childrenProxy.h ++// contains an invalid _ValueProxy::operator= path that calls ++// SdfChildrenProxy::_Set(), which does not exist. ++// ++// This shim is target-local to hdMoonrayAdapters only. ++// It does not represent a MoonRay integration fix, a USD update, ++// or a translation layer. It only quarantines a compiler/header ++// compatibility problem so the existing MoonRay mesh-light and ++// light-filter adapter registration path can still be built and ++// runtime-tested. ++// ++// Remove this workaround when building against a Houdini/USD/toolchain ++// combination where the usdImaging adapter headers compile normally. + + #include "pxr/pxr.h" + #include "pxr/usd/sdf/api.h" +@@ -72,7 +89,8 @@ + template + _ValueProxy& operator=(const U& x) + { +- _owner->_Set(*_pos, x); ++ _owner->erase(_owner->_view.key(*_pos)); ++ _owner->insert(mapped_type(x)); + return *this; + } +``` + +The adapter sources do not use `SdfChildrenProxy` directly. The failing path is +reached while parsing Houdini's `usdImaging` adapter headers. + +## Toolchain + +Compiler path: + +```text +/usr/bin/clang++ +``` + +AppleClang version: + +```text +Apple clang version 21.0.0 (clang-2100.1.1.101) +``` + +Actual Xcode toolchain compiler: + +```text +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ +Apple clang version 21.0.0 (clang-2100.1.1.101) +``` + +SDK path: + +```text +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk +``` + +SDK version: + +```text +26.5 +``` + +`DEVELOPER_DIR` was unset for these probes, so `xcrun` selected the default +Xcode installation. + +Homebrew LLVM was checked and was unavailable: + +```text +brew --prefix llvm +/opt/homebrew/opt/llvm + +/opt/homebrew/opt/llvm/bin/clang++ --version +zsh: no such file or directory: /opt/homebrew/opt/llvm/bin/clang++ +``` + +## Baseline Probe Without Shim + +The probes were modeled after Houdini/HDK compile flags, not generic CMake +flags. They used Houdini toolkit includes, Houdini Python 3.11 includes, +C++17, the active macOS SDK, and the HDK-style preprocessor defines. + +### lightAdapter.h + +Command: + +```zsh +SDK=$(xcrun --show-sdk-path) +HFS=/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources +printf '#include \nint main(){return 0;}\n' | clang++ -fsyntax-only -x c++ - -std=c++17 -mmacosx-version-min=10.15 -fPIC -faligned-new -isysroot "$SDK" -I"$HFS/toolkit/include" -I"$HFS/toolkit/include/python3.11" -DVERSION=\"20.5.684\" -DARM64 -DSIZEOF_VOID_P=8 -DFBX_ENABLED=1 -DOPENCL_ENABLED=1 -DOPENVDB_ENABLED=1 -D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS=1 -DSESI_LITTLE_ENDIAN -DHBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT=1 -DHBOOST_BIND_GLOBAL_PLACEHOLDERS=1 -DUT_ASSERT_LEVEL=0 -DH_PYTHON_VERSION=3.11 -DGCC3 -DGCC4 -D_GNU_SOURCE -DENABLE_THREADS -DUSE_PTHREADS -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DMBSD -DMBSD_COCOA -DMBSD_ARM -DMAKING_DSO -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION -DNEED_SPECIALIZATION_STORAGE +``` + +Result: + +```text +FAIL: pxr/usd/sdf/childrenProxy.h:75:21: error: no member named '_Set' in 'SdfChildrenProxy<_View>' +``` + +### lightFilterAdapter.h + +Command: + +```zsh +SDK=$(xcrun --show-sdk-path) +HFS=/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources +printf '#include \nint main(){return 0;}\n' | clang++ -fsyntax-only -x c++ - -std=c++17 -mmacosx-version-min=10.15 -fPIC -faligned-new -isysroot "$SDK" -I"$HFS/toolkit/include" -I"$HFS/toolkit/include/python3.11" -DVERSION=\"20.5.684\" -DARM64 -DSIZEOF_VOID_P=8 -DFBX_ENABLED=1 -DOPENCL_ENABLED=1 -DOPENVDB_ENABLED=1 -D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS=1 -DSESI_LITTLE_ENDIAN -DHBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT=1 -DHBOOST_BIND_GLOBAL_PLACEHOLDERS=1 -DUT_ASSERT_LEVEL=0 -DH_PYTHON_VERSION=3.11 -DGCC3 -DGCC4 -D_GNU_SOURCE -DENABLE_THREADS -DUSE_PTHREADS -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DMBSD -DMBSD_COCOA -DMBSD_ARM -DMAKING_DSO -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION -DNEED_SPECIALIZATION_STORAGE +``` + +Result: + +```text +FAIL: pxr/usd/sdf/childrenProxy.h:75:21: error: no member named '_Set' in 'SdfChildrenProxy<_View>' +``` + +## Adapter-Target-Style Probe With Shim + +These probes add only the target-local shim include directory before Houdini's +toolkit include path. + +### lightAdapter.h + +Command: + +```zsh +SDK=$(xcrun --show-sdk-path) +HFS=/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources +SHIM=/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray/plugin/adapters/compat/houdini20_5_usd24_xcode26 +printf '#include \nint main(){return 0;}\n' | clang++ -fsyntax-only -x c++ - -std=c++17 -mmacosx-version-min=10.15 -fPIC -faligned-new -isysroot "$SDK" -I"$SHIM" -I"$HFS/toolkit/include" -I"$HFS/toolkit/include/python3.11" -DVERSION=\"20.5.684\" -DARM64 -DSIZEOF_VOID_P=8 -DFBX_ENABLED=1 -DOPENCL_ENABLED=1 -DOPENVDB_ENABLED=1 -D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS=1 -DSESI_LITTLE_ENDIAN -DHBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT=1 -DHBOOST_BIND_GLOBAL_PLACEHOLDERS=1 -DUT_ASSERT_LEVEL=0 -DH_PYTHON_VERSION=3.11 -DGCC3 -DGCC4 -D_GNU_SOURCE -DENABLE_THREADS -DUSE_PTHREADS -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DMBSD -DMBSD_COCOA -DMBSD_ARM -DMAKING_DSO -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION -DNEED_SPECIALIZATION_STORAGE +``` + +Result: + +```text +PASS with one TBB deprecation warning. +``` + +### lightFilterAdapter.h + +Command: + +```zsh +SDK=$(xcrun --show-sdk-path) +HFS=/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources +SHIM=/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray/plugin/adapters/compat/houdini20_5_usd24_xcode26 +printf '#include \nint main(){return 0;}\n' | clang++ -fsyntax-only -x c++ - -std=c++17 -mmacosx-version-min=10.15 -fPIC -faligned-new -isysroot "$SDK" -I"$SHIM" -I"$HFS/toolkit/include" -I"$HFS/toolkit/include/python3.11" -DVERSION=\"20.5.684\" -DARM64 -DSIZEOF_VOID_P=8 -DFBX_ENABLED=1 -DOPENCL_ENABLED=1 -DOPENVDB_ENABLED=1 -D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS=1 -DSESI_LITTLE_ENDIAN -DHBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT=1 -DHBOOST_BIND_GLOBAL_PLACEHOLDERS=1 -DUT_ASSERT_LEVEL=0 -DH_PYTHON_VERSION=3.11 -DGCC3 -DGCC4 -D_GNU_SOURCE -DENABLE_THREADS -DUSE_PTHREADS -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DMBSD -DMBSD_COCOA -DMBSD_ARM -DMAKING_DSO -D_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION -DNEED_SPECIALIZATION_STORAGE +``` + +Result: + +```text +PASS with one TBB deprecation warning. +``` + +## Required Validation + +The shim only proves that the Houdini `usdImaging` adapter headers can be +parsed for the `hdMoonrayAdapters` target under the current compiler. It does +not prove adapter runtime behavior. + +Before treating `hdMoonrayAdapters` behavior as working, validate: + +1. Configure and build: + + ```zsh + cd /Applications/MoonRay/openmoonray + cmake --preset macos-houdini-release + cmake --build /Applications/MoonRay/build --target hdMoonrayAdapters --config Release --verbose + ``` + +2. Install/discovery: + + ```zsh + find /Applications/MoonRay/installs/openmoonray -iname "*hdMoonrayAdapters*" -o -path "*/plugin/pxr/hdMoonrayAdapters/plugInfo.json" + source /Applications/MoonRay/openmoonray/scripts/macOS/setupHoudini.sh + echo "$PXR_PLUGINPATH_NAME" + ``` + +3. Runtime mesh-light fixture: + + ```text + USD relationship -> UsdImaging adapter -> Hydra geometryLight sprim + -> hdMoonray Light::syncParams -> native MoonRay MeshLight geometry + ``` + +4. Runtime light-filter fixture: + + ```text + USD moonray:projector / moonray:light_filters relationships + -> UsdImaging adapter -> Hydra lightFilter sprim + -> hdMoonray LightFilter::syncProjector/syncCombineFilters + -> native MoonRay LightFilterSet + ``` + +Remove the shim when a Houdini/USD/toolchain combination is available where +`pxr/usdImaging/usdImaging/lightAdapter.h` and +`pxr/usdImaging/usdImaging/lightFilterAdapter.h` compile without it. diff --git a/lib/hydramoonray/CMakeLists.txt b/lib/hydramoonray/CMakeLists.txt index 5bc7fa8..a0f3dce 100644 --- a/lib/hydramoonray/CMakeLists.txt +++ b/lib/hydramoonray/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(${component} MurmurHash3.cc NullRenderer.cc Points.cc + PrimTypeUtils.cc Primvars.cc Procedural.cc RenderBuffer.cc @@ -50,6 +51,7 @@ set_property(TARGET ${component} NullRenderer.h PixelData.h Points.h + PrimTypeUtils.h Procedural.h RenderBuffer.h RenderDelegate.h @@ -67,6 +69,25 @@ target_include_directories(${component} $ ) +# Dirty compatibility workaround: +# Houdini 20.5 ships USD 24.03 headers that fail to compile through +# usdImaging/delegate.h under Xcode 26 / AppleClang 21 because +# pxr/usd/sdf/childrenProxy.h contains an invalid _ValueProxy::operator= +# path that calls SdfChildrenProxy::_Set(), which does not exist. +# +# This include path is limited to the hydramoonray source files that actually +# use UsdImagingDelegate for USD time-sampling. It does not represent a +# MoonRay integration fix, a USD update, or a translation layer. Remove this +# when building against a Houdini/USD/toolchain combination where the +# usdImaging headers compile normally. +set(houdini205Usd24CompatInclude + ${CMAKE_CURRENT_SOURCE_DIR}/../../plugin/adapters/compat/houdini20_5_usd24_xcode26 +) +set_source_files_properties(RenderDelegate.cc RenderPass.cc + PROPERTIES + COMPILE_FLAGS "-I${houdini205Usd24CompatInclude}" +) + if (NOT IsDarwinPlatform) set(PlatformSpecificLibs atomic) endif() diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index 65409e4..af4c608 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -4,6 +4,7 @@ #include "Light.h" #include "LightFilter.h" #include "Mesh.h" +#include "PrimTypeUtils.h" #include "RenderDelegate.h" #include "ValueConverter.h" #include "HdmLog.h" @@ -56,6 +57,13 @@ namespace hdMoonray { using scene_rdl2::logging::Logger; +Light::Light(const pxr::TfToken& type, const pxr::SdfPath& id): + pxr::HdLight(id), + mType(canonicalSprimType(type)), + mRectToSpotlight(false) +{ +} + pxr::HdDirtyBits Light::GetInitialDirtyBitsMask() const { @@ -111,7 +119,7 @@ Light::rdlClassName(const pxr::SdfPath& id, bool Light::isSupportedType(const pxr::TfToken& type) { - return !defaultRdlClassName(type).empty(); + return !defaultRdlClassName(canonicalSprimType(type)).empty(); } diff --git a/lib/hydramoonray/Light.h b/lib/hydramoonray/Light.h index 699284a..cb57003 100644 --- a/lib/hydramoonray/Light.h +++ b/lib/hydramoonray/Light.h @@ -18,8 +18,7 @@ class RenderDelegate; class Light: public pxr::HdLight { public: - Light(const pxr::TfToken& type, const pxr::SdfPath& id): - pxr::HdLight(id), mType(type), mRectToSpotlight(false) {} + Light(const pxr::TfToken& type, const pxr::SdfPath& id); /// Dirty bits to pass to first call to Sync() pxr::HdDirtyBits GetInitialDirtyBitsMask() const override; @@ -65,4 +64,3 @@ class Light: public pxr::HdLight }; } - diff --git a/lib/hydramoonray/PrimTypeUtils.cc b/lib/hydramoonray/PrimTypeUtils.cc new file mode 100644 index 0000000..04b486e --- /dev/null +++ b/lib/hydramoonray/PrimTypeUtils.cc @@ -0,0 +1,56 @@ +// Copyright 2023-2024 DreamWorks Animation LLC +// SPDX-License-Identifier: Apache-2.0 + +#include "PrimTypeUtils.h" + +#include + +namespace { + +const pxr::TfToken domeLight1Token("DomeLight_1", pxr::TfToken::Immortal); +const pxr::TfToken usdLuxDomeLight1Token("UsdLuxDomeLight_1", pxr::TfToken::Immortal); +const pxr::TfToken openvdbAssetToken("openvdbAsset", pxr::TfToken::Immortal); +const pxr::TfToken houdiniFieldAssetToken("houdiniFieldAsset", pxr::TfToken::Immortal); + +} + +namespace hdMoonray { + +pxr::TfToken +canonicalSprimType(const pxr::TfToken& type) +{ + if (type == domeLight1Token || type == usdLuxDomeLight1Token) { + return pxr::HdPrimTypeTokens->domeLight; + } + return type; +} + +pxr::TfToken +canonicalBprimType(const pxr::TfToken& type) +{ + if (type == houdiniFieldAssetToken) { + return openvdbAssetToken; + } + return type; +} + +const pxr::TfTokenVector& +supportedSprimTypeAliases() +{ + static const pxr::TfTokenVector aliases = { + domeLight1Token, + usdLuxDomeLight1Token, + }; + return aliases; +} + +const pxr::TfTokenVector& +supportedBprimTypeAliases() +{ + static const pxr::TfTokenVector aliases = { + houdiniFieldAssetToken, + }; + return aliases; +} + +} diff --git a/lib/hydramoonray/PrimTypeUtils.h b/lib/hydramoonray/PrimTypeUtils.h new file mode 100644 index 0000000..e1a8dc4 --- /dev/null +++ b/lib/hydramoonray/PrimTypeUtils.h @@ -0,0 +1,22 @@ +// Copyright 2023-2024 DreamWorks Animation LLC +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace hdMoonray { + +/// Return the canonical Hydra sprim token MoonRay uses internally. +pxr::TfToken canonicalSprimType(const pxr::TfToken& type); + +/// Return the canonical Hydra bprim token MoonRay uses internally. +pxr::TfToken canonicalBprimType(const pxr::TfToken& type); + +/// Houdini/USD schema-version aliases that should be advertised as supported. +const pxr::TfTokenVector& supportedSprimTypeAliases(); + +/// Houdini-specific field aliases that should be advertised as supported. +const pxr::TfTokenVector& supportedBprimTypeAliases(); + +} diff --git a/lib/hydramoonray/Primvars.cc b/lib/hydramoonray/Primvars.cc index 737e26c..53ed554 100644 --- a/lib/hydramoonray/Primvars.cc +++ b/lib/hydramoonray/Primvars.cc @@ -39,17 +39,30 @@ namespace { return false; } + template + bool emptyArray(const T& v, const TfToken& name) + { + if (!v.empty()) { + return false; + } + Logger::warn("Skipping empty primvar '", name, "'"); + return true; + } + // utility to set a Vec3f attribute inline void setVec3fPrimvar(Geometry* geometry, const std::string& rdlName, + const TfToken& hydraName, const VtValue& values) { if (values.IsHolding()) { const VtVec3fArray& va = values.UncheckedGet(); + if (emptyArray(va, hydraName)) return; const Vec3f* p = reinterpret_cast(&va[0]); geometry->set(rdlName, Vec3fVector(p, p + va.size())); } else { - Logger::warn(rdlName, " requires a Vec3f array"); + Logger::warn("Skipping primvar '", hydraName, "': ", rdlName, + " requires a Vec3f array, got ", values.GetTypeName()); } } } @@ -172,7 +185,7 @@ GeometryMixin::primvarChanged(HdSceneDelegate *sceneDelegate, if (value.IsEmpty()) { mGeometry->resetToDefault("accleration_list"); // sic } else { - setVec3fPrimvar(mGeometry, "accleration_list", value); + setVec3fPrimvar(mGeometry, "accleration_list", name, value); } } catch (std::exception& e) { // geometry isn't guarantred to have "accleration_list" @@ -243,86 +256,104 @@ void setUserDataInterpolation(UserData* userData, } } -void setUserDataValues(UserData* userData, - const TfToken& name, - const VtValue& values, - const TfToken& role) -{ - // we have to handle the types case-by-case + bool setUserDataValues(UserData* userData, + const TfToken& name, + const VtValue& values, + const TfToken& role) + { + // we have to handle the types case-by-case // // Apparently, int user data cannot be output into RenderOutput // buffers, so id-type primvars must be of float type. If the // RenderBuffer is requested as an int format, it is translated - // from float to int in RenderBuffer::Resolve() [q.v.] - if (values.IsHolding()) { - const VtFloatArray& v = values.UncheckedGet(); - const float* p = &v[0]; - userData->setFloatData(name, FloatVector(p, p + v.size())); - } else if (values.IsHolding()) { - float v = values.UncheckedGet(); - userData->setFloatData(name, FloatVector{v}); - } else if (values.IsHolding()) { - float v = float(values.UncheckedGet()); - userData->setFloatData(name, FloatVector{v}); - } else if (values.IsHolding()) { - const VtVec2fArray& v = values.UncheckedGet(); - const Vec2f* p = reinterpret_cast(&v[0]); - userData->setVec2fData(name, Vec2fVector(p, p + v.size())); - } else if (values.IsHolding()) { - const VtVec3fArray& v = values.UncheckedGet(); - if (role == HdPrimvarRoleTokens->color) { - const Rgb* p = reinterpret_cast(&v[0]); - userData->setColorData(name, RgbVector(p, p + v.size())); - } else { - const Vec3f* p = reinterpret_cast(&v[0]); - userData->setVec3fData(name, Vec3fVector(p, p + v.size())); + // from float to int in RenderBuffer::Resolve() [q.v.] + if (values.IsHolding()) { + const VtFloatArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + const float* p = &v[0]; + userData->setFloatData(name, FloatVector(p, p + v.size())); + } else if (values.IsHolding()) { + float v = values.UncheckedGet(); + userData->setFloatData(name, FloatVector{v}); + } else if (values.IsHolding()) { + float v = float(values.UncheckedGet()); + userData->setFloatData(name, FloatVector{v}); + } else if (values.IsHolding()) { + const VtVec2fArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + const Vec2f* p = reinterpret_cast(&v[0]); + userData->setVec2fData(name, Vec2fVector(p, p + v.size())); + } else if (values.IsHolding()) { + const VtVec3fArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + if (role == HdPrimvarRoleTokens->color) { + const Rgb* p = reinterpret_cast(&v[0]); + userData->setColorData(name, RgbVector(p, p + v.size())); + } else { + const Vec3f* p = reinterpret_cast(&v[0]); + userData->setVec3fData(name, Vec3fVector(p, p + v.size())); } } else if (values.IsHolding()) { const GfVec3f& v = values.UncheckedGet(); - if (role == HdPrimvarRoleTokens->color) { - userData->setColorData(name, RgbVector{reinterpret_cast(v)}); - } else { - userData->setVec3fData(name, Vec3fVector{reinterpret_cast(v)}); - } - } else if (values.IsHolding()) { - const VtStringArray& v = values.UncheckedGet(); - const std::string* p = &v[0]; - userData->setStringData(name, StringVector(p, p + v.size())); - } else if (values.IsHolding()) { - const std::string& v = values.Get(); - userData->setStringData(name, StringVector{v}); - } else if (values.IsHolding()) { - // HDM-266 moonray does not support attribute type Int for face varying attribute, - // cast to float - const VtUIntArray& v = values.UncheckedGet(); - const float* p = reinterpret_cast(&v[0]); - userData->setFloatData(name, FloatVector(p, p + v.size())); - } else if (values.IsHolding()) { - const VtIntArray& v = values.UncheckedGet(); - // HDM-266 moonray does not support attribute type Int for face varying attribute, - // cast to float - const float* p = reinterpret_cast(&v[0]); - userData->setFloatData(name.GetString(), FloatVector(p, p + v.size())); - } else if (values.IsHolding()) { - int v = values.UncheckedGet(); - userData->setIntData(name, IntVector{v}); - } else if (values.IsHolding()) { - int v = int(values.UncheckedGet()); - userData->setIntData(name, IntVector{v}); - } else if (values.IsHolding()) { - const VtBoolArray& v = values.UncheckedGet(); - const bool* p = &v[0]; - userData->setBoolData(name, BoolVector(p, p + v.size())); - } else { - Logger::warn(userData->getName(), ": ", values.GetTypeName(), " not translated"); - } - } -} + if (role == HdPrimvarRoleTokens->color) { + userData->setColorData(name, RgbVector{reinterpret_cast(v)}); + } else { + userData->setVec3fData(name, Vec3fVector{reinterpret_cast(v)}); + } + } else if (values.IsHolding()) { + const VtStringArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + const std::string* p = &v[0]; + userData->setStringData(name, StringVector(p, p + v.size())); + } else if (values.IsHolding()) { + const std::string& v = values.Get(); + userData->setStringData(name, StringVector{v}); + } else if (values.IsHolding()) { + // HDM-266 moonray does not support attribute type Int for face varying attribute, + // cast to float + const VtUIntArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + FloatVector data; + data.reserve(v.size()); + for (unsigned int item : v) { + data.push_back(static_cast(item)); + } + userData->setFloatData(name, std::move(data)); + } else if (values.IsHolding()) { + const VtIntArray& v = values.UncheckedGet(); + // HDM-266 moonray does not support attribute type Int for face varying attribute, + // cast to float + if (emptyArray(v, name)) return false; + FloatVector data; + data.reserve(v.size()); + for (int item : v) { + data.push_back(static_cast(item)); + } + userData->setFloatData(name.GetString(), std::move(data)); + } else if (values.IsHolding()) { + int v = values.UncheckedGet(); + userData->setIntData(name, IntVector{v}); + } else if (values.IsHolding()) { + int v = int(values.UncheckedGet()); + userData->setIntData(name, IntVector{v}); + } else if (values.IsHolding()) { + const VtBoolArray& v = values.UncheckedGet(); + if (emptyArray(v, name)) return false; + BoolVector data; + for (bool item : v) { + data.push_back(item); + } + userData->setBoolData(name, std::move(data)); + } else { + Logger::warn("Skipping primvar '", name, "' on ", userData->getName(), + ": ", values.GetTypeName(), " not translated"); + return false; + } + return true; + } } // namespace { -namespace hdMoonray { - void GeometryMixin::primvarUserData(RenderDelegate& renderDelegate, @@ -351,10 +382,13 @@ GeometryMixin::primvarUserData(RenderDelegate& renderDelegate, mUserDataChanged = true; } - // store the primvar values into the UserData object - UpdateGuard guard(renderDelegate, userData); - setUserDataInterpolation(userData, interp); - setUserDataValues(userData, name, value, role); + // store the primvar values into the UserData object + UpdateGuard guard(renderDelegate, userData); + setUserDataInterpolation(userData, interp); + if (!setUserDataValues(userData, name, value, role)) { + mUserData.erase(name); + mUserDataChanged = true; + } } @@ -374,28 +408,33 @@ GeometryMixin::setVec3fPrimvarMb(HdSceneDelegate* sceneDelegate, mGeometry->resetToDefault(rdlName_1); return; } - - HdTimeSampleArray vals; - sceneDelegate->SamplePrimvar(rprim.GetId(), hydraName, &vals); - - if (vals.count <= 1) { - setVec3fPrimvar(mGeometry, rdlName_0, value); - mGeometry->resetToDefault(rdlName_1); - } else { - const bool sizesMatch = - vals.values[0].Get().size() == - vals.values[vals.count - 1].Get().size(); - if (sizesMatch) { - setVec3fPrimvar(mGeometry, rdlName_0, vals.values[0]); - setVec3fPrimvar(mGeometry, rdlName_1, vals.values[vals.count - 1]); - - } else { - // If the sizes of the arrays don't match, then the topology - // is changing and we should just use the second set of values - setVec3fPrimvar(mGeometry, rdlName_0, vals.values[vals.count - 1]); - } - } + HdTimeSampleArray vals; + sceneDelegate->SamplePrimvar(rprim.GetId(), hydraName, &vals); + + if (vals.count <= 1) { + setVec3fPrimvar(mGeometry, rdlName_0, hydraName, value); + mGeometry->resetToDefault(rdlName_1); + } else { + if (!vals.values[0].IsHolding() || + !vals.values[vals.count - 1].IsHolding()) { + Logger::warn("Skipping primvar '", hydraName, + "': motion samples require Vec3f arrays"); + return; + } + const bool sizesMatch = + vals.values[0].Get().size() == + vals.values[vals.count - 1].Get().size(); + + if (sizesMatch) { + setVec3fPrimvar(mGeometry, rdlName_0, hydraName, vals.values[0]); + setVec3fPrimvar(mGeometry, rdlName_1, hydraName, vals.values[vals.count - 1]); + } else { + // If the sizes of the arrays don't match, then the topology + // is changing and we should just use the second set of values + setVec3fPrimvar(mGeometry, rdlName_0, hydraName, vals.values[vals.count - 1]); + } + } } catch (std::exception& e) { // may be called in cases that rdlName_0 or _1 don't exist : e.g. // if Hydra generates a "points" primvar for procedurals that don't @@ -404,6 +443,3 @@ GeometryMixin::setVec3fPrimvarMb(HdSceneDelegate* sceneDelegate, } } - - - diff --git a/lib/hydramoonray/RenderBuffer.cc b/lib/hydramoonray/RenderBuffer.cc index af618ac..45488a0 100644 --- a/lib/hydramoonray/RenderBuffer.cc +++ b/lib/hydramoonray/RenderBuffer.cc @@ -131,6 +131,50 @@ void split(pxr::TfToken token, pxr::TfToken& prefix, pxr::TfToken& suffix) suffix = token; } +pxr::TfToken +tokenFromValue(const pxr::VtValue& value) +{ + if (value.IsHolding()) { + return value.UncheckedGet(); + } + if (value.IsHolding()) { + return pxr::TfToken(value.UncheckedGet()); + } + return pxr::TfToken(); +} + +pxr::TfToken +aovNameFromSettings(const pxr::HdRenderPassAovBinding& aovBinding) +{ + auto sourceNameIt = aovBinding.aovSettings.find(pxr::TfToken("sourceName")); + pxr::TfToken sourceName = sourceNameIt == aovBinding.aovSettings.end() ? + pxr::TfToken() : tokenFromValue(sourceNameIt->second); + if (sourceName.IsEmpty()) { + return aovBinding.aovName; + } + + auto sourceTypeIt = aovBinding.aovSettings.find(pxr::TfToken("sourceType")); + const pxr::TfToken sourceType = sourceTypeIt == aovBinding.aovSettings.end() ? + pxr::TfToken() : tokenFromValue(sourceTypeIt->second); + if (sourceType == pxr::TfToken("primvar") || sourceType == pxr::TfToken("primvars")) { + if (lookup(sourceName)) { + return sourceName; + } + return pxr::TfToken("primvars:" + sourceName.GetString()); + } + if (sourceType == pxr::HdAovTokens->lpe || sourceType == pxr::TfToken("lpe")) { + return pxr::TfToken("lpe:" + sourceName.GetString()); + } + if (sourceType == pxr::HdAovTokens->shader || sourceType == pxr::TfToken("shader")) { + return pxr::TfToken("shader:" + sourceName.GetString()); + } + if (!sourceType.IsEmpty() && sourceType != pxr::TfToken("raw")) { + scene_rdl2::logging::Logger::warn("Unsupported AOV sourceType '", sourceType, + "' for sourceName '", sourceName, "'"); + } + return sourceName; +} + } namespace hdMoonray { @@ -217,7 +261,7 @@ RenderBuffer::bind(const pxr::HdRenderPassAovBinding& aovBinding, const Camera* { hdmLogRenderBuffer("Bind", GetId()); - mAovName = aovBinding.aovName; + mAovName = aovNameFromSettings(aovBinding); mNear = camera->getNear(); mFar = camera->getFar(); pxr::HdAovSettingsMap aovSettings = aovBinding.aovSettings; @@ -342,6 +386,10 @@ RenderBuffer::bind(const pxr::HdRenderPassAovBinding& aovBinding, const Camera* } else if (prefix == pxr::HdAovTokens->shader) { ro.setResult(ro.RESULT_MATERIAL_AOV); ro.setMaterialAov(suffix.GetString()); + } else { + Logger::warn("Unsupported AOV mapping '", mAovName, + "'; RenderOutput disabled"); + ro.setActive(false); } } diff --git a/lib/hydramoonray/RenderDelegate.cc b/lib/hydramoonray/RenderDelegate.cc index 0d21114..9f68538 100644 --- a/lib/hydramoonray/RenderDelegate.cc +++ b/lib/hydramoonray/RenderDelegate.cc @@ -11,6 +11,7 @@ #include "Material.h" #include "Mesh.h" #include "Points.h" +#include "PrimTypeUtils.h" #include "Procedural.h" #include "RenderBuffer.h" #include "Renderer.h" @@ -164,7 +165,7 @@ RenderDelegate::GetSupportedRprimTypes() const pxr::TfTokenVector const& RenderDelegate::GetSupportedSprimTypes() const { - static const pxr::TfTokenVector SUPPORTED_SPRIM_TYPES = { + static pxr::TfTokenVector SUPPORTED_SPRIM_TYPES = { pxr::HdPrimTypeTokens->camera, //pxr::HdPrimTypeTokens->drawTarget, // used by hdSt to return OpenGL textures pxr::HdPrimTypeTokens->material, @@ -180,16 +181,30 @@ RenderDelegate::GetSupportedSprimTypes() const lightFilterToken, pxr::HdPrimTypeTokens->extComputation, }; + static const bool aliasesAdded = []() { + for (const pxr::TfToken& alias : supportedSprimTypeAliases()) { + SUPPORTED_SPRIM_TYPES.push_back(alias); + } + return true; + }(); + (void)aliasesAdded; return SUPPORTED_SPRIM_TYPES; } pxr::TfTokenVector const& RenderDelegate::GetSupportedBprimTypes() const { - static const pxr::TfTokenVector SUPPORTED_BPRIM_TYPES = { + static pxr::TfTokenVector SUPPORTED_BPRIM_TYPES = { pxr::HdPrimTypeTokens->renderBuffer, openvdbAssetToken, }; + static const bool aliasesAdded = []() { + for (const pxr::TfToken& alias : supportedBprimTypeAliases()) { + SUPPORTED_BPRIM_TYPES.push_back(alias); + } + return true; + }(); + (void)aliasesAdded; return SUPPORTED_BPRIM_TYPES; } @@ -201,6 +216,21 @@ RenderDelegate::GetResourceRegistry() const return ptr; } +void +RenderDelegate::SetRenderSetting(pxr::TfToken const& key, pxr::VtValue const& value) +{ + pxr::HdRenderDelegate::SetRenderSetting(key, value); + + static const pxr::TfToken renderPauseToken("houdini:render_pause"); + if (key == renderPauseToken && value.IsHolding()) { + if (value.UncheckedGet()) { + Pause(); + } else { + Resume(); + } + } +} + // Result of this is passed to RenderBuffer::Allocate pxr::HdAovDescriptor RenderDelegate::GetDefaultAovDescriptor(pxr::TfToken const& name) const @@ -311,18 +341,19 @@ pxr::HdSprim* RenderDelegate::CreateSprim(pxr::TfToken const& typeId, pxr::SdfPath const& sprimId) { - if (typeId == pxr::HdPrimTypeTokens->camera) { + const pxr::TfToken type = canonicalSprimType(typeId); + if (type == pxr::HdPrimTypeTokens->camera) { return new Camera(sprimId); - } else if (typeId == pxr::HdPrimTypeTokens->material) { + } else if (type == pxr::HdPrimTypeTokens->material) { return new Material(sprimId); - } else if (typeId == pxr::HdPrimTypeTokens->coordSys) { + } else if (type == pxr::HdPrimTypeTokens->coordSys) { return new CoordSys(sprimId); - } else if (typeId == pxr::HdPrimTypeTokens->extComputation) { + } else if (type == pxr::HdPrimTypeTokens->extComputation) { return new pxr::HdExtComputation(sprimId); // no subclass needed - } else if (typeId == lightFilterToken) { - return new LightFilter(typeId,sprimId); - } else if (Light::isSupportedType(typeId)) { - auto p = new Light(typeId, sprimId); + } else if (type == lightFilterToken) { + return new LightFilter(type,sprimId); + } else if (Light::isSupportedType(type)) { + auto p = new Light(type, sprimId); if (not sprimId.IsEmpty()) mLights.insert(p); return p; @@ -349,9 +380,10 @@ pxr::HdBprim * RenderDelegate::CreateBprim(pxr::TfToken const& typeId, pxr::SdfPath const& bprimId) { - if (typeId == pxr::HdPrimTypeTokens->renderBuffer) { + const pxr::TfToken type = canonicalBprimType(typeId); + if (type == pxr::HdPrimTypeTokens->renderBuffer) { return new RenderBuffer(bprimId); - } else if (typeId == openvdbAssetToken) { + } else if (type == openvdbAssetToken) { return new OpenVdbAsset(bprimId); } else { Logger::warn(bprimId, ": unknown Bprim type ", typeId); diff --git a/lib/hydramoonray/RenderDelegate.h b/lib/hydramoonray/RenderDelegate.h index 568aa68..1da7bb3 100644 --- a/lib/hydramoonray/RenderDelegate.h +++ b/lib/hydramoonray/RenderDelegate.h @@ -5,8 +5,8 @@ #include "RenderSettings.h" +#include #include -#include namespace scene_rdl2 {namespace rdl2 { class Camera; @@ -21,6 +21,10 @@ class LayerAssignment; class VolumeShader; } } +PXR_NAMESPACE_OPEN_SCOPE +class UsdImagingDelegate; +PXR_NAMESPACE_CLOSE_SCOPE + namespace hdMoonray { class Renderer; @@ -58,6 +62,11 @@ class RenderDelegate final: public pxr::HdRenderDelegate pxr::HdRenderSettingDescriptorList GetRenderSettingDescriptors() const override { return mRenderSettingDescriptors; } + /// Houdini and husk send render settings outside normal Hydra dirty-bit + /// Sync() flow. Keep the base storage/versioning, and apply fast controls + /// that need immediate response. + void SetRenderSetting(pxr::TfToken const& key, pxr::VtValue const& value) override; + #if PXR_VERSION >= 2108 /// Commands supported by this render delegate. pxr::HdCommandDescriptors GetCommandDescriptors() const override; @@ -313,7 +322,7 @@ class RenderDelegate final: public pxr::HdRenderDelegate Renderer* mRenderer = nullptr; RenderSettings mRenderSettings; - unsigned mPreviousRenderSettings = 0; + unsigned mPreviousRenderSettings = ~0u; pxr::HdRenderSettingDescriptorList mRenderSettingDescriptors; bool mDisableLighting = false; diff --git a/lib/hydramoonray/RenderPass.cc b/lib/hydramoonray/RenderPass.cc index f1a9b63..8209e25 100644 --- a/lib/hydramoonray/RenderPass.cc +++ b/lib/hydramoonray/RenderPass.cc @@ -7,6 +7,7 @@ #include "Camera.h" #include +#include #include #include @@ -101,6 +102,7 @@ RenderPass::_Execute(const pxr::HdRenderPassStateSharedPtr& renderPassState, motionSteps[0] = interval.first; motionSteps[1] = interval.second; } + renderDelegate.renderSettings().getHoudiniFrame(frame); const bool motionBlur = renderDelegate.getEnableMotionBlur() && motionSteps[0] < motionSteps[1]; @@ -151,4 +153,3 @@ RenderPass::_Execute(const pxr::HdRenderPassStateSharedPtr& renderPassState, } } - diff --git a/lib/hydramoonray/RenderSettings.cc b/lib/hydramoonray/RenderSettings.cc index a8e12cf..bb69e52 100644 --- a/lib/hydramoonray/RenderSettings.cc +++ b/lib/hydramoonray/RenderSettings.cc @@ -32,6 +32,14 @@ TF_DEFINE_PRIVATE_TOKENS(Tokens, (pruneWrapDeform) (forcePolygon) (executionMode) + ((houdiniFrame, "houdini:frame")) + ((houdiniFps, "houdini:fps")) + ((houdiniViewport, "houdini:viewport")) + ((usdFilename, "usdFilename")) + ((usdFileTimeStamp, "usdFileTimeStamp")) + ((renderCameraPath, "renderCameraPath")) + ((batchCommandLine, "batchCommandLine")) + ((huskDelegateOptions, "huskDelegateOptions")) ); } @@ -150,6 +158,46 @@ RenderSettings::getExecutionMode() const } } +bool +RenderSettings::getHoudiniFrame(float& frame) const +{ + VtValue val = mDelegate.GetRenderSetting(Tokens->houdiniFrame); + if (val.IsEmpty()) return false; + if (val.IsHolding()) { + frame = static_cast(val.UncheckedGet()); + return true; + } + if (val.IsHolding()) { + frame = val.UncheckedGet(); + return true; + } + if (val.IsHolding()) { + frame = static_cast(val.UncheckedGet()); + return true; + } + return false; +} + +bool +RenderSettings::getHoudiniFps(double& fps) const +{ + VtValue val = mDelegate.GetRenderSetting(Tokens->houdiniFps); + if (val.IsEmpty()) return false; + if (val.IsHolding()) { + fps = val.UncheckedGet(); + return true; + } + if (val.IsHolding()) { + fps = static_cast(val.UncheckedGet()); + return true; + } + if (val.IsHolding()) { + fps = static_cast(val.UncheckedGet()); + return true; + } + return false; +} + void RenderSettings::setDeepIdAttributeName(){ TfToken key = TfToken("moonray:sceneVariable:deep_id_attribute_names"); diff --git a/lib/hydramoonray/RenderSettings.h b/lib/hydramoonray/RenderSettings.h index 2968263..70fed90 100644 --- a/lib/hydramoonray/RenderSettings.h +++ b/lib/hydramoonray/RenderSettings.h @@ -37,6 +37,8 @@ class RenderSettings } std::string getExecutionMode() const; + bool getHoudiniFrame(float& frame) const; + bool getHoudiniFps(double& fps) const; void setDeepIdAttributeName(); private: diff --git a/lib/hydramoonray/ValueConverter.cc b/lib/hydramoonray/ValueConverter.cc index 03357e9..04e48a4 100644 --- a/lib/hydramoonray/ValueConverter.cc +++ b/lib/hydramoonray/ValueConverter.cc @@ -6,7 +6,10 @@ #include #include +#include +#include #include +#include #include @@ -53,6 +56,88 @@ static bool _narrowIntChecked(SceneObject* sceneObj, return true; } +static std::string _normalizedToken(const std::string& value) +{ + std::string result = value; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { + if (c == '-' || c == ' ') return '_'; + return static_cast(std::tolower(c)); + }); + return result; +} + +static bool _rampInterpolationTokenToInt(const std::string& token, Int* out) +{ + const std::string key = _normalizedToken(token); + + // MoonRay ramp interpolation enums are documented in the generated + // scene class metadata for ramp IntVector attributes: + // None: 0 | Linear: 1 | Exponential Up: 2 | Exponential Down: 3 | + // Smooth: 4 | Catmull Rom: 5 | Monotone Cubic: 6 + if (key == "none" || key == "constant") { + *out = 0; + return true; + } + if (key == "linear") { + *out = 1; + return true; + } + if (key == "exponential_up" || key == "ease_in") { + *out = 2; + return true; + } + if (key == "exponential_down" || key == "ease_out") { + *out = 3; + return true; + } + // Houdini ramp menus can author Hermite, Bezier, and B-spline tokens. + // MoonRay's native ramp metadata has one smooth/cubic interpolation slot, + // so those Houdini spline-style bases map to Smooth. + if (key == "smooth" || key == "smoothstep" || key == "hermite" || + key == "bezier" || key == "bspline" || key == "b_spline") { + *out = 4; + return true; + } + if (key == "catmull_rom" || key == "catmullrom") { + *out = 5; + return true; + } + if (key == "monotone_cubic" || key == "monotonecubic") { + *out = 6; + return true; + } + return false; +} + +static bool _setRampInterpolationVectorFromToken(SceneObject* sceneObj, + const Attribute* attribute, + const std::string& token) +{ + const std::string attrName = attribute->getName(); + if (attrName.find("interpolation") == std::string::npos) { + return false; + } + + Int interpolation = 0; + if (!_rampInterpolationTokenToInt(token, &interpolation)) { + Logger::warn(sceneObj->getName(), '.', attrName, + ": unsupported ramp interpolation token '", token, + "' for IntVector attribute; keeping the MoonRay default"); + return true; + } + + IntVector values = attribute->getDefaultValue(); + if (values.empty()) { + values.push_back(interpolation); + } else { + std::fill(values.begin(), values.end(), interpolation); + } + sceneObj->set(AttributeKey(*attribute), values); + _clearBinding(sceneObj, attribute); + return true; +} + template static void _set(SceneObject* sceneObj, const Attribute* attribute, const T& value) { sceneObj->set(AttributeKey(*attribute), value); @@ -123,6 +208,7 @@ template struct Converter, pxr::VtArray> { static std::vector _(const pxr::VtArray& v) { + if (v.empty()) return {}; const T* p = &(RefConverter::_(v[0])); return std::vector(p, p + v.size()); } @@ -133,6 +219,7 @@ template<> struct Converter> { static BoolVector _(const pxr::VtArray& v) { + if (v.empty()) return {}; return BoolVector(&v[0], &v[0] + v.size()); } }; @@ -295,6 +382,22 @@ ValueConverter::setAttribute(SceneObject* sceneObj, const Attribute* attribute, break; case TYPE_INT_VECTOR: if (_setAttribute>(sceneObj, attribute, val)) return; + if (val.IsHolding()) { + const std::string token = val.UncheckedGet().GetString(); + if (_setRampInterpolationVectorFromToken(sceneObj, attribute, token)) return; + Logger::warn(sceneObj->getName(), '.', attribute->getName(), + ": unsupported scalar token value '", token, + "' for IntVector attribute; keeping the MoonRay default"); + return; + } + if (val.IsHolding()) { + const std::string token = val.UncheckedGet(); + if (_setRampInterpolationVectorFromToken(sceneObj, attribute, token)) return; + Logger::warn(sceneObj->getName(), '.', attribute->getName(), + ": unsupported scalar string value '", token, + "' for IntVector attribute; keeping the MoonRay default"); + return; + } break; case TYPE_LONG_VECTOR: if (_setAttribute>(sceneObj, attribute, val)) return; diff --git a/package.py b/package.py index 908a1f5..14bb0a8 100755 --- a/package.py +++ b/package.py @@ -85,7 +85,7 @@ def version(): 'usd_imaging-0.22.5.x.4', 'openimageio-2.3.20.0.x', 'opt_level-optdebug', - 'python-3.9' + 'python-3.11' ], ] @@ -120,7 +120,7 @@ def version(): # }, "rats-opt-debug": { "command": "rats -a --rco=2 --nohtml --rac --maxConcurrentTests=10", - "requires": ["rats", "opt_level-optdebug", "usd_core_dwa_plugin", "moonshine_usd", "usd_imaging-0.22.5", "moonshine_dwa", "houdini_dwa-19", "python-3.9", "gcc", "refplat-vfx2022"] + "requires": ["rats", "opt_level-optdebug", "usd_core_dwa_plugin", "moonshine_usd", "usd_imaging-0.22.5", "moonshine_dwa", "houdini_dwa-19", "python-3.11", "gcc", "refplat-vfx2022"] } } diff --git a/plugin/adapters/CMakeLists.txt b/plugin/adapters/CMakeLists.txt index 5765232..bf4d1db 100644 --- a/plugin/adapters/CMakeLists.txt +++ b/plugin/adapters/CMakeLists.txt @@ -24,6 +24,27 @@ target_sources(${component} ${DwaSpecificSources} ) +# Dirty compatibility workaround: +# Houdini 20.5 ships USD 24.03 headers that fail to compile +# through usdImaging lightAdapter/lightFilterAdapter under +# Xcode 26 / AppleClang 21 because pxr/usd/sdf/childrenProxy.h +# contains an invalid _ValueProxy::operator= path that calls +# SdfChildrenProxy::_Set(), which does not exist. +# +# This shim is target-local to hdMoonrayAdapters only. +# It does not represent a MoonRay integration fix, a USD update, +# or a translation layer. It only quarantines a compiler/header +# compatibility problem so the existing MoonRay mesh-light and +# light-filter adapter registration path can still be built and +# runtime-tested. +# +# Remove this workaround when building against a Houdini/USD/toolchain +# combination where the usdImaging adapter headers compile normally. +target_include_directories(${component} + BEFORE PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/compat/houdini20_5_usd24_xcode26 +) + target_include_directories(${component} PUBLIC $ @@ -80,6 +101,12 @@ install(TARGETS ${component} ) # install main plugInfo +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../plugInfo.json + COMPONENT ${component} + DESTINATION plugin/pxr +) + INSTALL(FILES ${plugInfoFile} + COMPONENT ${component} DESTINATION plugin/pxr/${component} ) diff --git a/plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h b/plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h new file mode 100644 index 0000000..25966e2 --- /dev/null +++ b/plugin/adapters/compat/houdini20_5_usd24_xcode26/pxr/usd/sdf/childrenProxy.h @@ -0,0 +1,543 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#ifndef PXR_USD_SDF_CHILDREN_PROXY_H +#define PXR_USD_SDF_CHILDREN_PROXY_H + +/// \file sdf/childrenProxy.h + +// Dirty compatibility workaround: +// Houdini 20.5 ships USD 24.03 headers that fail to compile +// through usdImaging lightAdapter/lightFilterAdapter under +// Xcode 26 / AppleClang 21 because pxr/usd/sdf/childrenProxy.h +// contains an invalid _ValueProxy::operator= path that calls +// SdfChildrenProxy::_Set(), which does not exist. +// +// This shim is target-local to hdMoonrayAdapters only. +// It does not represent a MoonRay integration fix, a USD update, +// or a translation layer. It only quarantines a compiler/header +// compatibility problem so the existing MoonRay mesh-light and +// light-filter adapter registration path can still be built and +// runtime-tested. +// +// Remove this workaround when building against a Houdini/USD/toolchain +// combination where the usdImaging adapter headers compile normally. + +#include "pxr/pxr.h" +#include "pxr/usd/sdf/api.h" +#include "pxr/usd/sdf/changeBlock.h" +#include "pxr/base/vt/value.h" +#include "pxr/base/tf/diagnostic.h" +#include "pxr/base/tf/iterator.h" + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +template +class SdfChildrenProxy { +public: + typedef _View View; + typedef typename View::Adapter Adapter; + typedef typename View::ChildPolicy ChildPolicy; + typedef typename View::key_type key_type; + typedef typename View::value_type mapped_type; + typedef std::vector mapped_vector_type; + typedef std::pair value_type; + typedef std::map map_type; + typedef typename View::size_type size_type; + typedef SdfChildrenProxy This; + +private: + typedef typename View::const_iterator _inner_iterator; + + class _ValueProxy { + public: + _ValueProxy() : _owner(NULL) { } + _ValueProxy(This* owner, _inner_iterator i) : _owner(owner), _pos(i) + { + // Do nothing + } + + operator mapped_type() const + { + return *_pos; + } + + template + _ValueProxy& operator=(const U& x) + { + _owner->erase(_owner->_view.key(*_pos)); + _owner->insert(mapped_type(x)); + return *this; + } + + bool operator==(const mapped_type& other) const + { + return *_pos == other; + } + + private: + This* _owner; + _inner_iterator _pos; + }; + + class _PairProxy { + public: + explicit _PairProxy(This* owner, _inner_iterator i) : + first(owner->_view.key(i)), second(owner, i) { } + + const key_type first; + _ValueProxy second; + + operator value_type() const + { + return value_type(first, second); + } + }; + friend class _PairProxy; + + class _Traits { + public: + static value_type Dereference(const This* owner, _inner_iterator i) + { + return value_type(owner->_view.key(i), *i); + } + + static _PairProxy Dereference(This* owner, _inner_iterator i) + { + return _PairProxy(owner, i); + } + }; + + template + class _Iterator { + class _PtrProxy { + public: + _Value* operator->() { return &_value; } + private: + friend class _Iterator; + explicit _PtrProxy(const _Value& value) : _value(value) {} + _Value _value; + }; + public: + static_assert(!std::is_reference<_Value>::value && + !std::is_pointer<_Value>::value, + "_Value cannot be a pointer or reference type."); + using iterator_category = std::bidirectional_iterator_tag; + using value_type = _Value; + using reference = _Value; + using pointer = _PtrProxy; + using difference_type = std::ptrdiff_t; + + _Iterator() = default; + _Iterator(_Owner owner, _inner_iterator i) : _owner(owner), _pos(i) { } + template + _Iterator(const _Iterator& other) : + _owner(other._owner), _pos(other._pos) { } + + reference operator*() const { return dereference(); } + pointer operator->() const { return pointer(dereference()); } + + _Iterator& operator++() { + increment(); + return *this; + } + + _Iterator& operator--() { + decrement(); + return *this; + } + + _Iterator operator++(int) { + _Iterator result(*this); + increment(); + return result; + } + + _Iterator operator--(int) { + _Iterator result(*this); + decrement(); + return result; + } + + template + bool operator==(const _Iterator& other) const { + return equal(other); + } + + template + bool operator!=(const _Iterator& other) const { + return !equal(other); + } + + private: + _Value dereference() const + { + return _Traits::Dereference(_owner, _pos); + } + + template + bool equal(const _Iterator& other) const + { + return _pos == other._pos; + } + + void increment() { + ++_pos; + } + + void decrement() { + --_pos; + } + + private: + _Owner _owner; + _inner_iterator _pos; + + template + friend class _Iterator; + }; + +public: + typedef _ValueProxy reference; + typedef _Iterator iterator; + typedef Tf_ProxyReferenceReverseIterator reverse_iterator; + typedef _Iterator const_iterator; + typedef Tf_ProxyReferenceReverseIterator const_reverse_iterator; + + static const int CanSet = 1; + static const int CanInsert = 2; + static const int CanErase = 4; + + SdfChildrenProxy(const View& view, const std::string& type, + int permission = CanSet | CanInsert | CanErase) : + _view(view), _type(type), _permission(permission) + { + // Do nothing + } + + template + SdfChildrenProxy(const SdfChildrenProxy& other) : + _view(other._view), _type(other._type), _permission(other._permission) + { + // Do nothing + } + + This& operator=(const This& other) + { + if (other._Validate()) { + _Copy(other._view.values()); + } + return *this; + } + + template + This& operator=(const SdfChildrenProxy& other) + { + if (other._Validate()) { + _Copy(other._view.values()); + } + return *this; + } + + This& operator=(const mapped_vector_type& values) + { + _Copy(values); + return *this; + } + + operator mapped_vector_type() const + { + return _Validate() ? _view.values() : mapped_vector_type(); + } + + map_type items() const + { + return _Validate() ? _view.template items_as() :map_type(); + } + + iterator begin() + { + return iterator(_GetThis(), _view.begin()); + } + iterator end() + { + return iterator(_GetThis(), _view.end()); + } + const_iterator begin() const + { + return const_iterator(_GetThis(), _view.begin()); + } + const_iterator end() const + { + return const_iterator(_GetThis(), _view.end()); + } + + reverse_iterator rbegin() + { + return reverse_iterator(end()); + } + reverse_iterator rend() + { + return reverse_iterator(begin()); + } + const_reverse_iterator rbegin() const + { + return reverse_iterator(end()); + } + const_reverse_iterator rend() const + { + return reverse_iterator(begin()); + } + + size_type size() const + { + return _Validate() ? _view.size() : 0; + } + + size_type max_size() const + { + return _view.max_size(); + } + + bool empty() const + { + return _Validate() ? _view.empty() : true; + } + + std::pair insert(const mapped_type& value) + { + if (_Validate(CanInsert)) { + iterator i = find(_view.key(value)); + if (i == end()) { + if (_PrimInsert(value, size())) { + return std::make_pair(find(_view.key(value)), true); + } + else { + return std::make_pair(end(), false); + } + } + else { + return std::make_pair(i, false); + } + } + else { + return std::make_pair(iterator(), false); + } + } + + iterator insert(iterator pos, const mapped_type& value) + { + return insert(value).first; + } + + template + void insert(InputIterator first, InputIterator last) + { + if (_Validate(CanInsert)) { + SdfChangeBlock block; + for (; first != last; ++first) { + _PrimInsert(*first, size()); + } + } + } + + void erase(iterator pos) + { + _Erase(pos->first); + } + + size_type erase(const key_type& key) + { + return _Erase(key) ? 1 : 0; + } + + void erase(iterator first, iterator last) + { + if (_Validate(CanErase)) { + SdfChangeBlock block; + while (first != last) { + const key_type& key = first->first; + ++first; + _PrimErase(key); + } + } + } + + void clear() + { + _Copy(mapped_vector_type()); + } + + iterator find(const key_type& key) + { + return _Validate() ? iterator(this, _view.find(key)) : iterator(); + } + + const_iterator find(const key_type& key) const + { + return _Validate() ? const_iterator(this, _view.find(key)) : + const_iterator(); + } + + size_type count(const key_type& key) const + { + return _Validate() ? _view.count(key) : 0; + } + + bool operator==(const This& other) const + { + return _view == other._view; + } + + bool operator!=(const This& other) const + { + return !(*this == other); + } + + /// Explicit bool conversion operator. The proxy object converts to + /// \c true if it is valid, \c false otherwise. + explicit operator bool() const + { + return _view.IsValid(); + } + +private: + const std::string& _GetType() const + { + return _type; + } + + int _GetPermission() const + { + return _permission; + } + + This* _GetThis() + { + return _Validate() ? this : NULL; + } + + const This* _GetThis() const + { + return _Validate() ? this : NULL; + } + + bool _Validate() const + { + if (_view.IsValid()) { + return true; + } + else { + TF_CODING_ERROR("Accessing expired %s", _type.c_str()); + return false; + } + } + + bool _Validate(int permission) + { + if (!_Validate()) { + return false; + } + if ((_permission & permission) == permission) { + return true; + } + const char* op = "edit"; + if (~_permission & permission & CanSet) { + op = "replace"; + } + else if (~_permission & permission & CanInsert) { + op = "insert"; + } + else if (~_permission & permission & CanErase) { + op = "remove"; + } + TF_CODING_ERROR("Cannot %s %s", op, _type.c_str()); + return false; + } + + bool _Copy(const mapped_vector_type& values) + { + return _Validate(CanSet) ? _PrimCopy(values) : false; + } + + bool _Insert(const mapped_type& value, size_t index) + { + return _Validate(CanInsert) ? _PrimInsert(value, index) : false; + } + + bool _Erase(const key_type& key) + { + return _Validate(CanErase) ? _PrimErase(key) : false; + } + + bool _PrimCopy(const mapped_vector_type& values) + { + typedef std::vector + ChildrenValueVector; + + ChildrenValueVector v; + for (size_t i = 0; i < values.size(); ++i) + v.push_back(Adapter::Convert(values[i])); + + return _view.GetChildren().Copy(v, _type); + } + + bool _PrimInsert(const mapped_type& value, size_t index) + { + return _view.GetChildren().Insert( + Adapter::Convert(value), index, _type); + } + + bool _PrimErase(const key_type& key) + { + return _view.GetChildren().Erase(key, _type); + } + +private: + View _view; + std::string _type; + int _permission; + + template friend class SdfChildrenProxy; + template friend class SdfPyChildrenProxy; +}; + +// Allow TfIteration over children proxies. +template +struct Tf_ShouldIterateOverCopy > : std::true_type +{ +}; + +// Cannot get from a VtValue except as the correct type. +template +struct Vt_DefaultValueFactory > { + static Vt_DefaultValueHolder Invoke() = delete; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // PXR_USD_SDF_CHILDREN_PROXY_H diff --git a/plugin/houdini/UsdRenderers.json b/plugin/houdini/UsdRenderers.json index 140ae30..2018204 100644 --- a/plugin/houdini/UsdRenderers.json +++ b/plugin/houdini/UsdRenderers.json @@ -4,15 +4,29 @@ "menulabel" : "Moonray", "needsselection" : true, "depthstyle": "linear", + "aovsupport" : false, + "diskproducttypes" : [ "raster", "deepRaster" ], "defaultpurposes" : [ "render" ], - "complexitymultiplier" : 1.173 + "complexitymultiplier" : 1.173, + "statsdatapaths" : { + "percentDone" : "percentDone", + "totalClockTime" : "totalClockTime", + "renderProgressAnnotation" : "renderProgressAnnotation" + } }, "HdMoonrayRendererDebugPlugin" : { "valid" : true, "menulabel" : "Moonray (Debug)", "needsselection" : true, "depthstyle": "linear", + "aovsupport" : false, + "diskproducttypes" : [ "raster", "deepRaster" ], "defaultpurposes" : [ "render" ], - "complexitymultiplier" : 1.173 + "complexitymultiplier" : 1.173, + "statsdatapaths" : { + "percentDone" : "percentDone", + "totalClockTime" : "totalClockTime", + "renderProgressAnnotation" : "renderProgressAnnotation" + } } } diff --git a/testSuite/README.md b/testSuite/README.md index 280a463..7d480b3 100644 --- a/testSuite/README.md +++ b/testSuite/README.md @@ -26,7 +26,7 @@ Build hdMoonRay variant 6, e.g.: rez-env buildtools cd hdmoonray rez-build -i --variants 6 - # os-CentOS-7 opt_level-optdebug usd_core_dwa_plugin moonshine_usd usd_imaging-0.22.5 moonshine_dwa houdini_dwa-19 python-3.9 gcc refplat-vfx2022 + # os-CentOS-7 opt_level-optdebug usd_core_dwa_plugin moonshine_usd usd_imaging-0.22.5 moonshine_dwa houdini_dwa-19 python-3.11 gcc refplat-vfx2022 ### Running @@ -38,7 +38,7 @@ is one. To run the RaTS tests, use the following rez-env: rez2 - rez-env hdMoonray rats opt_level-optdebug usd_core_dwa_plugin moonshine_usd usd_imaging-0.22.5 moonshine_dwa houdini_dwa-19 python-3.9 gcc refplat-vfx2022 + rez-env hdMoonray rats opt_level-optdebug usd_core_dwa_plugin moonshine_usd usd_imaging-0.22.5 moonshine_dwa houdini_dwa-19 python-3.11 gcc refplat-vfx2022 Then, use the normal RaTS commands **from the hdmoonray rez install dir**, e.g.: diff --git a/testSuite/aov/render_var_source_metadata/scene.usd b/testSuite/aov/render_var_source_metadata/scene.usd new file mode 100644 index 0000000..15a9e6a --- /dev/null +++ b/testSuite/aov/render_var_source_metadata/scene.usd @@ -0,0 +1,79 @@ +#usda 1.0 +( + startTimeCode = 1 + endTimeCode = 1 +) + +def Camera "camera" +{ + float2 clippingRange = (0.01, 10000) + float focalLength = 50 + float horizontalAperture = 36 + float verticalAperture = 20.25 + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] +} + +def Mesh "card" +{ + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + point3f[] points = [(-1, -1, 0), (1, -1, 0), (-1, 1, 0), (1, 1, 0)] + color3f[] primvars:displayColor = [(1, 1, 1)] ( + interpolation = "constant" + ) + float2[] primvars:st = [(0, 0), (1, 0), (0, 1), (1, 1)] ( + interpolation = "vertex" + ) +} + +def DomeLight "dome" +{ + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 1 +} + +def Scope "Render" +{ + def Scope "Products" + { + def Scope "Vars" + { + def RenderVar "st" + { + token dataType = "float2" + string sourceName = "st" + token sourceType = "primvar" + } + + def RenderVar "beauty" + { + token dataType = "color3f" + string sourceName = "beauty" + token sourceType = "raw" + } + } + + def RenderProduct "product" + { + rel orderedVars = [ + , + , + ] + token productName = "/tmp/moonray_render_var_source_metadata.exr" + token productType = "raster" + } + } + + def RenderSettings "settings" + { + rel camera = + rel products = + int2 resolution = (256, 144) + custom int moonray:sceneVariable:pixel_samples = 2 + custom int moonray:sceneVariable:max_depth = 0 + } +} diff --git a/testSuite/aov/render_var_source_metadata/test.xml b/testSuite/aov/render_var_source_metadata/test.xml new file mode 100644 index 0000000..a4754a7 --- /dev/null +++ b/testSuite/aov/render_var_source_metadata/test.xml @@ -0,0 +1,17 @@ + + + + Houdini-authored RenderVar sourceName/sourceType metadata should bind MoonRay AOVs. + + + + husk + -s /Render/settings --make-output-path -o ${result} ${args} -R HdMoonrayRendererPlugin --purpose geometry,render --complexity high --no-mplay --disable-delegate-products -V 9 ${shot_dir}/scene.usd + + + ${oiiotool_path}oiiotool + ${shot_tmp_dir}/${result} ${shot_tmp_dir}/${canonical} -a --warn ${error_threshold} --fail ${error_threshold} --failpercent .01 --hardfail 0.02 --diff --absdiff -o ${shot_tmp_dir}/${diff} + + + + diff --git a/testSuite/light/adapter_runtime/README.md b/testSuite/light/adapter_runtime/README.md new file mode 100644 index 0000000..a6c26ea --- /dev/null +++ b/testSuite/light/adapter_runtime/README.md @@ -0,0 +1,247 @@ +# Houdini 20.5 Adapter Runtime Fixture + +This fixture validates only the `hdMoonrayAdapters` runtime translation path. +It does not validate AOVs, ROPs, resolution handling, geometry settings, or +general Solaris UI authoring. + +Fixture: + +```text +/Applications/MoonRay/openmoonray/moonray/hydra/hdMoonray/testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda +``` + +## Runtime Paths Under Test + +### MoonrayMeshLight + +USD input: + +```usda +def MoonrayMeshLight "meshLight" +{ + uniform token moonray:class = "MeshLight" + rel inputs:geometry = +} +``` + +Expected path: + +```text +USD MoonrayMeshLight prim +-> MoonrayMeshLightAdapter::Populate() +-> UsdImagingIndexProxy::InsertSprim("geometryLight", ...) +-> hdMoonray RenderDelegate::CreateSprim("geometryLight", ...) +-> hdMoonray Light::syncParams() +-> sceneDelegate->Get(id, "inputs:geometry") +-> MoonrayMeshLightAdapter::Get() returns SdfPath +-> Mesh::geometryForMeshLight() +-> native MoonRay MeshLight.geometry +``` + +Source files: + +```text +plugin/adapters/MoonrayMeshLightAdapter.cc +lib/hydramoonray/RenderDelegate.cc +lib/hydramoonray/Light.cc +lib/hydramoonray/Mesh.cc +``` + +### MoonrayLightFilter projector relationship + +USD input: + +```usda +def MoonrayLightFilter "cookie" +{ + uniform token moonray:class = "CookieLightFilter" + rel moonray:projector = +} +``` + +Expected path: + +```text +USD MoonrayLightFilter prim +-> MoonrayLightFilterAdapter registration for primTypeName "MoonrayLightFilter" +-> Hydra lightFilter sprim +-> hdMoonray RenderDelegate::CreateSprim(lightFilter, ...) +-> hdMoonray LightFilter::syncProjector() +-> sceneDelegate->Get(id, "moonray:projector") +-> MoonrayLightFilterAdapter::Get() returns SdfPath +-> Camera::createCamera() +-> native MoonRay CookieLightFilter.projector +``` + +Source files: + +```text +plugin/adapters/MoonrayLightFilterAdapter.cc +lib/hydramoonray/RenderDelegate.cc +lib/hydramoonray/LightFilter.cc +lib/hydramoonray/Camera.cc +``` + +### MoonrayLightFilter combine relationship + +USD input: + +```usda +def MoonrayLightFilter "combine" +{ + uniform token moonray:class = "CombineLightFilter" + rel moonray:light_filters = [, ] +} +``` + +Expected path: + +```text +USD MoonrayLightFilter prim +-> MoonrayLightFilterAdapter registration for primTypeName "MoonrayLightFilter" +-> Hydra lightFilter sprim +-> hdMoonray LightFilter::syncCombineFilters() +-> sceneDelegate->Get(id, "moonray:light_filters") +-> MoonrayLightFilterAdapter::Get() returns SdfPathVector +-> LightFilter::getFilter() for each target +-> native MoonRay CombineLightFilter.light_filters +``` + +Source files: + +```text +plugin/adapters/MoonrayLightFilterAdapter.cc +lib/hydramoonray/LightFilter.cc +``` + +### Light to filter-set relationship + +USD input: + +```usda +def DiskLight "keyLight" +{ + rel light:filters = [] +} +``` + +Expected path: + +```text +USD light:filters relationship +-> Hydra HdTokens->filters +-> hdMoonray Light::syncFilterList() +-> LightFilter::getFilter() +-> native MoonRay Light.light_filters +-> RenderDelegate category assignment +-> native MoonRay LightFilterSet creation when linked geometry assignment is evaluated +``` + +Source files: + +```text +lib/hydramoonray/Light.cc +lib/hydramoonray/LightFilter.cc +lib/hydramoonray/RenderDelegate.cc +``` + +## Preflight Plugin Discovery + +Run before `husk`: + +```zsh +source /Applications/MoonRay/openmoonray/scripts/macOS/setupHoudini.sh +PYTHONPATH=/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources/houdini/python3.11libs:$PYTHONPATH \ + /Applications/Houdini/Houdini20.5.684/Frameworks/Python.framework/Versions/3.11/bin/python3.11 - <<'PY' +import os +from pxr import Plug +reg = Plug.Registry() +print(reg.RegisterPlugins(os.environ["PXR_PLUGINPATH_NAME"])) +plugin = reg.GetPluginWithName("hdMoonrayAdapters") +print(plugin.name if plugin else None) +print(plugin.path if plugin else None) +print(plugin.resourcePath if plugin else None) +PY +``` + +Pass criteria: + +```text +name=hdMoonrayAdapters +path=/Applications/MoonRay/installs/openmoonray/plugin/hdMoonrayAdapters.dylib +resourcePath=/Applications/MoonRay/installs/openmoonray/plugin/pxr/hdMoonrayAdapters/ +``` + +## Husk Command + +```zsh +source /Applications/MoonRay/openmoonray/scripts/macOS/setupHoudini.sh + +export HDM_LOG_FILE=/tmp/moonray_h20_adapter_fixture.hdm.log +export HDMOONRAY_RDLA_OUTPUT=/tmp/moonray_h20_adapter_fixture.rdla + +/Applications/Houdini/Houdini20.5.684/Frameworks/Houdini.framework/Versions/Current/Resources/bin/husk \ + -V4 \ + -R HdMoonrayRendererPlugin \ + -f 1 \ + /Applications/MoonRay/openmoonray/moonray/hydra/hdMoonray/testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda +``` + +## Inspection Points + +`HDM_LOG_FILE` should contain sync entries for the adapter-provided sprims: + +```text +SyncStart Mesh /World/emitterMesh +SyncStart Light /World/meshLight +SyncStart LightFilter /World/cookie +SyncStart LightFilter /World/rod +SyncStart LightFilter /World/combine +SyncStart Light /World/keyLight +``` + +The RDL dump should contain native MoonRay objects and relationships: + +```zsh +grep -nE 'MeshLight|CookieLightFilter|RodLightFilter|CombineLightFilter|LightFilterSet|geometry|projector|light_filters' \ + /tmp/moonray_h20_adapter_fixture.rdla +``` + +Expected evidence: + +- `MeshLight` object for `/World/meshLight`. +- `MeshLight.geometry` points at the RDL geometry generated from + `/World/emitterMesh`. +- `CookieLightFilter` object for `/World/cookie`. +- `CookieLightFilter.projector` points at the camera generated from + `/World/cookieProjector`. +- `CombineLightFilter` object for `/World/combine`. +- `CombineLightFilter.light_filters` includes the cookie and rod filters. +- `DiskLight`/`SpotLight` for `/World/keyLight` references the combined filter + through native MoonRay light-filter data. + +## Pass Criteria + +Pass: + +- `husk` can load `HdMoonrayRendererPlugin` and `hdMoonrayAdapters`. +- No unsupported prim-type warning for `MoonrayMeshLight`. +- No unsupported prim-type warning for `MoonrayLightFilter`. +- `HDM_LOG_FILE` shows `Light` sync for `/World/meshLight`. +- `HDM_LOG_FILE` shows `LightFilter` sync for `/World/cookie`, + `/World/rod`, and `/World/combine`. +- RDL output contains `MeshLight`, `CookieLightFilter`, `RodLightFilter`, and + `CombineLightFilter`. +- RDL output proves the `geometry`, `projector`, and `light_filters` + relationships were populated with scene objects rather than left empty. + +Fail: + +- `MoonrayMeshLight` does not populate as a `geometryLight` sprim. +- `MoonrayLightFilter` does not populate as a `lightFilter` sprim. +- `Light::syncParams()` logs missing geometry for `/World/meshLight`. +- `LightFilter::syncProjector()` logs `moonray:projector: must be a path` or + `not found`. +- `LightFilter::syncCombineFilters()` logs `moonray:light_filters: must be a + list of paths` or target filters not found. +- RDL output lacks the native MoonRay objects or relationship attributes. diff --git a/testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda b/testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda new file mode 100644 index 0000000..d536834 --- /dev/null +++ b/testSuite/light/adapter_runtime/moonray_h20_adapter_fixture.usda @@ -0,0 +1,116 @@ +#usda 1.0 +( + defaultPrim = "World" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 2.5, 9, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + float focalLength = 35 + float horizontalAperture = 20.955 + float verticalAperture = 15.2908 + float2 clippingRange = (0.01, 1000) + } + + def Mesh "receiver" + { + float3[] extent = [(-3, 0, -3), (3, 0, 3)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + point3f[] points = [(-3, 0, 3), (3, 0, 3), (-3, 0, -3), (3, 0, -3)] + color3f[] primvars:displayColor = [(0.8, 0.8, 0.8)] ( + interpolation = "constant" + ) + } + + def Mesh "emitterMesh" + { + matrix4d xformOp:transform = ( (1.2, 0, 0, 0), + (0, 1.2, 0, 0), + (0, 0, 1.2, 0), + (0, 3, -1.5, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + float3[] extent = [(-0.5, -0.5, 0), (0.5, 0.5, 0)] + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + point3f[] points = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (0.5, 0.5, 0)] + color3f[] primvars:displayColor = [(1, 0.8, 0.5)] ( + interpolation = "constant" + ) + } + + def MoonrayMeshLight "meshLight" + { + uniform token moonray:class = "MeshLight" + rel inputs:geometry = + float inputs:intensity = 1.5 + float intensity = 1.5 + token moonray:visible_in_camera = "force on" + } + + def Camera "cookieProjector" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 3, 2, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + float focalLength = 50 + float horizontalAperture = 20.955 + float verticalAperture = 15.2908 + } + + def MoonrayLightFilter "cookie" + { + uniform token moonray:class = "CookieLightFilter" + rel moonray:projector = + } + + def MoonrayLightFilter "rod" + { + uniform token moonray:class = "RodLightFilter" + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0.75, 2.5, -1, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + float moonray:depth = 2 + float moonray:height = 2 + float moonray:radius = 0.25 + float moonray:width = 1 + } + + def MoonrayLightFilter "combine" + { + uniform token moonray:class = "CombineLightFilter" + int moonray:mode = 0 + rel moonray:light_filters = [, ] + } + + def DiskLight "keyLight" + { + uniform token moonray:class = "SpotLight" + matrix4d xformOp:transform = ( (0, 0, 1, 0), + (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 4, 3, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + float inputs:intensity = 0.5 + float inputs:shaping:cone:angle = 35 + bool inputs:normalize = true + rel light:filters = [] + } +} diff --git a/testSuite/light/env_schema_v1/scene.usd b/testSuite/light/env_schema_v1/scene.usd new file mode 100644 index 0000000..afc526f --- /dev/null +++ b/testSuite/light/env_schema_v1/scene.usd @@ -0,0 +1,31 @@ +#usda 1.0 + +def Camera "camera" +{ + float focalLength = 50 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 10000) + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 1, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] +} + +def Mesh "card" +{ + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 3, 2] + point3f[] points = [(-1, -1, 0), (1, -1, 0), (-1, 1, 0), (1, 1, 0)] + color3f[] primvars:displayColor = [(0.8, 0.8, 0.8)] ( + interpolation = "constant" + ) +} + +def DomeLight_1 "dome" +{ + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 1 + token inputs:visibility = "inherited" +} diff --git a/testSuite/light/env_schema_v1/test.xml b/testSuite/light/env_schema_v1/test.xml new file mode 100644 index 0000000..d521110 --- /dev/null +++ b/testSuite/light/env_schema_v1/test.xml @@ -0,0 +1,17 @@ + + + + Houdini USD 24 DomeLight_1 schema alias should be accepted as a MoonRay EnvLight. + + + + hd_render + -in ${shot_dir}/scene.usd -out ${result} ${args} -res ${res} -renderer Moonray -camera camera -size 256 144 -set 'moonray:sceneVariable:pixel_samples' 2 -set 'moonray:sceneVariable:max_depth' 0 + + + ${oiiotool_path}oiiotool + ${shot_tmp_dir}/${result} ${shot_tmp_dir}/${canonical} -a --warn ${error_threshold} --fail ${error_threshold} --failpercent .01 --hardfail 0.1 --diff --absdiff -o ${shot_tmp_dir}/${diff} + + + + diff --git a/testSuite/runtime/h20_isolation/assets/dome_texture.ppm b/testSuite/runtime/h20_isolation/assets/dome_texture.ppm new file mode 100644 index 0000000..da4c5b0 --- /dev/null +++ b/testSuite/runtime/h20_isolation/assets/dome_texture.ppm @@ -0,0 +1,5 @@ +P3 +4 2 +255 +255 110 45 255 210 80 60 110 255 25 35 120 +255 110 45 255 210 80 60 110 255 25 35 120 diff --git a/testSuite/runtime/h20_isolation/bare_sphere_no_material.usda b/testSuite/runtime/h20_isolation/bare_sphere_no_material.usda new file mode 100644 index 0000000..c010f96 --- /dev/null +++ b/testSuite/runtime/h20_isolation/bare_sphere_no_material.usda @@ -0,0 +1,73 @@ +#usda 1.0 +( + defaultPrim = "World" + upAxis = "Y" +) + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.2, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Sphere "sphere" + { + double radius = 1 + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + } + + def RectLight "key" + { + matrix4d xformOp:transform = ( (0.707107, 0, 0.707107, 0), + (0.353553, 0.866025, -0.353553, 0), + (-0.612372, 0.5, 0.612372, 0), + (-2.5, 4, 3, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + color3f inputs:color = (1, 0.95, 0.85) + float inputs:intensity = 350 + bool inputs:normalize = true + float inputs:width = 2 + float inputs:height = 2 + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "bare_sphere_no_material.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} diff --git a/testSuite/runtime/h20_isolation/bare_sphere_simple_material.usda b/testSuite/runtime/h20_isolation/bare_sphere_simple_material.usda new file mode 100644 index 0000000..dc141dd --- /dev/null +++ b/testSuite/runtime/h20_isolation/bare_sphere_simple_material.usda @@ -0,0 +1,84 @@ +#usda 1.0 +( + defaultPrim = "World" + upAxis = "Y" +) + +def Material "simple_material" +{ + token outputs:surface.connect = + token outputs:moonray:surface.connect = + + def Shader "preview" + { + uniform token info:id = "UsdPreviewSurface" + token outputs:surface + color3f inputs:diffuseColor = (0.8, 0.22, 0.08) + float inputs:roughness = 0.45 + float inputs:metallic = 0 + } +} + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.2, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Sphere "sphere" + { + double radius = 1 + rel material:binding = + } + + def RectLight "key" + { + matrix4d xformOp:transform = ( (0.707107, 0, 0.707107, 0), + (0.353553, 0.866025, -0.353553, 0), + (-0.612372, 0.5, 0.612372, 0), + (-2.5, 4, 3, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + color3f inputs:color = (1, 0.95, 0.85) + float inputs:intensity = 350 + bool inputs:normalize = true + float inputs:width = 2 + float inputs:height = 2 + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "bare_sphere_simple_material.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} diff --git a/testSuite/runtime/h20_isolation/dome_constant_no_texture.usda b/testSuite/runtime/h20_isolation/dome_constant_no_texture.usda new file mode 100644 index 0000000..89e319a --- /dev/null +++ b/testSuite/runtime/h20_isolation/dome_constant_no_texture.usda @@ -0,0 +1,77 @@ +#usda 1.0 +( + defaultPrim = "World" + upAxis = "Y" +) + +def Material "simple_material" +{ + token outputs:surface.connect = + token outputs:moonray:surface.connect = + + def Shader "preview" + { + uniform token info:id = "UsdPreviewSurface" + token outputs:surface + color3f inputs:diffuseColor = (0.55, 0.58, 0.62) + float inputs:roughness = 0.6 + } +} + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.2, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Sphere "sphere" + { + double radius = 1 + rel material:binding = + } + + def DomeLight_1 "dome" + { + color3f inputs:color = (0.35, 0.5, 1) + float inputs:intensity = 3 + float inputs:exposure = 0 + token inputs:visibility = "inherited" + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "dome_constant_no_texture.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} diff --git a/testSuite/runtime/h20_isolation/dome_texture.usda b/testSuite/runtime/h20_isolation/dome_texture.usda new file mode 100644 index 0000000..582eb50 --- /dev/null +++ b/testSuite/runtime/h20_isolation/dome_texture.usda @@ -0,0 +1,78 @@ +#usda 1.0 +( + defaultPrim = "World" + upAxis = "Y" +) + +def Material "simple_material" +{ + token outputs:surface.connect = + token outputs:moonray:surface.connect = + + def Shader "preview" + { + uniform token info:id = "UsdPreviewSurface" + token outputs:surface + color3f inputs:diffuseColor = (0.55, 0.58, 0.62) + float inputs:roughness = 0.6 + } +} + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.2, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Sphere "sphere" + { + double radius = 1 + rel material:binding = + } + + def DomeLight_1 "dome" + { + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 2 + asset inputs:texture:file = @assets/dome_texture.ppm@ + token inputs:texture:format = "latlong" + token inputs:visibility = "inherited" + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "dome_texture.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} diff --git a/testSuite/runtime/h20_isolation/dwa_material_no_dome.usda b/testSuite/runtime/h20_isolation/dwa_material_no_dome.usda new file mode 100644 index 0000000..5caf1a1 --- /dev/null +++ b/testSuite/runtime/h20_isolation/dwa_material_no_dome.usda @@ -0,0 +1,83 @@ +#usda 1.0 +( + defaultPrim = "World" + upAxis = "Y" +) + +def Material "dwa_material" +{ + color4f outputs:moonray:surface.connect = + + def Shader "dwa" + { + uniform token info:id = "DwaBaseMaterial" + color4f outputs:surface + color3f inputs:albedo = (0.35, 0.62, 0.9) + float inputs:roughness = 0.5 + float inputs:specular = 0.25 + } +} + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.2, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Sphere "sphere" + { + double radius = 1 + rel material:binding = + } + + def RectLight "key" + { + matrix4d xformOp:transform = ( (0.707107, 0, 0.707107, 0), + (0.353553, 0.866025, -0.353553, 0), + (-0.612372, 0.5, 0.612372, 0), + (-2.5, 4, 3, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + color3f inputs:color = (1, 0.95, 0.85) + float inputs:intensity = 350 + bool inputs:normalize = true + float inputs:width = 2 + float inputs:height = 2 + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "dwa_material_no_dome.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} diff --git a/testSuite/runtime/h20_isolation/generate_houdini_domelight_lop_fixture.py b/testSuite/runtime/h20_isolation/generate_houdini_domelight_lop_fixture.py new file mode 100644 index 0000000..dffcd70 --- /dev/null +++ b/testSuite/runtime/h20_isolation/generate_houdini_domelight_lop_fixture.py @@ -0,0 +1,141 @@ +"""Generate Houdini-authored Dome Light LOP fixtures. + +Run with Houdini's hython after sourcing the Houdini/OpenMoonRay environment. +The script intentionally creates the real Houdini `domelight::3.0` LOP and +does not edit its hidden `primtype` parameter. +""" + +from __future__ import print_function + +import json +import os +import sys + +import hou +from pxr import Usd + + +OUT_DIR = "/Applications/MoonRay/runtime-fixtures/houdini_ui_dome" +TEXTURE_PATH = ( + "/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray/" + "testSuite/runtime/h20_isolation/assets/dome_texture.ppm" +) + + +def _set_if_exists(node, parm_name, value): + parm = node.parm(parm_name) + if parm is not None: + parm.set(value) + return True + return False + + +def _build_network(name, texture=False): + stage = hou.node("/stage") + if stage is None: + stage = hou.node("/").createNode("lopnet", "stage") + + for child in stage.children(): + child.destroy() + + camera = stage.createNode("camera", "camera1") + camera.parm("primpath").set("/cameras/camera1") + camera.parmTuple("t").set((0, 1.2, 6)) + camera.parmTuple("r").set((-15, 0, 0)) + camera.parm("focalLength").set(45) + camera.parm("horizontalAperture").set(36) + camera.parm("verticalAperture").set(20.25) + + sphere = stage.createNode("sphere", "sphere1") + sphere.setInput(0, camera) + sphere.parm("primpath").set("/World/sphere") + sphere.parm("radius").set(1) + + dome = stage.createNode("domelight::3.0", "domelight1") + dome.setInput(0, sphere) + dome.parm("primpath").set("/lights/domelight1") + before_primtype = dome.parm("primtype").eval() + dome.parm("xn__inputsintensity_i0a").set(3 if not texture else 2) + dome.parmTuple("xn__inputscolor_zta").set((0.35, 0.5, 1.0) if not texture else (1, 1, 1)) + if texture: + dome.parm("xn__inputstexturefile_r3ah").set(TEXTURE_PATH) + dome.parm("xn__inputstextureformat_06ah").set("latlong") + after_primtype = dome.parm("primtype").eval() + + var = stage.createNode("rendervar", "color") + var.setInput(0, dome) + var.parm("primpath").set("/Render/Products/Vars/color") + var.parm("dataType").set("color4f") + var.parm("sourceName").set("color") + var.parm("sourceType").set("raw") + var.parm("xn__driverparametersaovname_jebkd").set("color") + + product = stage.createNode("renderproduct", "product") + product.setInput(0, var) + product.parm("primpath").set("/Render/Products/product") + product.parm("orderedVars").set("/Render/Products/Vars/color") + product.parm("productName").set(os.path.join(OUT_DIR, name + ".exr")) + product.parm("productType").set("raster") + + settings = stage.createNode("rendersettings", "settings") + settings.setInput(0, product) + settings.parm("primpath").set("/Render/RenderSettings") + settings.parm("products").set("/Render/Products/product") + settings.parm("camera").set("/cameras/camera1") + settings.setDisplayFlag(True) + + return settings, { + "node_type": dome.type().nameWithCategory(), + "node_path": dome.path(), + "primtype_before": before_primtype, + "primtype_after": after_primtype, + "texture": texture, + "texture_path": TEXTURE_PATH if texture else "", + } + + +def _export_and_inspect(settings, name, info): + os.makedirs(OUT_DIR, exist_ok=True) + usd_path = os.path.join(OUT_DIR, name + ".usda") + stage = settings.stage() + stage.Flatten().Export(usd_path) + + exported = Usd.Stage.Open(usd_path) + light = exported.GetPrimAtPath("/lights/domelight1") + info.update({ + "usd_path": usd_path, + "prim_path": str(light.GetPath()) if light else "", + "prim_type": light.GetTypeName() if light else "", + "attributes": {}, + }) + if light: + for attr_name in [ + "inputs:intensity", + "inputs:exposure", + "inputs:color", + "inputs:texture:file", + "inputs:texture:format", + "texture:file", + ]: + attr = light.GetAttribute(attr_name) + if attr: + info["attributes"][attr_name] = str(attr.Get()) + + info_path = os.path.join(OUT_DIR, name + ".json") + with open(info_path, "w") as out: + json.dump(info, out, indent=2, sort_keys=True) + print(json.dumps(info, indent=2, sort_keys=True)) + return usd_path + + +def main(): + for name, texture in [ + ("houdini_domelight_constant", False), + ("houdini_domelight_texture", True), + ]: + settings, info = _build_network(name, texture) + _export_and_inspect(settings, name, info) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/testSuite/runtime/h20_isolation/generate_moonray_material_builder_fixture.py b/testSuite/runtime/h20_isolation/generate_moonray_material_builder_fixture.py new file mode 100644 index 0000000..5cf3124 --- /dev/null +++ b/testSuite/runtime/h20_isolation/generate_moonray_material_builder_fixture.py @@ -0,0 +1,181 @@ +#!/usr/bin/env hython + +"""Generate a minimal Houdini-authored MoonRay material builder fixture. + +This intentionally creates the installed MoonRay Material Builder operator +inside a Solaris Material Library LOP, exports the authored MoonRay USD material +network, and then adds a tiny sphere/light/camera scene around it for husk. +""" + +from __future__ import annotations + +import argparse +import os + +import hou +import moonray_material_builder +from pxr import Gf, Sdf, Usd, UsdGeom, UsdLux, UsdRender, UsdShade + + +BUILDER_TOOL = "moonraymaterialbuilder" + + +def _reset_stage(): + stage = hou.node("/stage") or hou.node("/").createNode("lopnet", "stage") + for child in stage.children(): + child.destroy() + return stage + + +def _set_if_present(node, parm_name, value): + parm = node.parm(parm_name) + if parm is None: + raise RuntimeError(f"Missing expected parm {node.path()}.{parm_name}") + parm.set(value) + + +def _enable_iridescence_fixture(builder): + dwa = builder.node("dwa_base") + _set_if_present(dwa, "iridescence", 1.0) + _set_if_present(dwa, "iridescence_color_control", "use ramp") + _set_if_present(dwa, "iridescence_ramp_interpolation_mode", "RGB") + + # Houdini's ramp menu exports tokens such as hermite/catmull-rom; hdMoonray + # must convert them to MoonRay's native IntVector ramp interpolation enums. + ramp = hou.Ramp( + ( + hou.rampBasis.Hermite, + hou.rampBasis.CatmullRom, + hou.rampBasis.MonotoneCubic, + hou.rampBasis.Bezier, + hou.rampBasis.BSpline, + hou.rampBasis.Linear, + hou.rampBasis.Constant, + ), + (0.0, 0.167, 0.333, 0.5, 0.667, 0.833, 1.0), + ( + (1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, 1.0, 1.0), + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (1.0, 0.0, 0.0), + ), + ) + _set_if_present(dwa, "iridescence_ramp", ramp) + + +def _make_material_library(enable_iridescence=False): + stage = _reset_stage() + matlib = stage.createNode("materiallibrary", "materiallibrary1") + builder = moonray_material_builder.create_moonray_material_builder( + {"pane": None, "node": matlib}, "moonray_material" + ) + if not builder.isMaterialFlagSet(): + raise RuntimeError("MoonRay Material Builder was created without its material flag") + + if builder.node("dwa_base") is None: + raise RuntimeError("MoonRay Material Builder did not create default DwaBaseMaterial") + if builder.node("normal_displacement") is None: + raise RuntimeError("MoonRay Material Builder did not create default NormalDisplacement path") + + if enable_iridescence: + _enable_iridescence_fixture(builder) + + matlib.parm("genpreviewshaders").set(0) + matlib.parm("referencerendervars").set(0) + matlib.parm("matnode1").set(builder.name()) + matlib.parm("matpath1").set("/materials/moonray_material") + matlib.cook(force=True) + return matlib, builder + + +def _add_render_scene(stage): + UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y) + + sphere = UsdGeom.Sphere.Define(stage, "/world/sphere") + sphere.CreateRadiusAttr(1.5) + sphere.AddTranslateOp().Set(Gf.Vec3d(0.0, 0.0, 0.0)) + + material = UsdShade.Material.Get(stage, "/materials/moonray_material") + if not material: + raise RuntimeError("Exported stage does not contain /materials/moonray_material") + UsdShade.MaterialBindingAPI(sphere).Bind(material) + + light = UsdLux.RectLight.Define(stage, "/lights/key") + light.AddTransformOp().Set(Gf.Matrix4d( + 0.707107, 0, 0.707107, 0, + 0.353553, 0.866025, -0.353553, 0, + -0.612372, 0.5, 0.612372, 0, + -2.5, 4, 3, 1)) + light.CreateIntensityAttr(350.0) + light.CreateNormalizeAttr(True) + light.CreateWidthAttr(2.0) + light.CreateHeightAttr(2.0) + + camera = UsdGeom.Camera.Define(stage, "/camera") + camera.AddTransformOp().Set(Gf.Matrix4d( + 1, 0, 0, 0, + 0, 0.965926, -0.258819, 0, + 0, 0.258819, 0.965926, 0, + 0, 1.2, 6, 1)) + camera.CreateFocalLengthAttr(35.0) + stage.SetDefaultPrim(stage.GetPrimAtPath("/world")) + + settings = UsdRender.Settings.Define(stage, "/Render/RenderSettings") + settings.CreateResolutionAttr(Gf.Vec2i(64, 64)) + settings.CreateCameraRel().SetTargets([Sdf.Path("/camera")]) + product = UsdRender.Product.Define(stage, "/Render/Product") + product.CreateProductTypeAttr("raster") + product.CreateProductNameAttr("moonray_material_builder_basic.exr") + var = UsdRender.Var.Define(stage, "/Render/Vars/color") + var.CreateDataTypeAttr("color4f") + var.CreateSourceNameAttr("color") + var.CreateSourceTypeAttr("raw") + var.GetPrim().CreateAttribute( + "driver:parameters:aov:name", Sdf.ValueTypeNames.String).Set("color") + product.CreateOrderedVarsRel().SetTargets([var.GetPath()]) + settings.CreateProductsRel().SetTargets([product.GetPath()]) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--out", + default="/tmp/moonray_material_builder_basic.usda", + help="Output USDA path.", + ) + parser.add_argument( + "--hip", + default="", + help="Optional Houdini HIP file path to save for UI inspection.", + ) + parser.add_argument( + "--iridescence", + action="store_true", + help="Enable a DwaBaseMaterial iridescence ramp with non-default interpolation tokens.", + ) + args = parser.parse_args() + + matlib, builder = _make_material_library(args.iridescence) + usd_stage = Usd.Stage.Open(matlib.stage().Flatten()) + _add_render_scene(usd_stage) + usd_stage.Export(args.out) + + if args.hip: + os.makedirs(os.path.dirname(args.hip), exist_ok=True) + hou.hipFile.save(args.hip) + + print(f"MoonRay Material Builder tool: {BUILDER_TOOL}") + print(f"MoonRay Material Builder node type: {builder.type().name()}") + print(f"Builder path: {builder.path()}") + print(f"Builder children: {[child.type().name() for child in builder.children()]}") + print(f"Iridescence fixture: {args.iridescence}") + print(f"USD output: {args.out}") + if args.hip: + print(f"HIP output: {args.hip}") + + +if __name__ == "__main__": + main() From d85ec5554999c1fd9a209a1e15d3b0ef70407553 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sun, 31 May 2026 23:22:36 +0200 Subject: [PATCH 03/15] subd/tess-translation Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Mesh.cc | 47 ++++-- .../moonray_geometry_settings/README.md | 39 +++++ .../geometry_settings.usda | 134 ++++++++++++++++++ 3 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 testSuite/geometry/moonray_geometry_settings/README.md create mode 100644 testSuite/geometry/moonray_geometry_settings/geometry_settings.usda diff --git a/lib/hydramoonray/Mesh.cc b/lib/hydramoonray/Mesh.cc index 5360642..7272c9b 100644 --- a/lib/hydramoonray/Mesh.cc +++ b/lib/hydramoonray/Mesh.cc @@ -193,11 +193,19 @@ Mesh::syncSubdivScheme(const HdMeshTopology& topology, // sets the subdivision scheme, and related options // resolution, adaptive_error and smooth normals + static TfToken isSubdToken("moonray:is_subd"); + static TfToken subdSchemeToken("moonray:subd_scheme"); + static TfToken meshResolutionToken("moonray:mesh_resolution"); + static TfToken adaptiveErrorToken("moonray:adaptive_error"); + static TfToken smoothNormalToken("moonray:smooth_normal"); + TfToken subdScheme = topology.GetScheme(); int rdlScheme = rdlSubdSchemeCatClark; // moonray default if (subdScheme == PxOsdOpenSubdivTokens->bilinear) rdlScheme = rdlSubdSchemeBilinear; // scheme "loop" is not supported by Moonray, and replaced by "catClark" - geometry()->set(rdlAttrSubdScheme, rdlScheme); + if (not isPrimvarUsed(subdSchemeToken)) { + geometry()->set(rdlAttrSubdScheme, rdlScheme); + } // This is the "complexity" menu item in usdview (low=0, medium=1, ...) // There is a topology.GetRefineLevel() but it appears to always be a copy of @@ -214,15 +222,12 @@ Mesh::syncSubdivScheme(const HdMeshTopology& topology, renderDelegate.getForcePolygon() || subdScheme == PxOsdOpenSubdivTokens->none; - - geometry()->set(rdlAttrIsSubd, !disableSubd); + if (not isPrimvarUsed(isSubdToken)) { + geometry()->set(rdlAttrIsSubd, !disableSubd); + } // mesh resolution, adaptive error and smooth_normals can be overridden // by primvars, so check before overwriting - static TfToken meshResolutionToken("moonray:mesh_resolution"); - static TfToken adaptiveErrorToken("moonray:adaptive_error"); - static TfToken smoothNormalToken("moonray:smooth_normal"); - if (not isPrimvarUsed(meshResolutionToken)) { // to match Storm, use 1 << refineLevel for resolution float rdlResolution = 1 << refineLevel; @@ -246,12 +251,17 @@ Mesh::syncSubdivTags(const PxOsdSubdivTags& tags) { // sets RDL attrs from subdiv tags + static TfToken subdBoundaryToken("moonray:subd_boundary"); + static TfToken subdFvarLinearToken("moonray:subd_fvar_linear"); + TfToken t = tags.GetVertexInterpolationRule(); int rdlValue; if (t == PxOsdOpenSubdivTokens->none) rdlValue = rdlSubdBoundaryNone; else if (t == PxOsdOpenSubdivTokens->edgeOnly) rdlValue = rdlSubdBoundaryEdgeOnly; else rdlValue = rdlSubdBoundaryEdgeAndCorner; - geometry()->set(rdlAttrSubdBoundary, rdlValue); + if (not isPrimvarUsed(subdBoundaryToken)) { + geometry()->set(rdlAttrSubdBoundary, rdlValue); + } t = tags.GetFaceVaryingInterpolationRule(); if (t == PxOsdOpenSubdivTokens->none) rdlValue = rdlSubdFvarLinearNone; @@ -261,7 +271,9 @@ Mesh::syncSubdivTags(const PxOsdSubdivTags& tags) else if (t == PxOsdOpenSubdivTokens->boundaries) rdlValue = rdlSubdFvarLinearBoundaries; else if (t == PxOsdOpenSubdivTokens->all) rdlValue = rdlSubdFvarLinearAll; else rdlValue = rdlSubdFvarLinearCornersOnly; - geometry()->set(rdlAttrSubdFvarLinear, rdlValue); + if (not isPrimvarUsed(subdFvarLinearToken)) { + geometry()->set(rdlAttrSubdFvarLinear, rdlValue); + } VtIntArray vi = tags.GetCreaseIndices(); @@ -297,7 +309,11 @@ Mesh::syncSubdivTags(const PxOsdSubdivTags& tags) if (not vi.empty()) { geometry()->set(rdlAttrSubdCornerIndices, IntVector(&vi[0], &vi[0] + vi.size())); VtFloatArray vf = tags.GetCornerWeights(); - geometry()->set(rdlAttrSubdCornerSharpnesses, FloatVector(&vf[0], &vf[0] + vf.size())); + if (vf.empty()) { + Logger::warn("Skipping empty subdivision corner sharpnesses for ", GetId()); + } else { + geometry()->set(rdlAttrSubdCornerSharpnesses, FloatVector(&vf[0], &vf[0] + vf.size())); + } } else { // geometry()->resetToDefault("subd_corner_indices"); // geometry()->resetToDefault("subd_corner_sharpnesses"); @@ -323,6 +339,11 @@ Mesh::primvarChanged(HdSceneDelegate *sceneDelegate, RenderDelegate& renderDeleg geometry()->resetToDefault(rdlAttrNormalList); } else if (value.IsHolding()) { const VtVec3fArray& v = value.UncheckedGet(); + if (v.empty()) { + Logger::warn("Skipping empty mesh normal primvar '", name, "' on ", GetId()); + geometry()->resetToDefault(rdlAttrNormalList); + return; + } const Vec3f* p = reinterpret_cast(&v[0]); Vec3fVector out(p, p + v.size()); geometry()->set(rdlAttrNormalList, out); @@ -340,6 +361,11 @@ Mesh::primvarChanged(HdSceneDelegate *sceneDelegate, RenderDelegate& renderDeleg } } else if (value.IsHolding()) { const VtVec2fArray& v = value.UncheckedGet(); + if (v.empty()) { + Logger::warn("Skipping empty mesh texture-coordinate primvar '", name, "' on ", GetId()); + geometry()->resetToDefault(rdlAttrUvList); + return; + } const Vec2f* p = reinterpret_cast(&v[0]); out.assign(p, p + v.size()); } @@ -440,4 +466,3 @@ Mesh::Sync(HdSceneDelegate *sceneDelegate, } } - diff --git a/testSuite/geometry/moonray_geometry_settings/README.md b/testSuite/geometry/moonray_geometry_settings/README.md new file mode 100644 index 0000000..2390b8f --- /dev/null +++ b/testSuite/geometry/moonray_geometry_settings/README.md @@ -0,0 +1,39 @@ +# MoonRay Geometry Settings Fixture + +This fixture validates the Houdini/Solaris `primvars:moonray:*` geometry +settings path for live hdMoonray Hydra mesh rendering. + +The expected bridge is: + +`USD Mesh primvars:moonray:*` -> `Hydra primvars` -> `GeometryMixin` +attribute overrides -> `RdlMeshGeometry` attributes -> RDL/render output. + +Run after sourcing the Houdini and MoonRay environments: + +```zsh +export HDMOONRAY_RDLA_OUTPUT=/tmp/moonray_geometry_settings.rdla +husk -V4 -R HdMoonrayRendererPlugin -f 1 geometry_settings.usda +grep -n "adaptive_error\\|mesh_resolution\\|is_subd\\|subd_scheme\\|subd_boundary\\|subd_fvar_linear\\|smooth_normal\\|motion_blur_type" /tmp/moonray_geometry_settings.rdla +``` + +Expected RDL class: + +- `RdlMeshGeometry` + +Expected user-authored attributes on `/World/subd_quad`: + +- `subd_scheme = "bilinear"` +- `subd_boundary = "edge only"` +- `subd_fvar_linear = "all"` +- `mesh_resolution = 6` +- `adaptive_error = 0.5` +- `smooth_normal = false` +- `motion_blur_type = "static"` + +Expected user-authored attribute on `/World/forced_polygon_quad`: + +- `is_subd = false` + +Crease/corner arrays and topology arrays are intentionally not represented as +user-facing renderer settings here. hdMoonray receives them from USD mesh +topology/subdivision tags when present. diff --git a/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda b/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda new file mode 100644 index 0000000..0df8ce1 --- /dev/null +++ b/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda @@ -0,0 +1,134 @@ +#usda 1.0 +( + defaultPrim = "World" + metersPerUnit = 1 + upAxis = "Y" +) + +def Material "preview_material" +{ + token outputs:surface.connect = + token outputs:moonray:surface.connect = + + def Shader "preview" + { + uniform token info:id = "UsdPreviewSurface" + token outputs:surface + color3f inputs:diffuseColor = (0.45, 0.62, 0.85) + float inputs:roughness = 0.5 + } +} + +def Xform "World" +{ + def Camera "camera" + { + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 0.965926, -0.258819, 0), + (0, 0.258819, 0.965926, 0), + (0, 1.5, 6, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + float focalLength = 45 + float horizontalAperture = 36 + float verticalAperture = 20.25 + float2 clippingRange = (0.01, 1000) + } + + def Mesh "subd_quad" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 2, 3] + point3f[] points = [(-1.25, -1, 0), (1.25, -1, 0), (1.25, 1, 0), (-1.25, 1, 0)] + uniform token subdivisionScheme = "catmullClark" + rel material:binding = + + bool primvars:moonray:is_subd = true ( + interpolation = "constant" + ) + token primvars:moonray:subd_scheme = "bilinear" ( + interpolation = "constant" + ) + token primvars:moonray:subd_boundary = "edge only" ( + interpolation = "constant" + ) + token primvars:moonray:subd_fvar_linear = "all" ( + interpolation = "constant" + ) + float primvars:moonray:mesh_resolution = 6 ( + interpolation = "constant" + ) + float primvars:moonray:adaptive_error = 0.5 ( + interpolation = "constant" + ) + bool primvars:moonray:smooth_normal = false ( + interpolation = "constant" + ) + token primvars:moonray:motion_blur_type = "static" ( + interpolation = "constant" + ) + } + + def Mesh "forced_polygon_quad" ( + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + int[] faceVertexCounts = [4] + int[] faceVertexIndices = [0, 1, 2, 3] + point3f[] points = [(-1, -0.75, 0), (1, -0.75, 0), (1, 0.75, 0), (-1, 0.75, 0)] + uniform token subdivisionScheme = "catmullClark" + rel material:binding = + matrix4d xformOp:transform = ( (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, -1.9, 0, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + bool primvars:moonray:is_subd = false ( + interpolation = "constant" + ) + } + + def RectLight "key" + { + matrix4d xformOp:transform = ( (0.707107, 0, 0.707107, 0), + (0.353553, 0.866025, -0.353553, 0), + (-0.612372, 0.5, 0.612372, 0), + (-2.5, 4, 3, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + color3f inputs:color = (1, 0.95, 0.85) + float inputs:intensity = 350 + bool inputs:normalize = true + float inputs:width = 2 + float inputs:height = 2 + } +} + +def Scope "Render" +{ + def RenderSettings "RenderSettings" + { + rel camera = + rel products = + int2 resolution = (64, 64) + } + + def RenderProduct "Product" + { + token productType = "raster" + string productName = "moonray_geometry_settings.exr" + rel orderedVars = + } + + def Scope "Vars" + { + def RenderVar "color" + { + custom string driver:parameters:aov:name = "color" + token dataType = "color4f" + string sourceName = "color" + token sourceType = "raw" + } + } +} From 24bac7ea740ceb8e694a5c19bc0f625e9b375ca9 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:07:32 +0200 Subject: [PATCH 04/15] hdMoonray: fix SceneVariables float conversion Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/ValueConverter.cc | 50 ++++++++++++++----- .../moonray_geometry_settings/README.md | 11 +++- .../geometry_settings.usda | 12 +++++ 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/lib/hydramoonray/ValueConverter.cc b/lib/hydramoonray/ValueConverter.cc index 04e48a4..c31a47f 100644 --- a/lib/hydramoonray/ValueConverter.cc +++ b/lib/hydramoonray/ValueConverter.cc @@ -56,6 +56,23 @@ static bool _narrowIntChecked(SceneObject* sceneObj, return true; } +static bool _setFloatFromIntegral64(SceneObject* sceneObj, + const Attribute* attribute, + std::int64_t value) +{ + const long double fMin = -static_cast(std::numeric_limits::max()); + const long double fMax = static_cast(std::numeric_limits::max()); + const long double wideValue = static_cast(value); + if (wideValue < fMin || wideValue > fMax) { + Logger::error(sceneObj->getName(), '.', attribute->getName(), + ": integer value ", value, " out of Float range"); + return false; + } + sceneObj->set(AttributeKey(*attribute), static_cast(value)); + _clearBinding(sceneObj, attribute); + return true; +} + static std::string _normalizedToken(const std::string& value) { std::string result = value; @@ -313,20 +330,29 @@ ValueConverter::setAttribute(SceneObject* sceneObj, const Attribute* attribute, if (_setAttributeRef(sceneObj, attribute, val)) return; break; case TYPE_FLOAT: - if (val.IsHolding()) { - const float floatVal = static_cast(val.UncheckedGet()); - sceneObj->set(AttributeKey(*attribute), floatVal); - return; - } else if (val.IsHolding()) { - const float floatVal = static_cast(val.UncheckedGet()); - sceneObj->set(AttributeKey(*attribute), floatVal); + { + std::int64_t int64Val = 0; + if (_extractIntegral64(val, &int64Val)) { + _setFloatFromIntegral64(sceneObj, attribute, int64Val); + return; + } + } + if (val.IsHolding()) { + const unsigned long long ullVal = val.UncheckedGet(); + if (static_cast(ullVal) > + static_cast(std::numeric_limits::max())) { + Logger::error(sceneObj->getName(), '.', attribute->getName(), + ": unsigned integer value ", ullVal, " out of Float range"); + return; + } + sceneObj->set(AttributeKey(*attribute), static_cast(ullVal)); + _clearBinding(sceneObj, attribute); return; - } else { - if (_setAttributeRef(sceneObj, attribute, val)) return; - if (_setAttribute(sceneObj, attribute, val)) return; - // handle incorrect types in Input bindings - if (_setAttributeRef(sceneObj, attribute, val)) return; } + if (_setAttributeRef(sceneObj, attribute, val)) return; + if (_setAttribute(sceneObj, attribute, val)) return; + // handle incorrect types in Input bindings + if (_setAttributeRef(sceneObj, attribute, val)) return; break; case TYPE_DOUBLE: if (_setAttributeRef(sceneObj, attribute, val)) return; diff --git a/testSuite/geometry/moonray_geometry_settings/README.md b/testSuite/geometry/moonray_geometry_settings/README.md index 2390b8f..3f6de62 100644 --- a/testSuite/geometry/moonray_geometry_settings/README.md +++ b/testSuite/geometry/moonray_geometry_settings/README.md @@ -13,7 +13,7 @@ Run after sourcing the Houdini and MoonRay environments: ```zsh export HDMOONRAY_RDLA_OUTPUT=/tmp/moonray_geometry_settings.rdla husk -V4 -R HdMoonrayRendererPlugin -f 1 geometry_settings.usda -grep -n "adaptive_error\\|mesh_resolution\\|is_subd\\|subd_scheme\\|subd_boundary\\|subd_fvar_linear\\|smooth_normal\\|motion_blur_type" /tmp/moonray_geometry_settings.rdla +grep -n "adaptive_error\\|mesh_resolution\\|is_subd\\|subd_scheme\\|subd_boundary\\|subd_fvar_linear\\|smooth_normal\\|motion_blur_type\\|label\\|shadow_receiver_label\\|side_type\\|reverse_normals" /tmp/moonray_geometry_settings.rdla ``` Expected RDL class: @@ -29,6 +29,10 @@ Expected user-authored attributes on `/World/subd_quad`: - `adaptive_error = 0.5` - `smooth_normal = false` - `motion_blur_type = "static"` +- `label = "geometry_settings_subd"` +- `shadow_receiver_label = "geometry_settings_shadow"` +- `side_type = "force two-sided"` +- `reverse_normals = false` Expected user-authored attribute on `/World/forced_polygon_quad`: @@ -37,3 +41,8 @@ Expected user-authored attribute on `/World/forced_polygon_quad`: Crease/corner arrays and topology arrays are intentionally not represented as user-facing renderer settings here. hdMoonray receives them from USD mesh topology/subdivision tags when present. + +`motion_blur_type` is validated here as a correctly authored and consumed +RDL geometry attribute. Its visible render behavior also requires appropriate +velocity/acceleration or multi-sample position data plus enabled render motion +blur settings, so that behavior belongs in a separate motion blur fixture. diff --git a/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda b/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda index 0df8ce1..89a30bb 100644 --- a/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda +++ b/testSuite/geometry/moonray_geometry_settings/geometry_settings.usda @@ -68,6 +68,18 @@ def Xform "World" token primvars:moonray:motion_blur_type = "static" ( interpolation = "constant" ) + string primvars:moonray:label = "geometry_settings_subd" ( + interpolation = "constant" + ) + string primvars:moonray:shadow_receiver_label = "geometry_settings_shadow" ( + interpolation = "constant" + ) + token primvars:moonray:side_type = "force two-sided" ( + interpolation = "constant" + ) + bool primvars:moonray:reverse_normals = false ( + interpolation = "constant" + ) } def Mesh "forced_polygon_quad" ( From 65e457b20f8d7ad36a3c1c39990f0b305f312489 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:12:16 +0200 Subject: [PATCH 05/15] hdMoonray: translate USD shaped lights to SpotLight Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Light.cc | 89 ++++++++++++++++++++++++++++++--------- lib/hydramoonray/Light.h | 5 ++- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index af4c608..4cb6ea5 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include #include @@ -51,6 +54,31 @@ defaultRdlClassName(const pxr::TfToken& type) return it->second; } +bool +isMoonRayLightClass(const std::string& className, hdMoonray::RenderDelegate& renderDelegate) +{ + try { + const SceneClass* sceneClass = renderDelegate.sceneContext().getSceneClass(className); + return sceneClass && (sceneClass->getDeclaredInterface() & INTERFACE_LIGHT); + } catch (const scene_rdl2::except::KeyError&) { + return false; + } +} + +bool +isUsdShapedLight(const pxr::SdfPath& id, pxr::HdSceneDelegate *sceneDelegate) +{ + return sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle).IsHolding() || + sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeSoftness).IsHolding(); +} + +bool +canUseMoonRaySpotLightForType(const pxr::TfToken& type) +{ + return type == pxr::HdPrimTypeTokens->diskLight || + type == pxr::HdPrimTypeTokens->sphereLight; +} + } namespace hdMoonray { @@ -73,15 +101,33 @@ Light::GetInitialDirtyBitsMask() const const std::string& Light::rdlClassName(const pxr::SdfPath& id, - pxr::HdSceneDelegate *sceneDelegate) + pxr::HdSceneDelegate *sceneDelegate, + RenderDelegate& renderDelegate) { // identify the rdl light class to use. This can be specified via "token moonray::class =" or // deduced from the Lux/Usd type const std::string& luxRdlClass(defaultRdlClassName(mType)); + mRectToSpotlight = false; + pxr::VtValue v = sceneDelegate->GetLightParamValue(id, moonrayClassToken); bool isRectLight = false; if (v.IsHolding()) { pxr::TfToken classToken = v.UncheckedGet(); + const std::string& className = classToken.GetString(); + if (!isMoonRayLightClass(className, renderDelegate)) { + if (!luxRdlClass.empty()) { + const bool shapedSpot = isUsdShapedLight(id, sceneDelegate) && + canUseMoonRaySpotLightForType(mType); + const std::string& fallbackClass = shapedSpot ? spotLightToken.GetString() : luxRdlClass; + Logger::warn(id, ".moonray:class: invalid MoonRay light class '", classToken, + "'; falling back to USD light type '", mType, + "' as '", fallbackClass, "'"); + return fallbackClass; + } + Logger::error(id, ".moonray:class: invalid MoonRay light class '", classToken, + "' and no fallback exists for USD light type '", mType, "'"); + return luxRdlClass; + } if (classToken == rectLightToken) { isRectLight = true; } @@ -96,18 +142,19 @@ Light::rdlClassName(const pxr::SdfPath& id, Logger::warn(id, ".moonray:class: '", classToken, "' may not be compatible with USD light type '", mType, "'"); } - return classToken.GetString(); + mRectToSpotlight = isRectLight && isUsdShapedLight(id, sceneDelegate); + return className; } - // existence of shaping api makes a SpotLight - v = sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle); - if (v.IsHolding() && v.UncheckedGet() < 90.0f) { - if (mType != pxr::HdPrimTypeTokens->diskLight) { + // Houdini/Solaris authors spotlights as a UsdLux SphereLight or DiskLight + // with UsdLuxShapingAPI cone attributes, not as a separate USD SpotLight + // prim. The USD default cone angle of 90 degrees is still a valid authored + // shaped light; do not require angle < 90 to select MoonRay SpotLight. + if (isUsdShapedLight(id, sceneDelegate)) { + if (!canUseMoonRaySpotLightForType(mType)) { Logger::warn(id, ": shaping api may not be compatible with USD light type '", mType, "'"); + } else { + return spotLightToken.GetString(); } - if (isRectLight) { - mRectToSpotlight = true; - } - return spotLightToken.GetString(); } if (luxRdlClass.empty()) { Logger::error(id, ": Unsupported light type ", mType, " replaced by DiskLight"); @@ -124,7 +171,7 @@ Light::isSupportedType(const pxr::TfToken& type) void -Light::fixCylinderLight(scene_rdl2::rdl2::Mat4d& mat) { +Light::fixLightXform(scene_rdl2::rdl2::Mat4d& mat) { // in Usd/Lux cylinder is along x-axis, in moonray it is along y. if (mType == pxr::HdPrimTypeTokens->cylinderLight) { // rotate -90deg about z @@ -143,17 +190,17 @@ Light::syncXform(const pxr::SdfPath& id, if (sampledXforms.count <= 1) { scene_rdl2::rdl2::Mat4d rdl2Xform0 = reinterpret_cast(sampledXforms.values[0]); - fixCylinderLight(rdl2Xform0); + fixLightXform(rdl2Xform0); mLight->set(mLight->sNodeXformKey, rdl2Xform0); } else { // first and last samples will be sample interval boundaries scene_rdl2::rdl2::Mat4d rdl2Xform0 = reinterpret_cast(sampledXforms.values[0]); - fixCylinderLight(rdl2Xform0); + fixLightXform(rdl2Xform0); mLight->set(mLight->sNodeXformKey, rdl2Xform0); scene_rdl2::rdl2::Mat4d rdl2Xform1 = reinterpret_cast(sampledXforms.values[sampledXforms.count-1]); - fixCylinderLight(rdl2Xform1); + fixLightXform(rdl2Xform1); mLight->set(mLight->sNodeXformKey, rdl2Xform1, scene_rdl2::rdl2::TIMESTEP_END); } } @@ -269,6 +316,9 @@ Light::syncParams(const pxr::SdfPath& id, float coneAngle = 90; // Lux default value val = sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle); if (val.IsHolding()) coneAngle = val.UncheckedGet(); + coneAngle = scene_rdl2::math::clamp(coneAngle, 0.0f, 180.0f); + // USD shaping:cone:angle is an off-axis half angle; MoonRay + // SpotLight outer_cone_angle is the full side-to-side apex. mLight->set(AttributeKey(**it), 2 * coneAngle); continue; @@ -276,13 +326,14 @@ Light::syncParams(const pxr::SdfPath& id, float softness = 0; // Lux default value val = sceneDelegate->GetLightParamValue(id, luxName); if (val.IsHolding()) softness = val.UncheckedGet(); + softness = scene_rdl2::math::clamp(softness, 0.0f, 1.0f); float coneAngle = 90; // Lux default value val = sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle); if (val.IsHolding()) coneAngle = val.UncheckedGet(); - float innerConeAngle = coneAngle; - if (softness > 0) { - innerConeAngle = (softness < 1) ? coneAngle * (1 - softness) : 0.0f; - } + coneAngle = scene_rdl2::math::clamp(coneAngle, 0.0f, 180.0f); + // USD softness is the fraction of non-cutoff angles used for + // falloff. MoonRay inner_cone_angle is the full bright apex. + const float innerConeAngle = coneAngle * (1.0f - softness); mLight->set(AttributeKey(**it), 2 * innerConeAngle); continue; @@ -385,7 +436,7 @@ Light::Sync(pxr::HdSceneDelegate *sceneDelegate, if (not (intensity > 0)) return; // don't create invisible lights // currently if the class changes, Finalize() is called, so there is no need to check // for this after the object is created. - const std::string& rdlClass = rdlClassName(id, sceneDelegate); + const std::string& rdlClass = rdlClassName(id, sceneDelegate, renderDelegate); scene_rdl2::rdl2::SceneObject* object = renderDelegate.createSceneObject(rdlClass, id); mLight = object ? object->asA() : nullptr; if (not mLight) return; // if there was an error this already printed an error message diff --git a/lib/hydramoonray/Light.h b/lib/hydramoonray/Light.h index cb57003..feaf376 100644 --- a/lib/hydramoonray/Light.h +++ b/lib/hydramoonray/Light.h @@ -36,7 +36,8 @@ class Light: public pxr::HdLight private: const std::string& rdlClassName(const pxr::SdfPath& id, - pxr::HdSceneDelegate *sceneDelegate); + pxr::HdSceneDelegate *sceneDelegate, + RenderDelegate& renderDelegate); void syncXform(const pxr::SdfPath& id, pxr::HdSceneDelegate *sceneDelegate); @@ -47,7 +48,7 @@ class Light: public pxr::HdLight pxr::HdSceneDelegate *sceneDelegate, RenderDelegate& renderDelegate); - void fixCylinderLight(scene_rdl2::rdl2::Mat4d& mat); + void fixLightXform(scene_rdl2::rdl2::Mat4d& mat); pxr::TfToken mType; From 439643916acd3082ac9aefe349661360035c0a1a Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:27:40 +0200 Subject: [PATCH 06/15] hdMoonray: map USD RectLight shaping to spread Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Light.cc | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index 4cb6ea5..8570b9d 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -79,6 +79,13 @@ canUseMoonRaySpotLightForType(const pxr::TfToken& type) type == pxr::HdPrimTypeTokens->sphereLight; } +bool +canUseMoonRayShapingForType(const pxr::TfToken& type) +{ + return canUseMoonRaySpotLightForType(type) || + type == pxr::HdPrimTypeTokens->rectLight; +} + } namespace hdMoonray { @@ -149,10 +156,14 @@ Light::rdlClassName(const pxr::SdfPath& id, // with UsdLuxShapingAPI cone attributes, not as a separate USD SpotLight // prim. The USD default cone angle of 90 degrees is still a valid authored // shaped light; do not require angle < 90 to select MoonRay SpotLight. + // + // RectLight also supports UsdLuxShapingAPI. Keep it as a MoonRay RectLight + // and translate cone angle to the native RectLight spread attribute below + // instead of converting the rectangular emitter to a SpotLight. if (isUsdShapedLight(id, sceneDelegate)) { - if (!canUseMoonRaySpotLightForType(mType)) { + if (!canUseMoonRayShapingForType(mType)) { Logger::warn(id, ": shaping api may not be compatible with USD light type '", mType, "'"); - } else { + } else if (canUseMoonRaySpotLightForType(mType)) { return spotLightToken.GetString(); } } @@ -305,6 +316,7 @@ Light::syncParams(const pxr::SdfPath& id, { "angular_extent", pxr::HdLightTokens->angle }, { "texture", pxr::HdLightTokens->textureFile }, { "lens_radius", pxr::HdLightTokens->radius }, + { "spread", pxr::HdLightTokens->shapingConeAngle }, { "outer_cone_angle", pxr::HdLightTokens->shapingConeAngle }, { "inner_cone_angle", pxr::HdLightTokens->shapingConeSoftness } }; @@ -312,7 +324,17 @@ Light::syncParams(const pxr::SdfPath& id, if (it2 != map.end()) { pxr::TfToken luxName = it2->second; - if (luxName == pxr::HdLightTokens->shapingConeAngle) { + if (attrName == "spread") { + float coneAngle = 90; // USD default value + val = sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle); + if (val.IsHolding()) coneAngle = val.UncheckedGet(); + // MoonRay RectLight spread is the normalized cone angle: + // 1 is a diffuse 90-degree cone and 0 is parallel emission. + const float spread = scene_rdl2::math::clamp(coneAngle, 0.0f, 90.0f) / 90.0f; + mLight->set(AttributeKey(**it), spread); + continue; + + } else if (luxName == pxr::HdLightTokens->shapingConeAngle) { float coneAngle = 90; // Lux default value val = sceneDelegate->GetLightParamValue(id, pxr::HdLightTokens->shapingConeAngle); if (val.IsHolding()) coneAngle = val.UncheckedGet(); From 27201a47dae82a7df1eb4588dafa2c4c046a6623 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:07:20 +0200 Subject: [PATCH 07/15] hdMoonray: checkpoint working SpotLight overrides Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Light.cc | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index 8570b9d..89c5f65 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -58,9 +58,9 @@ bool isMoonRayLightClass(const std::string& className, hdMoonray::RenderDelegate& renderDelegate) { try { - const SceneClass* sceneClass = renderDelegate.sceneContext().getSceneClass(className); + const SceneClass* sceneClass = renderDelegate.acquireSceneContext().createSceneClass(className); return sceneClass && (sceneClass->getDeclaredInterface() & INTERFACE_LIGHT); - } catch (const scene_rdl2::except::KeyError&) { + } catch (const std::exception&) { return false; } } @@ -453,12 +453,29 @@ Light::Sync(pxr::HdSceneDelegate *sceneDelegate, } bool initialize = false; + std::string rdlClass; + if (mLight && ((*dirtyBits) & pxr::HdLight::DirtyParams)) { + rdlClass = rdlClassName(id, sceneDelegate, renderDelegate); + if (rdlClass != mLight->getSceneClass().getName()) { + { + UpdateGuard guard(renderDelegate, mLight); + setOn(false, renderDelegate); + } + renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::LightLink, mLightLinkCategory); + renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::ShadowLink, mShadowLinkCategory); + mLight = nullptr; + mLightLinkCategory = pxr::TfToken(); + mShadowLinkCategory = pxr::TfToken(); + *dirtyBits = pxr::HdLight::AllDirty; + } + } + if (not mLight) { *dirtyBits = DirtyBits::Clean; // don't call Sync again if no light is created if (not (intensity > 0)) return; // don't create invisible lights - // currently if the class changes, Finalize() is called, so there is no need to check - // for this after the object is created. - const std::string& rdlClass = rdlClassName(id, sceneDelegate, renderDelegate); + if (rdlClass.empty()) { + rdlClass = rdlClassName(id, sceneDelegate, renderDelegate); + } scene_rdl2::rdl2::SceneObject* object = renderDelegate.createSceneObject(rdlClass, id); mLight = object ? object->asA() : nullptr; if (not mLight) return; // if there was an error this already printed an error message From f5331a867994df5792557130892e14a744a3c00b Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:21:15 +0200 Subject: [PATCH 08/15] hdMoonray: share light class swap cleanup Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Light.cc | 38 +++++++++++++++++++++----------------- lib/hydramoonray/Light.h | 1 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index 89c5f65..f917c16 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -229,6 +229,25 @@ Light::setOn(bool value, RenderDelegate& renderDelegate) { } } +void +Light::resetLightObject(RenderDelegate& renderDelegate) +{ + if (!mLight) { + return; + } + + // RDL SceneObjects cannot be deleted from SceneContext during interactive + // updates. Match the geometry lifecycle: make the old object inert and let + // createSceneObject() reuse or class-suffix replacement objects as needed. + UpdateGuard guard(renderDelegate, mLight); + setOn(false, renderDelegate); + renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::LightLink, mLightLinkCategory); + renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::ShadowLink, mShadowLinkCategory); + mLight = nullptr; + mLightLinkCategory = pxr::TfToken(); + mShadowLinkCategory = pxr::TfToken(); +} + pxr::GfVec3f colorTemperatureToRGB(float kelvin) { @@ -457,15 +476,7 @@ Light::Sync(pxr::HdSceneDelegate *sceneDelegate, if (mLight && ((*dirtyBits) & pxr::HdLight::DirtyParams)) { rdlClass = rdlClassName(id, sceneDelegate, renderDelegate); if (rdlClass != mLight->getSceneClass().getName()) { - { - UpdateGuard guard(renderDelegate, mLight); - setOn(false, renderDelegate); - } - renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::LightLink, mLightLinkCategory); - renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::ShadowLink, mShadowLinkCategory); - mLight = nullptr; - mLightLinkCategory = pxr::TfToken(); - mShadowLinkCategory = pxr::TfToken(); + resetLightObject(renderDelegate); *dirtyBits = pxr::HdLight::AllDirty; } } @@ -540,14 +551,7 @@ Light::Sync(pxr::HdSceneDelegate *sceneDelegate, void Light::Finalize(pxr::HdRenderParam *renderParam) { - if (mLight) { - RenderDelegate& renderDelegate(RenderDelegate::get(renderParam)); - UpdateGuard guard(renderDelegate, mLight); - setOn(false, renderDelegate); - renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::LightLink, mLightLinkCategory); - renderDelegate.releaseCategory(mLight, RenderDelegate::CategoryType::ShadowLink, mShadowLinkCategory); - mLight = nullptr; - } + resetLightObject(RenderDelegate::get(renderParam)); } } diff --git a/lib/hydramoonray/Light.h b/lib/hydramoonray/Light.h index feaf376..b31972e 100644 --- a/lib/hydramoonray/Light.h +++ b/lib/hydramoonray/Light.h @@ -58,6 +58,7 @@ class Light: public pxr::HdLight scene_rdl2::rdl2::Light* mLight = nullptr; bool mOn = false; void setOn(bool, RenderDelegate& renderDelegate); + void resetLightObject(RenderDelegate& renderDelegate); // the name of a category holding all geometry lit by this light pxr::TfToken mLightLinkCategory; From 8d1ded419a12fd98a0ad4e8652bd74cda4c55910 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:11:21 +0200 Subject: [PATCH 09/15] hdMoonray: document unit scale policy gaps Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- docs/units-and-scale-notes.md | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/units-and-scale-notes.md diff --git a/docs/units-and-scale-notes.md b/docs/units-and-scale-notes.md new file mode 100644 index 0000000..084a5ce --- /dev/null +++ b/docs/units-and-scale-notes.md @@ -0,0 +1,101 @@ +# hdMoonRay Units and Scale Notes + +This note records the current Houdini/Solaris, USD, hdMoonRay, and MoonRay unit +behavior observed during the Houdini 20.5 Solaris integration work. It is a +future-work audit note only; it does not define a completed unit conversion +policy. + +## Current Audit Findings + +- Houdini Solaris defaults to `metersPerUnit = 1.0`. +- Changing Houdini `unitlength` changes the authored USD `metersPerUnit`. +- hdMoonRay currently does not read or apply USD `metersPerUnit`. +- Changing `metersPerUnit` alone produced identical RDL values and identical + EXR stats in the audit fixtures. +- hdMoonRay currently passes numeric USD/Hydra values to MoonRay as raw scene + units. +- USD `inputs:normalize` maps to MoonRay `normalized`. +- DwaBaseMaterial `scattering_radius` and similar SSS distance controls are + passed as raw numeric scene-unit values. + +## Separate Scale Concepts + +### USD Stage Units + +USD `metersPerUnit` is stage-level unit metadata. In the current hdMoonRay path, +it is not consumed. Changing only `metersPerUnit` does not currently alter the +MoonRay RDL values or render statistics produced by hdMoonRay. + +### Houdini Light Apply Scene Scale + +Houdini light UI includes a light-specific normalized scale option: + +- label: `Apply Scene Scale` +- parameter: `nonrayscene_scale` +- help: `Whether to apply scene scale while normalized.` + +User testing showed visible issues around normalized light behavior when this +option is enabled. This is separate from the USD `metersPerUnit` metadata audit +and is not fully mapped by hdMoonRay today. + +### MoonRay Normalized Lights + +MoonRay lights have a native `normalized` attribute. The installed MoonRay RDL +metadata describes this as surface-area/radiance normalization, allowing light +size to change without changing total emitted energy. + +The current hdMoonRay translation preserves authored light size attributes such +as radius, width, and height, and passes transform scale through `node_xform`. +The audit render probes were useful for confirming RDL values, but were not +reliable enough for final photometric claims because fixture framing, emitter +position, and emitter/object intersection can dominate brightness comparisons. + +### MoonRay Material Distance Controls + +DwaBaseMaterial `scattering_radius` is a physical distance-like material +control. hdMoonRay currently passes this and similar SSS distance values raw. +Object scale changes the geometry transform in RDL; it does not change material +distance attributes. USD `metersPerUnit` also does not currently affect these +values. + +## Known Limitation + +hdMoonRay currently has no explicit, renderer-wide unit policy. + +This means: + +- USD `metersPerUnit` is not consumed. +- Houdini `nonrayscene_scale` / `Apply Scene Scale` behavior for normalized + lights is not fully mapped. +- SSS and material distance parameters are passed raw. +- No global conversion layer exists for physical distance or size attributes. + +## Decision + +Do not patch this during the Render Settings or AOV foundation work. + +Any unit-policy change could broadly affect: + +- normalized light brightness +- area light size behavior +- SSS and material distance interpretation +- camera focus distances +- physical camera quantities +- existing scene compatibility + +This should be handled as a later dedicated unit-policy task with controlled +comparison fixtures, ideally comparing Houdini/Karma expectations against +hdMoonRay/MoonRay behavior. + +## Future Unit-Policy Test Matrix + +A later unit-policy task should test: + +- `metersPerUnit = 1.0` vs `0.01` +- Houdini `Apply Scene Scale` / `nonrayscene_scale` on and off +- USD `inputs:normalize` on and off +- object and light transform scale `1` vs `10` +- SphereLight, DiskLight, and RectLight +- SSS material distance behavior +- camera focus distance / depth-of-field distance, if relevant + From 2eb0808460a6fe429bae83c16fb4cd2b93bbe428 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:02:20 +0200 Subject: [PATCH 10/15] hdMoonray: enable USD Render ROP beauty buffer support Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- plugin/hd_moonray/ArrasRenderer.cc | 64 ++++++++++++++++++++++++++---- plugin/houdini/UsdRenderers.json | 4 +- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/plugin/hd_moonray/ArrasRenderer.cc b/plugin/hd_moonray/ArrasRenderer.cc index e93a542..fbf709f 100644 --- a/plugin/hd_moonray/ArrasRenderer.cc +++ b/plugin/hd_moonray/ArrasRenderer.cc @@ -7,7 +7,6 @@ #include #include - #include #include #include @@ -18,6 +17,28 @@ namespace hdMoonray { using scene_rdl2::logging::Logger; +namespace { + +std::string +leafName(const std::string& objectName) +{ + const std::string::size_type slash = objectName.find_last_of('/'); + if (slash == std::string::npos || slash + 1 >= objectName.size()) { + return objectName; + } + return objectName.substr(slash + 1); +} + +bool +namesMatch(const std::string& requested, const std::string& leaf, const std::string& available) +{ + return available == requested || + available == leaf || + requested == "/_outputs/" + available; +} + +} + ArrasRenderer::ArrasRenderer() { mSceneContext = new scene_rdl2::rdl2::SceneContext(); @@ -319,17 +340,46 @@ ArrasRenderer::resolve(scene_rdl2::rdl2::RenderOutput* ro, PixelData& pd) if (mProgress < 0) return false; // see if no change since last time - unsigned n = mFbReceiver->getFbActivityCounter(); - if (n == pd.filmActivity) return false; - pd.filmActivity = n; + const unsigned activity = mFbReceiver->getFbActivityCounter(); + const std::string roName = isBeauty(ro) ? std::string("beauty") : ro->getName(); + if (activity == pd.filmActivity) return false; if (isBeauty(ro)) { mFbReceiver->getBeautyMTSafe(pd.vec, pd.mWidth, pd.mHeight); pd.mChannels = 4; + pd.filmActivity = activity; } else { - unsigned n = mFbReceiver->getRenderOutputMTSafe(ro->getName(), pd.vec, pd.mWidth, pd.mHeight); - if (not n) return false; // ignore occasional bad data - pd.mChannels = n; + int matchedId = -1; + const unsigned totalOutputs = mFbReceiver->getTotalRenderOutput(); + const std::string leaf = leafName(roName); + for (unsigned id = 0; id < totalOutputs; ++id) { + const std::string& available = mFbReceiver->getRenderOutputName(id); + if (namesMatch(roName, leaf, available)) { + matchedId = static_cast(id); + break; + } + } + + int n = mFbReceiver->getRenderOutputMTSafe(roName, pd.vec, pd.mWidth, pd.mHeight); + + if (n <= 0) { + if (leaf != roName) { + n = mFbReceiver->getRenderOutputMTSafe(leaf, pd.vec, pd.mWidth, pd.mHeight); + } + + if (n <= 0) { + if (matchedId >= 0) { + n = mFbReceiver->getRenderOutputMTSafe( + static_cast(matchedId), pd.vec, pd.mWidth, pd.mHeight); + } + } + } + + if (n <= 0) { + return false; // ignore occasional bad data + } + pd.mChannels = static_cast(n); + pd.filmActivity = activity; } pd.mData = pd.vec.data(); diff --git a/plugin/houdini/UsdRenderers.json b/plugin/houdini/UsdRenderers.json index 2018204..5d24079 100644 --- a/plugin/houdini/UsdRenderers.json +++ b/plugin/houdini/UsdRenderers.json @@ -4,7 +4,7 @@ "menulabel" : "Moonray", "needsselection" : true, "depthstyle": "linear", - "aovsupport" : false, + "aovsupport" : true, "diskproducttypes" : [ "raster", "deepRaster" ], "defaultpurposes" : [ "render" ], "complexitymultiplier" : 1.173, @@ -19,7 +19,7 @@ "menulabel" : "Moonray (Debug)", "needsselection" : true, "depthstyle": "linear", - "aovsupport" : false, + "aovsupport" : true, "diskproducttypes" : [ "raster", "deepRaster" ], "defaultpurposes" : [ "render" ], "complexitymultiplier" : 1.173, From 688868eb16a63187273241ff09b328368e15dfc0 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:35:40 +0200 Subject: [PATCH 11/15] hdMoonray: document development workflow and stable feature patterns Document stable hdMoonray development patterns from materials, lights, geometry, and H20.5 Solaris infrastructure. Add stable vs WIP classification for Render Settings LOP, AOVs, unit policy, and beauty buffer work. Capture validation workflow for USD, RDLA/RDL, render output, Houdini UI, runtime provenance, and EXR stats. Keep AOV/cameraDepth documented as failure/process contrast. Documentation only. No source or runtime changes. Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- docs/hdMoonray_aov_audit.md | 504 ++++++++++++++++++ docs/hdMoonray_development_guide.md | 170 ++++++ ...nray_development_patterns_from_features.md | 270 ++++++++++ ...dMoonray_feature_implementation_pattern.md | 249 +++++++++ docs/hdMoonray_stable_vs_wip_matrix.md | 71 +++ docs/hdMoonray_validation_workflow.md | 213 ++++++++ 6 files changed, 1477 insertions(+) create mode 100644 docs/hdMoonray_aov_audit.md create mode 100644 docs/hdMoonray_development_guide.md create mode 100644 docs/hdMoonray_development_patterns_from_features.md create mode 100644 docs/hdMoonray_feature_implementation_pattern.md create mode 100644 docs/hdMoonray_stable_vs_wip_matrix.md create mode 100644 docs/hdMoonray_validation_workflow.md diff --git a/docs/hdMoonray_aov_audit.md b/docs/hdMoonray_aov_audit.md new file mode 100644 index 0000000..2ca822f --- /dev/null +++ b/docs/hdMoonray_aov_audit.md @@ -0,0 +1,504 @@ +# hdMoonray AOV Audit + +## Purpose + +This document tracks the current hdMoonray AOV support model and the H20.5 production cameraDepth investigation. + +MoonRay `RenderOutput` scene objects are the native MoonRay AOV mechanism. Solaris/USD `RenderVar` prims are the USD/Hydra request mechanism that asks hdMoonray for image buffers. hdMoonray bridges these two worlds by translating Hydra AOV bindings into MoonRay `RenderOutput` objects and then resolving the renderer payload back into Hydra render buffers. + +The current key blocker for non-beauty AOVs is production Arras/mcrt payload delivery. USD authoring, Hydra RenderVar parsing, hdMoonray RenderBuffer allocation, and MoonRay RenderOutput declaration are far enough along to create active outputs, but production payloads for simple non-beauty AOVs can arrive as zero-filled buffers. + +`cameraDepth` was used as a diagnostic target because it is one channel, renderer-generated, simple to validate, and the debug renderer proves MoonRay can produce it. It is not a preferred product AOV target and should not be the first target in the next AOV pass. Production `cameraDepth` is not fixed. + +## References + +- Official MoonRay Render Outputs guide: https://docs.openmoonray.org/user-reference/how-to-guides/render-outputs/ +- Official MoonRay RenderOutput scene object reference: https://docs.openmoonray.org/user-reference/scene-objects/render-output/RenderOutput/ +- Official MoonRay Material AOV guide: https://docs.openmoonray.org/user-reference/how-to-guides/render-outputs/material-aovs/ +- Official MoonRay Visibility AOV guide: https://docs.openmoonray.org/user-reference/how-to-guides/render-outputs/visibility-aovs/ +- Official MoonRay SceneVariables reference: https://docs.openmoonray.org/user-reference/scene-objects/scene-variables/SceneVariables/ +- Official MoonRay Arras render docs: https://docs.openmoonray.org/user-reference/tools/arras_render/ +- OpenMoonRay download/superproject context: https://openmoonray.org/download +- Arnold USD repository, architecture reference only: https://github.com/Autodesk/arnold-usd +- Local metadata: `/Applications/MoonRay/installs/openmoonray/coredata/RenderOutput.json` +- Local hdMoonray source: + - `lib/hydramoonray/RenderBuffer.cc` + - `lib/hydramoonray/RenderBuffer.h` + - `lib/hydramoonray/RenderPass.cc` + - `lib/hydramoonray/RenderDelegate.cc` + - `plugin/hd_moonray_debug/RndrRenderer.cc` + - `plugin/hd_moonray/ArrasRenderer.cc` +- Local MoonRay transport/source: + - `moonray/lib/grid/engine_tool/McrtFbSender.cc` + - `scene_rdl2/lib/common/grid_util/PackTiles.h` + - `scene_rdl2/lib/common/grid_util/PackTiles.cc` + - `moonray_arras/mcrt_dataio/lib/client/receiver/ClientReceiverFb.cc` + - `moonray_arras/mcrt_dataio/lib/engine/merger/MergeFbSender.cc` +- Generated/temporary artifacts: + - `/tmp/moonray_aov_audit/bare_sphere_beauty_cameraDepth.usda` + - `/tmp/moonray_aov_audit/bare_sphere_beauty_N_depth.rdla` + - `/tmp/moonray_aov_audit/cameraDepth_docpass_prod.exr` + - `/tmp/moonray_aov_audit/cameraDepth_docpass_debug.exr` + - `/tmp/moonray_aov_audit/packtiles_roundtrip/packtiles_camera_depth_roundtrip.cc` + - `/tmp/moonray_aov_audit/packtiles_roundtrip/packtiles_camera_depth_roundtrip.results.txt` + +## cameraDepth Diagnostic Target Status + +`cameraDepth` was an invented/diagnostic target for this investigation, not the preferred product-facing first AOV. It was still useful because current hdMoonray maps `HdAovTokens->cameraDepth` to MoonRay `RenderOutput::RESULT_DEPTH`, and RDLA output confirmed it reached MoonRay as a native `RenderOutput` with `["result"] = "depth"`. + +Do not start the next AOV pass with `cameraDepth`. It served its purpose as a transport probe and produced too many custom-path hypotheses. The next baseline should start from the existing/native mappings in `RenderBuffer.cc`: + +1. `depth` +2. `Z` +3. `N` +4. `P` +5. `Ng` + +Those targets should be validated without code changes first. Only production H20.5 EXR stats may promote an AOV from “mapped” to “working”. + +## Source, Build, Install, And Load Mapping + +| Item | Path/value | Evidence | +|---|---|---| +| active parent checkout | `/Applications/MoonRay/openmoonray` symlink to `/Applications/MoonRay/source/openmoonray` | `pwd -P` resolves to `/Applications/MoonRay/source/openmoonray` | +| parent branch | `Moonray-Houdini21-macOS` | `git rev-parse --abbrev-ref HEAD` in parent | +| active hdMoonray checkout | `/Applications/MoonRay/openmoonray/moonray/hydra/hdMoonray` | symlink-resolved repo under `/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray` | +| hdMoonray branch | `h20.5-solaris-building-testing` | `git rev-parse --abbrev-ref HEAD` | +| active MoonRay core checkout | `/Applications/MoonRay/openmoonray/moonray/moonray` | symlink-resolved repo under `/Applications/MoonRay/source/openmoonray/moonray/moonray` | +| MoonRay core branch | `h20.5-primitive-user-data-python311` | `git rev-parse --abbrev-ref HEAD` | +| active mcrt_dataio checkout | `/Applications/MoonRay/openmoonray/moonray/moonray_arras/mcrt_dataio` | nested submodule, detached `HEAD` | +| active scene_rdl2 checkout | `/Applications/MoonRay/openmoonray/moonray/scene_rdl2` | branch `h20.5-python311-build` | +| CMake source dir | `/Applications/MoonRay/openmoonray` | build output references this source root | +| CMake build dir | `/Applications/MoonRay/build` | used by `cmake --build /Applications/MoonRay/build --target grid_engine_tool --config Release` | +| edited live-debug file from previous pass | `moonray/lib/grid/engine_tool/McrtFbSender.cc` | restored to clean git state after failed diagnostic | +| target expected to compile it | `grid_engine_tool` | built dylib path `build/moonray/moonray/lib/grid/engine_tool/Release/libgrid_engine_tool.dylib` | +| installed dylib path | `/Applications/MoonRay/installs/openmoonray/lib/libgrid_engine_tool.dylib` | shasum matches rebuilt clean build product: `ed15d845d2b22cf5da841ade62ae0d8dd43a86a6` | +| final diagnostic strings removed | yes | `strings ... | rg "HDMR_CAMDEPTH|cameraDepth temporary|cameraDepth sender|cameraDepth receiver|cameraDepth merger"` returns no matches | +| isolated PackTiles test binary | `/tmp/moonray_aov_audit/packtiles_roundtrip/packtiles_camera_depth_roundtrip` | shasum `2cc68f0be91ae622e59cc9224837dd57b9a1c7a5` | +| isolated test linked dylibs | `/Applications/MoonRay/installs/openmoonray/lib` and `/Applications/MoonRay/installs/lib` | `otool -L` and `LC_RPATH` show installed MoonRay libraries | + +Source/build/install lessons: + +- There was a real confusion risk between `/Applications/MoonRay/openmoonray`, `/Applications/MoonRay/source/openmoonray`, `/Applications/MoonRay/build`, and `/Applications/MoonRay/installs/openmoonray`. +- `/Applications/MoonRay/openmoonray` resolves to `/Applications/MoonRay/source/openmoonray`, but future work must verify this before assuming source edits are built. +- The build directory must be checked before compiling. Earlier cache state referenced a missing H20.5.684 install before it was reconfigured for H20.5.584. +- Rebuilt artifacts must be copied into the actual installed runtime paths. Build-tree plugin changes alone are not enough because H20.5 loads installed dylibs. +- `hd_moonray.dylib`, `libgrid_engine_tool.dylib`, and `libclient_receiver.dylib` can each retain stale diagnostics if source is reverted but binaries are not rebuilt and reinstalled. +- Use shasum checks for build-vs-installed dylibs and `strings` checks for diagnostic markers after every diagnostic pass. + +## cameraDepth Evidence Chain + +| Stage | File/function/artifact | Evidence | cameraDepth status | Conclusion | +|---|---|---|---|---| +| USD RenderVar | `/tmp/moonray_aov_audit/bare_sphere_beauty_cameraDepth.usda` | `RenderVar "cameraDepth"` has `sourceName = "cameraDepth"`, `sourceType = "raw"`, `dataType = "float"`, and `driver:parameters:aov:name = "cameraDepth"` | authored | USD request is present. | +| Hydra AOV binding | `RenderBuffer::bind()` | `aovNameFromSettings()` returns sourceName for raw sourceType | requested as `cameraDepth` | Hydra binding name is expected. | +| hdMoonray RenderBuffer mapping | `RenderBuffer.cc` lookup table | `HdAovTokens->cameraDepth -> HdFormatFloat32, RO::RESULT_DEPTH` | mapped | `cameraDepth` is an hdMoonray/Hydra name mapped to MoonRay depth result. | +| RDLA RenderOutput | `/tmp/moonray_aov_audit/bare_sphere_beauty_N_depth.rdla` | `RenderOutput("/_outputs/cameraDepth") { ["result"] = "depth", ["math_filter"] = "min" }` | declared active | MoonRay RenderOutput is created. | +| Debug RenderContext snapshot | `RndrRenderer::resolve()` | uses `RenderContext::snapshotRenderOutput()` by RenderOutputDriver index | works | Debug renderer produces valid depth. | +| Production ArrasRenderer lookup | `ArrasRenderer::resolve()` | looks up full path, leaf name, then matched id via `ClientReceiverFb::getRenderOutputMTSafe()` | mostly ruled out | Earlier diagnostics showed `/_outputs/cameraDepth` is listed and lookup returns payload dimensions. | +| ClientReceiverFb raw extraction before fix | `ClientReceiverFb::getRenderOutputMTSafe()` diagnostic from prior pass | returned 64x64 one-channel payload but all zero | present but zero | Lookup is not the primary loss point. | +| ClientReceiverFb closest extraction before fix | same diagnostic | closest extraction also zero, closestStatus was 0 | zero | Closest-filter read did not recover data. | +| RenderOutputDriver mapping | local source/diagnostic | `RESULT_DEPTH` aliases to a depth state AOV internally | valid | `RESULT_DEPTH` is a legitimate MoonRay depth path. | +| McrtFbSender sender-side buffer | prior `McrtFbSender.cc` diagnostic | finite hit depths around 5.1 and background/no-hit `inf` before encode | valid before encode | MoonRay/mcrt has real depth data before transport. | +| PackTiles / delta encode | `McrtFbSender::addRenderOutputToProgressiveFrame()` | calls `PackTiles::encodeRenderOutput()` with active pixels, AOV buffer, default value, weight buffer, direct-to-client/noNumSampleMode=true | suspected | Transport encode/decode is the failure area. | +| Client receiver decode | prior `ClientReceiverFb` diagnostic | decode completed with active action but produced zero data | zero after decode | Receiver side sees zeroed payload. | +| Unit-weight encode attempt | temporary sender patch from previous pass | branch matched, emitted nonzero-sized packets, final EXR stayed zero | failed | Sender unit weights alone were adjacent but not sufficient as a live fix. | +| Unsafe self-decode probe | temporary live sender diagnostic | crashed mcrt with signal 11 | removed | Live self-decode used unsafe/incorrect preconditions; do isolated tests instead. | +| Restored production EXR baseline | `/tmp/moonray_aov_audit/cameraDepth_docpass_prod.exr` | `Pz` min=0 max=0 constant yes | still blocked | No production fix is currently proven. | + +Important conclusions: + +- `cameraDepth` naming was not the root cause. +- `RESULT_DEPTH` versus `STATE_VARIABLE_DEPTH` was tested and was not the root cause. +- The sender-side mcrt buffer contained valid finite depth values before encode. +- The failure occurred after sender-side values existed, in production transport/decode. +- The unit-weight encode attempt did not fix final production `Pz`. +- The unsafe live self-decode probe crashed mcrt with signal 11 and was removed. +- Current production status is still blocked. +- Beauty works because radiance/color AOVs are weight-related by design. `cameraDepth` is different because it is a state/depth payload whose finite hit values should survive independently of radiance sample weights. +- The next clean investigation is to follow the receiver-side packet application path with the isolated PackTiles result in mind. + +## Confirmed Render-Output Path + +| Stage | Current evidence | Remaining unknown | +|---|---|---| +| USD/Hydra RenderVar/AOV | Scratch USDA authored a `cameraDepth` RenderVar with raw sourceName/sourceType and float dataType. hdMoonray `aovNameFromSettings()` uses raw `sourceName`. | Whether native `depth`, `Z`, `N`, `P`, and `Ng` requests behave the same in production is not yet baseline-tested. | +| hdMoonray RenderBuffer mapping | `RenderBuffer.cc` maps `cameraDepth` to `HdFormatFloat32` and MoonRay `RESULT_DEPTH`; `Z` maps to `STATE_VARIABLE_DEPTH`; `N`, `P`, `Ng` map to state-variable outputs. | Whether product-facing names should prefer Hydra tokens, MoonRay names, or USD RenderVar conventions needs later UI work after production payloads work. | +| MoonRay RenderOutput declaration | RDLA declared `RenderOutput("/_outputs/cameraDepth")` with `["result"] = "depth"` and `["math_filter"] = "min"`. | The exact best `channel_format`, channel naming, and product metadata for final USD/Husk AOV UX remain deferred. | +| `RenderContext::snapshotDeltaRenderOutput` / `snapshotDeltaAov` | Source shows `snapshotDeltaRenderOutput()` dispatches to `snapshotDeltaAov()` for regular AOVs; transcripts recorded sender-side finite depth values after this snapshot path. | The exact live value/active/weight contract at the input to PackTiles remains unresolved. | +| `McrtFbSender::addRenderOutputToProgressiveFrame` | Source sends render-output buffers through `PackTiles::encodeRenderOutput()`. Diagnostics showed the path was reached and emitted nonzero-sized packets. | Which live active/weight/value combinations produce the zero decoded payload is still not proven safely. | +| `PackTiles::encodeRenderOutput` | Isolated roundtrip proved zero weights can zero finite depth values, and unit/active weights can preserve them in isolation. | The isolated result did not translate to a production fix, so the live contract differs from the simplified probe or another downstream step still clears data. | +| `PackTiles::decodeRenderOutput` | Receiver-side diagnostics saw decode complete with non-empty active masks, but values were already zero after decode. | Whether the zeroing is encode-side, decode-side, packet semantic, or receiver-application semantic remains open. | +| `ClientReceiverFb` decode/application | Diagnostics found `ClientReceiverFb` had the output and returned a one-channel 64x64 payload, but values were zero. | The exact point inside receiver framebuffer application where non-radiance AOVs should bypass weight semantics is still unknown. | +| `ClientReceiverFb::getRenderOutputMTSafe` | hdMoonray production lookup by full path, leaf name, then id returned a payload; closest-filter extraction did not recover data. | Whether native `depth`/`Z` use different extraction behavior remains untested. | +| hdMoonray `ArrasRenderer::resolve` | Source currently asks receiver for full path, leaf, then matched id and copies payload into render buffers. This is less suspicious after receiver diagnostics found zero before final resolve. | Final resolve should still be checked for native AOVs, but it is not the leading cameraDepth blocker. | +| `RenderBuffer::Resolve` -> EXR output | Beauty writes filled pixels; production `cameraDepth/Pz` writes metadata/channel/subimage but constant zero pixels. | Filled non-beauty production output is unproven. | + +## Sender-Side Findings + +- `McrtFbSender` has semantic access to `RenderOutputDriver` and therefore to render-output metadata. `cameraDepth` is not merely a string by the time it reaches sender code. +- `RenderContext::snapshotDeltaRenderOutput()` delegates regular AOVs to `snapshotDeltaAov()`. +- The investigated snapshot path records AOV values plus the film/beauty weight buffer. This is separate from `RenderOutputDriver::requiresScaledByWeight()`, which is tracked by the sender but does not mean the weight buffer is absent. +- Source and transcript evidence identified two weight-related hazards: + - snapshot utilities can suppress active pixels when the source weight is zero; + - `PackTiles::enqTileVal()` can write zero when the supplied weight is zero, even when the render-output packet is not in normalized/scaled mode. +- Earlier non-crashing sender diagnostics observed finite cameraDepth hit values around the expected sphere range, with no-hit/background represented as `inf`. +- Sender diagnostics also showed nonzero packets were emitted for `/_outputs/cameraDepth`. +- Production EXR `Pz` still decoded as zero, so sender presence and packet presence alone were not sufficient. + +## Receiver-Side Findings + +- `ArrasRenderer::resolve()` asks `ClientReceiverFb` for render outputs by full RenderOutput path, by leaf name, then by enumerated id. +- `ClientReceiverFb::getRenderOutputMTSafe()` returned a 64x64 one-channel cameraDepth payload during diagnostics, but the values were all zero. +- Closest-filter extraction was tested as a hypothesis and did not recover usable depth. +- Receiver decode diagnostics saw non-empty active masks. +- Decoded `FbAov` float payload values were already all zero before active-pixel OR and before hdMoonray final resolve copied data to Hydra buffers. +- This reduces suspicion on hdMoonray `RenderBuffer::Resolve` and EXR writing for the cameraDepth failure. The remaining blocker is before hdMoonray receives usable decoded values. + +## PackTiles And Layout Findings + +- Isolated PackTiles tests proved finite depth values with zero weights can decode as all zero. +- The same isolated tests proved active/unit weights preserve finite depth values. +- Therefore render-output weights are a real hazard for depth/state transport. +- PackTiles walks render-output buffers in tile-linear order (`tileId << 6` plus tile bit offset), not simple row-major order. +- A row-major active/weight construction was identified as a plausible mismatch risk and was superseded. +- A tile-linear derived active/unit-weight attempt was tested and failed in production. +- An existing-active-mask plus unit-weight attempt was also tested and failed in production. +- Therefore the live production issue is not safely fixed by rewriting only the sender-side weight buffer. + +## RenderOutput Attribute Model + +| Attribute | Type | Default | Enum values | Required for which result type | Source/evidence | Relevant to hdMoonRay? | +|---|---|---|---|---|---|---| +| active | Bool | true | n/a | all | `RenderOutput.json` | yes | +| camera | SceneObject* | false/null | Camera interface | optional per-output camera | `RenderOutput.json` | later | +| channel_format | Int | 1 | float=0, half=1 | output encoding | `RenderOutput.json`, docs | yes | +| channel_name | String | empty | n/a | output channel naming | `RenderOutput.json` | yes | +| channel_suffix_mode | Int | 0 | auto=0, rgb=1, xyz=2, uvw=3 | multi-channel naming | `RenderOutput.json` | later | +| checkpoint_file_name | String | `checkpoint.exr` | n/a | checkpoint outputs | `RenderOutput.json` | deferred | +| checkpoint_multi_version_file_name | String | empty | n/a | checkpoint outputs | `RenderOutput.json` | deferred | +| compression | Int | 1 | none, zip, rle, zips, piz, pxr24, b44, b44a, dwaa, dwab | file output | `RenderOutput.json` | mostly external/husk | +| cryptomatte_depth | Int | 6 | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_beauty | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_normals | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_p0 | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_positions | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_refn | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_refp | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_output_uv | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_record_reflected | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_record_refracted | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| cryptomatte_support_resume_render | Bool | false | n/a | cryptomatte | `RenderOutput.json` | deferred | +| denoise | Bool | false | n/a | file denoise | `RenderOutput.json` | deferred | +| denoiser_input | Int | 0 | not an input=0, as albedo=1, as normal=2 | denoiser helpers | `RenderOutput.json` | deferred | +| display_filter | SceneObject* | false/null | DisplayFilter interface | display filter result | `RenderOutput.json` | deferred | +| exr_dwa_compression_level | Float | 85 | n/a | file output | `RenderOutput.json` | deferred | +| exr_header_attributes | SceneObject* | false/null | metadata object | EXR header | `RenderOutput.json` | deferred | +| file_name | String | `scene.exr` | n/a | file output | `RenderOutput.json` | hdMoonray currently sets placeholder for outputs | +| file_part | String | empty | n/a | multipart EXR | `RenderOutput.json` | deferred | +| lpe | String | empty | n/a | light aov | `RenderOutput.json`, docs | mapped by prefix but unvalidated | +| material_aov | String | empty | n/a | material aov | `RenderOutput.json`, docs | mapped by prefix but unvalidated | +| material_aov_secondary_rays | Bool | false | n/a | material aov | `RenderOutput.json` | deferred | +| math_filter | Int | 0 | average=0, sum=1, min=2, max=3, force_consistent_sampling=4, closest=5 | reductions and depth | `RenderOutput.json` | yes | +| output_type | String | `flat` | e.g. flat/deep by docs convention | file output | `RenderOutput.json` | deferred | +| primitive_attribute | String | empty | n/a | primitive attribute result | `RenderOutput.json` | mapped by prefix but unvalidated | +| primitive_attribute_type | Int | 0 | FLOAT=0, VEC2F=1, VEC3F=2, RGB=3 | primitive attribute | `RenderOutput.json` | mapped by prefix | +| result | Int | 0 | see result table | all | `RenderOutput.json`, `RenderOutput.h` | yes | +| resume_file_name | String | empty | n/a | resume render | `RenderOutput.json` | deferred | +| state_variable | Int | 2 | see state table | state variable result | `RenderOutput.json`, `RenderOutput.h` | yes | +| visibility_aov | String | default LPE expression | n/a | visibility aov | `RenderOutput.json`, docs | deferred | + +## RenderOutput Result Enum + +| result enum/value | Meaning | Required extra attributes | Simple/direct output? | Requires material/shader/light setup? | Safe hdMoonray target? | Notes | +|---|---|---|---|---|---|---| +| RESULT_BEAUTY / 0 / beauty | full RGB render | none | yes | no | yes | Production works. | +| RESULT_ALPHA / 1 / alpha | alpha | none | yes | no | mapped, unvalidated | Reference/skip path in sender. | +| RESULT_DEPTH / 2 / depth | z distance from camera | usually `math_filter=min`, float channel | yes | no | target after transport fix | hdMoonray `cameraDepth` maps here. | +| RESULT_STATE_VARIABLE / 3 / state variable | built-in state variable | `state_variable` | yes | no | debug only today | Production payload delivery is broken for tested state AOVs. | +| RESULT_PRIMITIVE_ATTRIBUTE / 4 / primitive attribute | procedural/prim attr | `primitive_attribute`, `primitive_attribute_type` | no | requires attr | later | Needs USD primvar/attribute validation. | +| RESULT_HEAT_MAP / 5 / time per pixel | diagnostic timing | none | yes | no | mapped but untested | Sender special reference path. | +| RESULT_WIREFRAME / 6 / wireframe | wireframe diagnostic | none | yes | no | unsafe | Code comment says crashes. | +| RESULT_MATERIAL_AOV / 7 / material aov | material expression | `material_aov` | no | material labels/closures | later | Mapped by `shader:` prefix. | +| RESULT_LIGHT_AOV / 8 / light aov | LPE | `lpe` | no | scene/light setup | later | Mapped by `lpe:` prefix. | +| RESULT_VISIBILITY_AOV / 9 / visibility aov | light visibility ratio | `visibility_aov` | no | light/path setup | later | Deferred. | +| RESULT_WEIGHT / 11 / weight | sample weight | none | yes | no | mapped but untested | Sender special reference path. | +| RESULT_BEAUTY_AUX / 12 / beauty aux | adaptive auxiliary | none | yes | no | mapped but untested | Sender special reference path. | +| RESULT_CRYPTOMATTE / 13 / cryptomatte | ID mattes | cryptomatte attrs and ID setup | no | cryptomatte setup | deferred | Complex, not first target. | +| RESULT_ALPHA_AUX / 14 / alpha aux | adaptive alpha auxiliary | none | yes | no | mapped but untested | Sender special reference path. | +| RESULT_DISPLAY_FILTER / 15 / display filter | display filter output | `display_filter` | no | display filter object | deferred | Not part of first AOV implementation. | + +## State Variable Enum + +| state_variable enum/value | Meaning | Channels | Expected MoonRay type/format | Expected Hydra HdFormat | Expected USD RenderVar dataType | Safe first target? | Notes | +|---|---|---:|---|---|---|---|---| +| P / 0 | position | 3 | VEC3F/RGB-like | Float32Vec3 | vector3f/color3f | after transport fix | Mapped. | +| Ng / 1 | geometric normal | 3 | VEC3F | Float32Vec3 | normal3f/vector3f | after transport fix | Mapped. | +| N / 2 | shading normal | 3 | VEC3F | Float32Vec3 | normal3f/vector3f | after transport fix | Debug works; production zero observed. | +| St / 3 | texture coordinates | 2 | VEC2F | Float32Vec2 | float2 | later | Mapped as `St` and `primvars:st`. | +| dPds / 4 | derivative of P wrt S | 3 | VEC3F | Float32Vec3 | vector3f | later | Mapped. | +| dPdt / 5 | derivative of P wrt T | 3 | VEC3F | Float32Vec3 | vector3f | later | Mapped. | +| dSdx / 6 | s derivative wrt x | 1 | FLOAT | Float32 | float | later | Mapped. | +| dSdy / 7 | s derivative wrt y | 1 | FLOAT | Float32 | float | later | Mapped. | +| dTdx / 8 | t derivative wrt x | 1 | FLOAT | Float32 | float | later | Mapped. | +| dTdy / 9 | t derivative wrt y | 1 | FLOAT | Float32 | float | later | Mapped. | +| Wp / 10 | world position | 3 | VEC3F | Float32Vec3 | vector3f/color3f | later | Mapped. | +| depth / 11 | z distance from camera | 1 | FLOAT | Float32 | float | candidate | `Z` maps here; `cameraDepth` uses result depth instead. | +| motionvec / 12 | 2D motion vector | 2 | VEC2F | Float32Vec2 | float2 | later | Do not implement in this pass. | + +## Material AOV Model + +Material AOVs are diagnostic material-property outputs. Official docs state they are not influenced by scene lighting or occlusion and use a label/selection/property expression model. + +| material_aov/property | Meaning | Requires material labels/selection? | Lighting affected? | Supported by MoonRay docs/source? | Supported by hdMoonray now? | Safe later target? | +|---|---|---|---|---|---|---| +| albedo | unoccluded white-light response | optional labels/selections | no | yes | mapped by `shader:` only | yes, later | +| color | material/lobe color | optional labels/selections | no | yes | mapped by `shader:` only | yes, later | +| emission | emitted radiance property | optional labels/selections | no | yes | mapped by `shader:` only | later | +| factor | fresnel factor | optional labels/selections | no | yes | mapped by `shader:` only | later | +| normal | material normal property | optional labels/selections | no | yes | mapped by `shader:` only | later | +| radius | subsurface radius | optional labels/selections | no | yes | mapped by `shader:` only | later | +| roughness | glossy roughness | optional labels/selections | no | yes | mapped by `shader:` only | later | +| depth, dPds, dPdt, dSdx, dSdy, dTdx, dTdy, matte, motionvec, N, Ng, P, pbr_validity, St, Wp | documented accepted material AOV properties in local metadata | varies | no | yes, in `RenderOutput.json` material_aov comment | not production-validated | later | +| float:, rgb:, vec2:, vec3: | primitive attribute access through material AOV expression | requires attr | no | yes, in local metadata | not production-validated | later | + +## Primitive Attribute AOV Model + +| primitive_attribute_type enum/value | Meaning | Hydra format | USD dataType | Notes | +|---|---|---|---|---| +| FLOAT / 0 | one float channel | Float32 | float | Used for int-like Hydra ID fallbacks today, but exact ID behavior needs validation. | +| VEC2F / 1 | two float channels | Float32Vec2 | float2 | Mapped for unknown `primvars:` if format guessed as vec2. | +| VEC3F / 2 | three float channels | Float32Vec3 | vector3f/color3f | Mapped for vec3 primvars. | +| RGB / 3 | three color channels | Float32Vec3 | color3f | Default for unknown non-scalar primvars. | + +## LPE, Light, And Visibility AOV Model + +| AOV type | Attribute | Example values | Requires scene/light setup? | Supported by MoonRay? | Supported by hdMoonray now? | Notes | +|---|---|---|---|---|---|---| +| LPE/light AOV | `lpe` | `caustic`, `diffuse`, `emission`, `glossy`, `mirror`, `reflection`, `translucent`, `transmission` | yes | yes | prefix `lpe:` maps to `RESULT_LIGHT_AOV` | Deferred. | +| light aov | `result=light aov`, `lpe` | custom OSL-style LPE | yes | yes | mapped by `lpe:` | Needs scene/light validation. | +| visibility aov | `visibility_aov` | default expression from metadata | yes | yes | not currently exposed in RenderBuffer lookup | Deferred. | + +## RenderBuffer To MoonRay RenderOutput Contract + +| Field | Value | Evidence | +|---|---|---| +| Hydra requested AOV name | `cameraDepth` | USD RenderVar sourceName and RenderBuffer lookup | +| USD RenderVar sourceName | `cameraDepth` | scratch USDA | +| USD RenderVar dataType | `float` | scratch USDA | +| RenderPass AOV binding name | `cameraDepth` | raw sourceType returns sourceName in `aovNameFromSettings()` | +| RenderBuffer constructor/input name | Hydra render buffer id bound to AOV | `RenderBuffer::bind()` | +| RenderBuffer internal AOV token/name | `cameraDepth` | `mAovName = aovNameFromSettings(aovBinding)` | +| RenderBuffer HdFormat | `HdFormatFloat32` | lookup table | +| RenderBuffer channel count | 1 | `Allocate()` format handling | +| RenderBuffer clear value | USD clearValue 0 in fixture | scratch USDA | +| RenderBuffer MoonRay output path | `/_outputs/cameraDepth` | `ROId = "/_outputs/" + mAovName` | +| RenderBuffer MoonRay result enum | `RESULT_DEPTH` | lookup table and RDLA | +| RenderBuffer MoonRay state_variable enum | not set for `cameraDepth` | `RESULT_DEPTH` branch | +| RenderBuffer MoonRay math_filter | `MATH_FILTER_MIN` | RenderBuffer depth branch and RDLA | +| RenderBuffer MoonRay channel_format | default half unless custom settings override | metadata default; no override in RDLA | +| RenderBuffer MoonRay channel_name | empty/default | metadata default; no override in RDLA | +| RenderBuffer MoonRay output_type | `flat` | metadata default | +| RenderBuffer active flag | true | RenderBuffer sets active; RDLA output exists | + +Answers: + +- `cameraDepth` uses official `RenderOutput.result = depth`. +- `Z` uses `RenderOutput.result = state variable` plus `state_variable = depth`. +- `cameraDepth` is different from `Z` in hdMoonray. +- `MATH_FILTER_MIN` matches MoonRay's depth example and is not currently proven to be the production transport root cause. +- Production closest-filter depth is not currently involved; earlier diagnostics reported closestStatus 0 for cameraDepth. +- `cameraDepth` is a USD/Hydra/hdMoonray name. MoonRay internally receives a RenderOutput named `/_outputs/cameraDepth` with result `depth`. + +## Current hdMoonray RenderBuffer AOV Matrix + +| Hydra/RenderBuffer AOV name | MoonRay RenderOutput result | Extra MoonRay attr | math_filter | channel_format | channel_name | HdFormat | channel count | USD RenderVar dataType | Debug plugin payload | Production plugin payload | Current status | Missing work | +|---|---|---|---|---|---|---|---:|---|---|---|---|---| +| color | beauty special case | none | default | n/a | n/a | Float32Vec4 | 4 | color4f | works | works | works production | none for beauty | +| beauty | beauty | none | default | default | default | Float32Vec3 | 3 | color3f | untested | untested | mapped but untested | validate | +| alpha | alpha | none | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| depth | depth | none | min | default | default | Float32 | 1 | float | untested | untested | mapped but untested | note clip-space remap path | +| cameraDepth | depth | none | min | default | default | Float32 | 1 | float | works | zero | declared but zero in production | transport/decode fix | +| heat_map | time per pixel | none | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| wireframe | wireframe | none | default | default | default | Float32Vec3 | 3 | color3f | untested | untested | unsafe | code comment says crashes | +| weight | weight | none | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| beauty_aux | beauty aux | none | default | default | default | Float32Vec3 | 3 | color3f | untested | untested | mapped but untested | adaptive validation | +| alpha_aux | alpha aux | none | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | adaptive validation | +| P | state variable | P | default | default | default | Float32Vec3 | 3 | vector3f | untested | untested | mapped but untested | transport fix first | +| Ng | state variable | Ng | default | default | default | Float32Vec3 | 3 | normal3f/vector3f | untested | likely zero if tested | mapped but untested | transport fix first | +| N / normal | state variable | N | default | default | default | Float32Vec3 | 3 | normal3f | works debug | zero observed | declared but zero in production | transport fix first | +| St / primvars:st | state variable | St | default | default | default | Float32Vec2 | 2 | float2 | untested | untested | mapped but untested | validate | +| dPds | state variable | dPds | default | default | default | Float32Vec3 | 3 | vector3f | untested | untested | mapped but untested | validate | +| dPdt | state variable | dPdt | default | default | default | Float32Vec3 | 3 | vector3f | untested | untested | mapped but untested | validate | +| dSdx | state variable | dSdx | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| dSdy | state variable | dSdy | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| dTdx | state variable | dTdx | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| dTdy | state variable | dTdy | default | default | default | Float32 | 1 | float | untested | untested | mapped but untested | validate | +| Wp | state variable | Wp | default | default | default | Float32Vec3 | 3 | vector3f | untested | untested | mapped but untested | validate | +| Z | state variable | depth | default | default | default | Float32 | 1 | float | untested | explicit test did not fix cameraDepth | mapped but untested | separate validation | +| motionvec | state variable | motionvec | default | default | default | Float32Vec2 | 2 | float2 | not tested here | not tested here | mapped but untested | later | +| primvars:* | primitive attribute | primitive_attribute/type | varies | default | default | guessed | varies | varies | untested | untested | mapped but requires primitive attr setup | later | +| lpe:* | light aov | lpe | default | default | default | Float32Vec3 default | 3 | color3f | untested | untested | mapped but requires LPE/light setup | later | +| shader:* | material aov | material_aov | default | default | default | Float32Vec3 default | 3 | color3f | untested | untested | mapped but requires material setup | later | +| cryptomatte | cryptomatte | cryptomatte attrs | default | default | default | custom | custom | custom | untested | untested | mapped but complex | later | +| primId/instanceId/elementId/edgeId/pointId | primitive attribute fallback | primitive attribute | closest | default | default | Int32 | 1 | int | untested | untested | mapped but format uncertain | selection/ID validation | + +## AOV Support Tiers + +| Tier | AOV family | Examples | Current hdMoonray status | First validation target | Blocker | +|---|---|---|---|---|---| +| 0 | beauty/color only | color/beauty | production-working | done | none | +| 1 | simple renderer/state outputs | cameraDepth/depth, N, Ng, P, motionvec | mapped; debug works for cameraDepth/N; production zero for tested outputs | cameraDepth | production transport/decode | +| 2 | material/diagnostic outputs | albedo, roughness, normal, emission, pbr_validity | prefix mapped but unvalidated | albedo after Tier 1 | material AOV model and payload transport | +| 3 | primitive attribute/primvar outputs | primvars:st, IDs, custom attrs | prefix mapped but unvalidated | simple float primvar | USD primvar source and type mapping | +| 4 | LPE/light/visibility outputs | diffuse, glossy, emission, visibility | LPE prefix mapped; visibility not exposed | simple LPE after transport | scene/light setup and output semantics | +| 5 | Cryptomatte/ID/deep/denoiser helpers | Cryptomatte, OIDN helpers, deep | complex/deferred | none yet | dedicated implementation | + +## H20.5 Validation + +Houdini validation used `/Applications/Houdini/Houdini20.5.584/Frameworks/Houdini.framework/Versions/20.5/Resources/bin/husk`. + +Renderer discovery in the normal interactive user package environment can crash while Houdini loads unrelated third-party render packages. The controlled H20.5.584 validation environment used: + +```zsh +export HOUDINI_INSTALL_DIR=/Applications/Houdini/Houdini20.5.584 +source /Applications/MoonRay/installs/openmoonray/scripts/macOS/setupHoudini.sh +export HOUDINI_PACKAGE_SKIP=1 +``` + +In that controlled environment, `husk --list-renderers` lists `HdMoonrayRendererPlugin (Moonray)` and `HdMoonrayRendererDebugPlugin`. + +| Renderer | AOV | Exists? | Min | Max | Constant? | Finite hit values preserved? | Background representation | Pass? | +|---|---|---|---:|---:|---|---|---|---| +| HdMoonrayRendererPlugin | beauty/C | yes | 0 | 12744.106445 | no | yes | black background/alpha | pass | +| HdMoonrayRendererPlugin | cameraDepth/Pz | yes | 0 | 0 | yes | no | zero-filled | fail | +| HdMoonrayRendererDebugPlugin | cameraDepth/Pz | yes | 5.123846 | 5.973000 | no | yes | `inf` no-hit background, InfCount 2458 | pass | + +## 2026-06-06 Production Transport Attempts + +The focused follow-up pass tested whether the live production failure was caused by the render-output weight/active-mask path around `McrtFbSender::addRenderOutputToProgressiveFrame()` and `PackTiles::encodeRenderOutput()`. + +| Attempt | Source change tested | Output | Beauty result | cameraDepth result | Conclusion | +|---|---|---|---|---|---| +| derived finite-depth active/unit weights | For `/_outputs/cameraDepth`, derived tile-linear active pixels and unit weights from finite positive depth values before encode. | `/tmp/moonray_aov_audit/cameraDepth_depth_transport_tile_fix_prod.exr` | `C` nonconstant, max RGB about `12744.106445 5335.891602 3334.152344`, alpha max `1` | `Pz.Z` min=max=avg `0`, constant | Failed. A sender-side unit-weight mask derived from finite depth values did not fix the production EXR. | +| existing active mask + unit weights | Preserved the live snapshot active mask and set unit weights for active `cameraDepth` pixels before encode. | `/tmp/moonray_aov_audit/cameraDepth_depth_transport_unit_weight_prod.exr` | `C` nonconstant, max RGB about `12744.107422 5335.892090 3334.152588`, alpha max `1` | `Pz.Z` min=max=avg `0`, constant | Failed. The live production zero is not fixed by sender-side unit weights alone. | +| guarded sender diagnostic | Added temporary `HDMR_CAMDEPTH_TRANSPORT_DIAG` sender-side diagnostics to inspect live active/value/weight state. | `/tmp/moonray_aov_audit/cameraDepth_sender_active_values_diag.log` | render did not complete | mcrt exited with `std::bad_alloc`, then signal 11 | Failed unsafe probe. The diagnostic was removed and the installed runtime was rebuilt without diagnostic strings. | + +The source changes from these attempts were not retained. The installed H20.5 runtime was restored to a non-diagnostic build; `strings` checks on the relevant installed dylibs no longer find `HDMR_CAMDEPTH` markers. + +The earlier non-crashing diagnostics remain the best evidence for the live value path: + +- sender-side mcrt had finite hit depths around the expected sphere range, with no-hit/background represented as `inf`; +- receiver-side decode saw a non-empty active mask and nonzero packet sizes for `/_outputs/cameraDepth`; +- the decoded render-output values were already all zero before hdMoonray final buffer resolve. + +The failed 2026-06-06 attempts narrow the remaining blocker: the live production issue is not safely fixed by rewriting only the sender-side weight buffer. The next safe investigation should instrument the exact `RenderContext::snapshotDeltaRenderOutput()` -> `McrtFbSender::addRenderOutputToProgressiveFrame()` -> `PackTiles::encodeRenderOutput()` input contract, or reproduce that complete contract in an isolated test, before changing PackTiles or receiver application semantics. + +## Failed Hypotheses And Attempts + +| Attempt/debug path | Intent | Files touched | Evidence produced | Result | Source retained? | Runtime cleaned? | Final status | +|---|---|---|---|---|---|---|---| +| original `RESULT_DEPTH` active/unit sender fix | Prevent depth values from being suppressed by radiance weights. | `moonray/lib/grid/engine_tool/McrtFbSender.cc`, `.h` | Beauty stayed valid; `Pz` stayed constant zero. | failed | no | yes | superseded by later narrower/layout-aware attempts | +| env-gated sender diagnostic | Print live sender stats only when `HDMR_CAMDEPTH_TRANSPORT_DIAG=1`. | `McrtFbSender.cc` | Initial env propagation/diagnostic behavior was unreliable across mcrt process boundaries. | inconclusive | no | yes | useful evidence only, superseded | +| hard-limited sender diagnostic | Force limited sender logging without relying on verbose logger routing. | `McrtFbSender.cc` | Proved sender branch could be reached and packets were nonzero. | evidence only | no | yes | useful evidence only | +| hdMoonray resolve diagnostic | Check whether `ArrasRenderer::resolve()` could find the cameraDepth output and whether it copied zeros. | `plugin/hd_moonray/ArrasRenderer.cc` | Full path/leaf/id lookup found output dimensions; final values still zero. | evidence only | no | yes | useful evidence only | +| ClientReceiverFb decode diagnostic | Inspect receiver-side decoded render-output payload before hdMoonray resolve. | `moonray_arras/mcrt_dataio/lib/client/receiver/ClientReceiverFb.cc` | Decode/application saw non-empty active masks but all-zero float payload. | evidence only | no | yes | useful evidence only | +| closest-filter extraction hypothesis | Test whether cameraDepth needed closest-filter receiver plane. | `ArrasRenderer.cc` | Closest extraction did not recover depth; closest status did not explain zero payload. | failed | no | yes | superseded | +| `cameraDepth` as `STATE_VARIABLE_DEPTH` hypothesis | Test whether `RESULT_DEPTH` was the wrong MoonRay result type. | hdMoonray RenderBuffer mapping during temporary test | Production did not become nonzero. | failed | no | yes | superseded | +| finite-depth derived active/unit weights | Build active mask and unit weights from finite positive depth values. | `McrtFbSender.cc`, `.h` | `/tmp/moonray_aov_audit/cameraDepth_depth_transport_tile_fix_prod.exr`: Beauty nonconstant, `Pz` zero. | failed | no | yes | failed | +| tile-linear derived active/unit weights | Correct the derived active/weight layout to PackTiles tile-linear order. | `McrtFbSender.cc`, `.h` | Render succeeded; `Pz` still zero. | failed | no | yes | failed | +| existing active mask + unit weights | Keep MoonRay snapshot active mask, replace only transport weights with unit weights. | `McrtFbSender.cc` | `/tmp/moonray_aov_audit/cameraDepth_depth_transport_unit_weight_prod.exr`: Beauty nonconstant, `Pz` zero. | failed | no | yes | failed | +| sender unit-weight encode attempt | Confirm whether nonzero sender packets plus unit weights were enough. | `McrtFbSender.cc` | Branch reached and packet size nonzero in diagnostics; final EXR stayed zero. | failed | no | yes | useful evidence only | +| sender-side encode/self-decode diagnostic | Decode the just-encoded packet inside sender to split encode vs downstream. | `McrtFbSender.cc` | mcrt crashed with signal 11; unsafe probe. | unsafe | no | yes | unsafe, do not repeat as live mcrt probe | +| guarded active-value diagnostic | Count values/weights at active pixels under `HDMR_CAMDEPTH_TRANSPORT_DIAG`. | `McrtFbSender.cc` | mcrt exited with `std::bad_alloc` / signal 11 before useful output. | unsafe | no | yes | unsafe | +| unsafe self-decode diagnostic | Live self-decode using production packet state. | `McrtFbSender.cc` | Crashed mcrt / signal 11. | unsafe | no | yes | unsafe | + +Rows marked “useful evidence only” should not be copied as implementation. They are breadcrumbs for where evidence came from. Rows marked “unsafe” should not be repeated inside live mcrt without first building an isolated reproduction. + +## Production Failure Classification + +| Hypothesis | Evidence for | Evidence against | Verdict | +|---|---|---|---| +| source/build/install mismatch invalidated prior diagnostics | possible risk in nested MoonRay core builds | clean rebuild/install shasums match; diagnostic strings removed; H20.5 baseline is stable | ruled out for current state | +| wrong RenderOutput parameter contract | cameraDepth could have been `state_variable depth` | MoonRay docs show direct `result=depth`; RDLA declares result depth; explicit state-variable test did not fix production | unlikely | +| valid RenderOutput contract but mcrt does not populate AOV film buffer | production final is zero | sender-side diagnostic found finite depths before encode | ruled out | +| mcrt populates it but McrtFbSender snapshots zero | final is zero | sender-side snapshot diagnostic had finite depths and `inf` background | ruled out | +| McrtFbSender has real data but PackTiles/delta encode suppresses it | isolated PackTiles test reproduces zero decode with zero weights | active/unit weights preserve finite values in isolation | likely | +| McrtFbSender has real data but MergeFbSender drops or clears it | merger could be in path in distributed render | local H20.5 production did not fire merger diagnostic | not local blocker | +| McrtFbSender has real data but ClientReceiverFb decode returns zeros | prior receiver diagnostic showed decode zeros | isolated test suggests zero weights can cause that | likely downstream symptom | +| receiver has real data but ArrasRenderer reads wrong payload plane | ArrasRenderer could lookup wrong name | receiver payload itself was zero; lookup returned dimensions | unlikely | +| output only available at final frame but sampled too early | progressive timing can affect AOVs | restored final EXR still zero; debug final works | unlikely | + +## Isolated PackTiles Roundtrip + +The isolated probe in `/tmp/moonray_aov_audit/packtiles_roundtrip/packtiles_camera_depth_roundtrip.cc` exercises: + +- `scene_rdl2::grid_util::PackTiles::encodeRenderOutput` +- `scene_rdl2::grid_util::PackTiles::decodeRenderOutput` +- one-channel `VariablePixelBuffer::FLOAT` +- `noNumSampleMode=true` +- `doNormalizeMode=false` +- `closestFilterStatus=false` +- synthetic finite depth hits around 5.24 to 5.87 +- `inf`, zero, and large finite background/default cases +- zero, active-one, and all-one weight buffers + +| Case | Value buffer | Weight buffer | Active mask | defaultValue | noNumSampleMode/directToClient | Expected | Actual | +|---|---|---|---|---|---|---|---| +| finite hits + inf background | production-like | all zero | finite hit pixels active | inf | true | nonzero finite hits survive | decoded all zero | +| finite hits + inf background | production-like | unit on finite hits | finite hit pixels active | inf | true | nonzero finite hits survive | finite hits preserved; background partially zero/inf | +| finite hits + inf background | production-like | all ones | all pixels active | inf | true | nonzero finite hits survive | finite hits and inf background preserved | +| finite hits + inf background | production-like | all zero | all pixels active | inf | true | identify whether mask or weights suppress | decoded all zero | +| finite hits + zero background | production-like | all zero | finite hit pixels active | zero | true | isolate inf default behavior | decoded all zero | +| finite hits + large finite background | production-like | all zero | finite hit pixels active | large finite | true | isolate inf default behavior | decoded all zero | +| finite hits only | no inf | all zero | finite pixels active | zero | true | isolate weight gating | decoded all zero | + +Key result: isolated PackTiles encode/decode reproduced zeroing when the render-output weight buffer is all zero, even with `doNormalizeMode=false`. Finite depth survives when weights are active/unit. This points at weight-buffer gating or receiver application semantics for unweighted state/depth AOVs. + +Do not apply a broad PackTiles fix yet. The next production fix should first prove why the live cameraDepth weight buffer is zero and whether unweighted/depth outputs should be encoded with an AOV-specific active mask/unit gate, or whether the receiver should apply non-normalized packets independently of sample weights. + +## Current Evidence-Based Conclusion + +- Production Beauty remains working in H20.5.584. +- Debug renderer cameraDepth works and can produce plausible finite depth values. +- Production `cameraDepth/Pz` remains zero-filled and constant. +- `cameraDepth` reached MoonRay as `RESULT_DEPTH`, which made it useful for probing native depth transport, but it was an invented diagnostic target and no production product AOV should be based on it yet. +- No production cameraDepth fix was proven. +- All source attempts were reverted. +- The installed H20.5 runtime was cleaned of temporary cameraDepth diagnostic markers. +- The remaining blocker is after mcrt has real render-output depth values but before hdMoonray receives usable decoded values. +- The exact live contract among `RenderContext::snapshotDeltaRenderOutput`, `McrtFbSender::addRenderOutputToProgressiveFrame`, `PackTiles::encodeRenderOutput`, and `ClientReceiverFb` decode/application remains unresolved. +- Further cameraDepth work is not recommended until native mapped AOVs are baseline-validated. +- Non-beauty AOV UI must remain hidden/deferred. + +## Next AOV Pass Rules + +Do not repeat the custom cameraDepth-first path. + +Rules for the next AOV pass: + +1. Do not start with `cameraDepth`. +2. Do not invent AOV names. +3. Treat `RenderBuffer.cc` mappings as the source of truth. +4. First baseline validation targets are `depth`, `Z`, `N`, `P`, and `Ng`. +5. Baseline first means: + - inspect the `RenderBuffer.cc` mapping; + - inspect RDLA `RenderOutput` declarations; + - render production H20.5 EXRs; + - compare debug renderer output only as a control; + - classify each AOV with image stats. +6. No code changes before baseline. +7. No PackTiles changes before baseline. +8. No `moonray_dcc_plugins` or UI changes before production payloads are proven. +9. No material AOVs, LPEs, primvars, Cryptomatte, denoiser outputs, or light AOVs in the baseline pass. +10. No support claim without H20.5 production EXR stats proving nonzero/nonconstant finite payloads where expected. + +## Open Questions + +- Does native `depth` fail the same way as diagnostic `cameraDepth`? +- Does `Z` use a different `RESULT_STATE_VARIABLE` path that bypasses or reproduces the `RESULT_DEPTH` issue? +- Do vec3 state variables `N`, `P`, and `Ng` fail at declaration, transport, decode, or final resolve? +- Are production AOV failures shared across all RenderOutput types, or specific to `RESULT_DEPTH` / depth-like outputs? +- Can the full live render-output encode/decode contract be reproduced in an isolated test without crashing mcrt? +- Is the issue in PackTiles encode, PackTiles decode, direct-to-client packet semantics, or receiver application? +- Which exact native AOVs are already production-working, if any? diff --git a/docs/hdMoonray_development_guide.md b/docs/hdMoonray_development_guide.md new file mode 100644 index 0000000..62bc14d --- /dev/null +++ b/docs/hdMoonray_development_guide.md @@ -0,0 +1,170 @@ +# hdMoonray Development Guide + +This guide captures practical development rules from recent successful hdMoonray and Houdini Solaris integration work. It is not a complete user manual. It is a working guide for future changes in this multi-repo, submodule-heavy MoonRay/Houdini environment. + +See also: + +- `hdMoonray_feature_implementation_pattern.md` +- `hdMoonray_validation_workflow.md` +- `hdMoonray_stable_vs_wip_matrix.md` +- `hdMoonray_aov_audit.md` +- `units-and-scale-notes.md` +- `houdini20_5_hdMoonrayAdapters_compat.md` + +## Source Of Truth + +Public PRs are useful context, but local submodule state is the source of truth for development: + +- Parent checkout: `/Applications/MoonRay/source/openmoonray` +- hdMoonray: `/Applications/MoonRay/source/openmoonray/moonray/hydra/hdMoonray` +- Houdini DCC plugin: `/Applications/MoonRay/source/openmoonray/moonray/moonray_dcc_plugins` +- MoonRay core: `/Applications/MoonRay/source/openmoonray/moonray/moonray` +- scene_rdl2: `/Applications/MoonRay/source/openmoonray/moonray/scene_rdl2` +- mcrt_dataio / client receiver: `/Applications/MoonRay/source/openmoonray/moonray/moonray_arras/mcrt_dataio` + +The symlink `/Applications/MoonRay/openmoonray` may resolve to `/Applications/MoonRay/source/openmoonray`. Verify it before assuming which checkout is active. + +Useful public anchors: + +- OpenMoonRay/openmoonray PR #228: compatibility and superproject context. +- OpenMoonRay/moonray_dcc_plugins PR #3: material builder context. + +Useful local checkpoints: + +- hdMoonray SpotLight backend checkpoint: `27201a47dae82a7df1eb4588dafa2c4c046a6623` +- dcc SpotLight UI/helper checkpoint: `9bf4945815b3e6c4a39a62702ef89be0453ecc28` +- parent pin checkpoint: `be43cbe6a77ec584ecea005101cdd78b0cd1e663` + +## Repo Ownership + +Pick the owning repo before editing. + +| Change type | Usually belongs in | Stable examples | +|---|---|---| +| Hydra translation, RDL object creation, SceneVariables, render buffers | `moonray/hydra/hdMoonray` | SpotLight class validation, RectLight spread, geometry settings | +| Houdini Solaris DS/UI, shelf tools, Python HDA generation | `moonray/moonray_dcc_plugins` | Material builder, SpotLight helper UI, Render Settings LOP prototype | +| MoonRay renderer core behavior, transport, scene_rdl2 classes | MoonRay core / scene_rdl2 / mcrt_dataio | AOV investigation touched these only diagnostically; do not edit without proof | +| Parent submodule pins | parent `openmoonray` | Checkpoint commits only after submodule changes are committed and pushed | + +Do not solve a backend problem with a UI-only change. Do not solve a UI authoring problem by changing renderer semantics unless the backend contract is actually wrong. + +## Baseline Before Feature Work + +Before implementing: + +1. Identify pre-feature behavior. +2. Identify the native MoonRay/RDL concept. +3. Identify the USD/Hydra/Solaris concept. +4. Identify existing hdMoonray translation patterns. +5. Identify Houdini DS/HDA/runtime behavior if UI is involved. +6. Decide which repo owns the smallest correct change. + +Successful examples: + +- DomeLight support began with the Solaris/Hydra prim type alias problem and mapped `DomeLight_1` / `UsdLuxDomeLight_1` to native MoonRay `EnvLight`. +- RectLight shaping preserved the authored RectLight class and mapped cone angle to native `RectLight.spread`. +- SpotLight override validation checked installed MoonRay SceneClass metadata instead of accepting any `moonray:class` token. +- Geometry settings used native MoonRay mesh/subdivision attributes and avoided stomping authored primvars. + +## Source / Build / Install / Runtime Mapping + +Always prove the runtime is loading the code you changed. + +Minimum checks: + +```sh +pwd -P +git status --short +git rev-parse --abbrev-ref HEAD +git rev-parse HEAD +``` + +For compiled code, also verify: + +- CMake source directory. +- CMake build directory. +- Installed plugin or dylib path. +- Build artifact checksum versus installed artifact checksum. +- Diagnostic strings are absent after cleanup if diagnostics were used. + +The AOV audit showed why this matters: stale build/install/runtime artifacts can make a correct source conclusion look wrong, or worse, leave crashing diagnostics installed. + +## Houdini 20.5 And Houdini 21 Evidence + +Keep Houdini-version evidence separate. + +The current production validation target for recent Solaris/AOV work has been Houdini 20.5. Do not use Houdini 21 behavior to prove a Houdini 20.5 production feature. PR #228 is valuable compatibility context, but H20.5 production behavior must be validated with the H20.5 build. + +## HDA And DS Source Of Truth + +Generated or installed UI artifacts are not the design source. + +Rules: + +- For generated HDAs, update the generation script first. +- Regenerate the HDA from the script. +- Confirm the regenerated HDA preserves the intended UI. +- Do not hand-polish only the `.hda`. +- Keep install-tree sync explicit and validated. + +Stable evidence: + +- `moonray_render_settings.py` became the source of truth for the custom MoonRay Render Settings LOP. +- Manual HDA-only drift was identified as a problem during Render Settings LOP iteration. + +For DS files: + +- Preserve original MoonRay-authored labels, defaults, ranges, menus, and help text unless the change is explicitly required. +- Do not use a default value to fake a UI range. +- Keep standard USD controls separate from renderer-specific overrides. + +## Commit And Parent Pin Discipline + +For submodule work: + +1. Commit and push the submodule first. +2. Validate the submodule branch and commit hash. +3. Commit the parent submodule pin only after the submodule commit is pushed. +4. Do not update unrelated submodule pins. + +Good checkpoint examples: + +- `moonray_dcc_plugins` SpotLight UI commit `9bf4945815b3e6c4a39a62702ef89be0453ecc28`. +- hdMoonray SpotLight backend/lifecycle commits ending in `f5331a8`. +- parent pin checkpoint `be43cbe6a77ec584ecea005101cdd78b0cd1e663`. + +## Stable Versus WIP Areas + +Use `hdMoonray_stable_vs_wip_matrix.md` before choosing an example to copy. + +Stable/proven patterns: + +- Material builder / native material subnet. +- DomeLight / EnvLight alias handling. +- Explicit native SpotLight override. +- RectLight ShapingAPI to native spread. +- Geometry/subdivision/tessellation exposure. + +Partial/WIP patterns: + +- Render Settings LOP. +- Beauty buffer support. +- Unit scale policy. + +Failure/process contrast: + +- Non-beauty AOVs and `cameraDepth` transport. + +## What Future Work Should Avoid + +Avoid these patterns: + +- Inventing renderer names, AOV names, or scene classes before checking MoonRay metadata/source. +- Default-authoring renderer-specific overrides for standard Solaris lights. +- Exposing Houdini controls before backend/RDLA/render proof exists. +- Using a hardcoded allowlist where installed MoonRay SceneClass metadata can be queried. +- Rewriting broad renderer transport code from a single failing AOV. +- Treating debug renderer success as production renderer success. +- Mixing H20.5 and H21 validation evidence. +- Leaving temporary diagnostics in installed runtime binaries. + diff --git a/docs/hdMoonray_development_patterns_from_features.md b/docs/hdMoonray_development_patterns_from_features.md new file mode 100644 index 0000000..da9da20 --- /dev/null +++ b/docs/hdMoonray_development_patterns_from_features.md @@ -0,0 +1,270 @@ +# hdMoonray Development Patterns From Features + +This companion note maps individual audited features to the patterns they support. It exists because the stable examples span several repos and are easier to compare in one place than inside the broader guide. + +## Material Builder / Native Material Subnet + +Status: stable / proven. + +Evidence: + +- `moonray_dcc_plugins` commit `952eea3`. +- Files: `houdini/python3.11libs/moonray_material_builder.py`, `houdini/toolbar/MoonRayTools.shelf`. +- H20.5 fixture generation in hdMoonray commit `77a673e`. + +Native contract: + +- Houdini material workflows already support editable VOP/subnet material builders. +- MoonRay already provides real material and displacement VOP nodes. + +Why it worked: + +- The tool created an editable native Houdini subnet. +- It used real installed MoonRay VOP node types. +- It avoided a fake locked material HDA and avoided backend material translation hacks. + +Copy this: + +- Build on native Houdini node graph idioms. +- Use actual renderer node types. +- Keep shelf/tool code small and predictable. + +Avoid this: + +- Do not force DWA materials through unrelated MaterialX nodes as a workaround. +- Do not hide unsupported material behavior behind fake UI. + +## DomeLight / EnvLight + +Status: stable / proven. + +Evidence: + +- `moonray_dcc_plugins` commit `9cfd73f`. +- hdMoonray infrastructure commit `77a673e`. +- Files: `PrimTypeUtils.cc`, `PrimTypeUtils.h`, `Light.cc`, `moonray_Light.ds`, `HdMoonrayRendererPlugin_Light.ds`. +- H20.5 fixtures under `testSuite/runtime/h20_isolation` and `testSuite/light/env_schema_v1`. + +Native contract: + +- USD/Solaris may present dome lights as `DomeLight_1` or `UsdLuxDomeLight_1`. +- MoonRay native environment light class is `EnvLight`. + +Why it worked: + +- The change canonicalized the Solaris/Hydra type instead of patching a single UI symptom. +- The backend and DS files both learned the alias set. +- The native MoonRay class stayed `EnvLight`. + +Copy this: + +- Add canonicalization helpers for schema/runtime aliases. +- Keep alias handling close to prim type translation. + +Avoid this: + +- Do not treat one Houdini type spelling as the only valid USD type. +- Do not create fake light classes to match Houdini labels. + +## SpotLight Native Override + +Status: stable / proven for explicit native override and live class swapping. + +Evidence: + +- Backend commits `65e457b`, `27201a4`, `f5331a8`. +- UI commits `dbf22fa`, `b01d7e0`, `9bf4945`. +- Files: `Light.cc`, `Light.h`, `moonray_Light.ds`, `HdMoonrayRendererPlugin_Light.ds`. + +Native contract: + +- MoonRay has a real `SpotLight` SceneClass. +- MoonRay does not have a valid installed `PointLight` SceneClass in this setup. +- USD has renderer-neutral light types and UsdLux ShapingAPI, not a core USD SpotLight prim. + +Why it worked: + +- `moonray:class` overrides are validated against installed scene class metadata and `INTERFACE_LIGHT`. +- Invalid `PointLight` does not create missing DSO failures. +- The Houdini helper is explicit and ownership-safe. +- Class changes trigger light object reset rather than stale class state. + +Copy this: + +- Keep renderer-native modes explicit. +- Validate native classes semantically. +- Use shared cleanup helpers for class/lifecycle changes. + +Avoid this: + +- Do not default-author `moonray:class`. +- Do not make normal Solaris point intent a native MoonRay `PointLight`. +- Do not expose native SpotLight controls as if they were generic USD shaping controls. + +## UsdLux Shaping And RectLight + +Status: RectLight shaping stable; Sphere/Disk shaping policy needs future re-audit. + +Evidence: + +- `65e457b` introduced shaped Sphere/Disk to SpotLight behavior. +- `4396439` corrected RectLight behavior to preserve RectLight and map cone angle to `spread`. +- File: `Light.cc`. + +Native contract: + +- UsdLux ShapingAPI is renderer-neutral. +- USD cone angle is an off-axis angular limit. +- MoonRay SpotLight cone angles are full apex angles. +- MoonRay RectLight has native `spread`. + +Why RectLight worked: + +- The backend preserved rectangular emitter semantics. +- It used the native `spread` attribute instead of converting RectLight to SpotLight. + +Copy this: + +- Preserve the authored USD light type when MoonRay has a native shaped/spread equivalent. +- Convert units/angle conventions with comments and formulae. + +Avoid this: + +- Do not use native SpotLight as a universal fallback without proving no native equivalent exists. + +## Geometry / Subdivision / Tessellation + +Status: stable / proven. + +Evidence: + +- hdMoonray commits `d85ec55`, `24bac7e`. +- dcc commit `78ca757`. +- Files: `Mesh.cc`, `HdMoonrayRendererPlugin_Geometry.ds`, `testSuite/geometry/moonray_geometry_settings/*`. + +Native contract: + +- MoonRay mesh objects have native subdivision/tessellation settings. +- Geometry settings are prim-level, not global render settings. + +Why it worked: + +- Backend sets native attrs while respecting authored primvars. +- UI exposes renderer-specific geometry controls on geometry, not in the Render Settings LOP. +- Fixtures document the authored inputs and expected translation. + +Copy this: + +- Keep geometry behavior on geometry prims. +- Use primvar/attribute guards before applying defaults. + +Avoid this: + +- Do not put geometry tessellation controls in global render settings. +- Do not replace authored prim-specific values with renderer defaults. + +## H20.5 Solaris Architecture Infrastructure + +Status: stable infrastructure evidence. + +Evidence: + +- hdMoonray commit `77a673e`. +- Files include `PrimTypeUtils.*`, `ValueConverter.cc`, `RenderSettings.*`, `RenderDelegate.*`, adapter compatibility docs, and H20.5 fixtures. + +Native contract: + +- Houdini 20.5 uses its own USD/Hydra adapter environment and compatibility constraints. +- hdMoonray needs compatibility shims and careful value conversion to operate in that environment. + +Why it worked: + +- It centralized compatibility helpers and test fixtures. +- It added source-backed validation fixtures for runtime behavior. + +Copy this: + +- Add infrastructure when several features need the same bridge. +- Document compatibility work with fixtures. + +Avoid this: + +- Do not use a broad infrastructure commit as a template for small feature changes. + +## Render Settings LOP + +Status: partial / WIP. + +Evidence: + +- `moonray_dcc_plugins` commits `8c995f8` through `6cd0361`. +- Files: `moonray_render_settings.py`, generated HDA, `moonray_render_settings_lop_audit.md`, validation script. + +Native contract: + +- Solaris render setup should author USD RenderSettings, RenderProduct, and RenderVar prims. +- MoonRay renderer settings should be curated and namespaced. +- Generated HDA source must remain the source of truth. + +What worked: + +- Resolution and product path authoring. +- Source-generated HDA workflow. +- Owned USD Render ROP lifecycle policy. +- Curated SceneVariable authoring. + +What remains WIP: + +- Final UI cadence. +- AOV integration. +- Renderer buffer/image-buffer behavior. + +Copy this: + +- Keep generation source and HDA in sync. +- Keep standard USD render contracts valid. + +Avoid this: + +- Do not hand-edit only the HDA. +- Do not expose unsupported AOV controls. + +## AOV / cameraDepth Investigation + +Status: experimental / failure contrast. + +Evidence: + +- `hdMoonray_aov_audit.md`. +- hdMoonray `2eb0808` for beauty buffer support. +- Scratch artifacts under `/tmp/moonray_aov_audit`. + +Native contract: + +- MoonRay RenderOutput is native AOV output. +- USD RenderVar asks Hydra/render delegates for buffers. +- hdMoonray must bridge both and return filled production buffers. + +What was useful: + +- Beauty support progressed. +- The audit mapped RenderVar -> RenderBuffer -> RenderOutput -> transport -> EXR. +- It identified production transport/weight hazards. + +What failed: + +- `cameraDepth` was a diagnostic name, not the ideal product baseline. +- Production `cameraDepth/Pz` remained constant zero. +- Debug/local evidence did not prove production support. + +Copy this: + +- Keep detailed source/build/install/run evidence. +- Use EXR stats as the support gate. + +Avoid this: + +- Do not start UI exposure from authoring-only success. +- Do not claim AOV support from metadata/channels alone. +- Do not leave unsafe diagnostics installed. + diff --git a/docs/hdMoonray_feature_implementation_pattern.md b/docs/hdMoonray_feature_implementation_pattern.md new file mode 100644 index 0000000..54da386 --- /dev/null +++ b/docs/hdMoonray_feature_implementation_pattern.md @@ -0,0 +1,249 @@ +# hdMoonray Feature Implementation Pattern + +This document extracts implementation rules from recent stable hdMoonray and Houdini Solaris work. Every rule below is tied to an audited feature or commit range; it should be updated when future evidence changes. + +## Pattern 1: Start With The Native MoonRay/RDL Contract + +Rule: identify the real MoonRay scene class, attribute, SceneVariable, or RenderOutput before writing translation code or UI. + +Evidence: + +- SpotLight backend commits `65e457b`, `27201a4`, and `f5331a8` validate explicit `moonray:class` using installed MoonRay SceneClass metadata and `INTERFACE_LIGHT`. +- RectLight shaping commit `4396439` maps USD cone angle to the native MoonRay `RectLight.spread` attribute. +- Geometry/subdivision commit `d85ec55` maps to native MoonRay mesh settings instead of inventing global renderer controls. +- Material builder commit `952eea3` creates real MoonRay VOP material/displacement nodes rather than a fake material wrapper. + +Why it worked: + +- The renderer received attributes it actually understands. +- Invalid classes such as `PointLight` stayed invalid. +- RDLA/RDL could prove the final MoonRay object and values. + +Future work should copy: + +- Check installed metadata such as `coredata/*.json`. +- Check MoonRay source and scene class interfaces. +- Use semantic validation when possible. + +Future work should avoid: + +- Hardcoding stale class lists. +- Adding fake classes or aliases. +- Exposing UI controls that author unconsumed attributes. + +## Pattern 2: Respect USD/Hydra/Solaris As The Input Contract + +Rule: preserve standard USD/Solaris semantics unless the user explicitly chooses a renderer-specific override. + +Evidence: + +- DomeLight support uses canonical type handling for `DomeLight_1` / `UsdLuxDomeLight_1` and maps to MoonRay `EnvLight`. +- SpotLight UI commit `9bf4945` adds an explicit native MoonRay SpotLight helper instead of making all shaped USD lights native MoonRay SpotLights through UI defaults. +- RectLight shaping commit `4396439` preserves RectLight semantics and maps shaping to `spread`. +- Render Settings LOP WIP authors standard USD RenderSettings/Product/RenderVar prims instead of inventing a MoonRay-only render contract. + +Why it worked: + +- Solaris-authored scenes stayed renderer-neutral where possible. +- Explicit renderer-specific behavior remained inspectable in USD. +- Generic USD Render ROP / husk workflows remained viable. + +Future work should copy: + +- Treat USD schemas as the source input model. +- Translate schema attributes to MoonRay equivalents only when the equivalent is proven. +- Keep renderer-specific overrides explicit and namespaced. + +Future work should avoid: + +- Treating UsdLux ShapingAPI as automatically meaning native MoonRay SpotLight in all cases. +- Converting RectLight with cone shaping to SpotLight when RectLight has a native spread equivalent. +- Mapping point-light intent to nonexistent MoonRay `PointLight`. + +## Pattern 3: hdMoonray Is A Translation Bridge + +Rule: make hdMoonray translate between established contracts; do not make it an independent invention layer. + +Evidence: + +- `PrimTypeUtils` from `77a673e` provides canonical prim type handling for Houdini/Solaris aliases. +- `ValueConverter.cc` improvements support robust VtValue conversion, including later float conversion fixes in `24bac7e`. +- `Light.cc` maps Hydra light params to MoonRay light attributes and validates class choices. +- `Mesh.cc` handles geometry/subdivision prim-level attributes. + +Why it worked: + +- Each layer stayed understandable: USD/Hydra input, hdMoonray translation, MoonRay/RDL output. +- Fixes stayed narrow and testable. + +Future work should copy: + +- Add helpers when they clarify translation boundaries, like `PrimTypeUtils`. +- Keep object-specific behavior near the object translator. +- Use existing RenderDelegate/Light/Mesh/RenderSettings patterns before adding new infrastructure. + +Future work should avoid: + +- Broad cross-cutting conversions without feature proof. +- Global unit or scale multipliers before a unit policy exists. +- Backend changes that only hide invalid UI authoring. + +## Pattern 4: Backend First, UI Second + +Rule: expose UI only after the backend consumes the authored value and RDLA/render evidence proves it. + +Evidence: + +- SpotLight native controls were restored only after verifying MoonRay SpotLight attributes and backend consumption. +- The native SpotLight helper authors `moonray:class_control = set` and `moonray:class = SpotLight` only when explicitly enabled. +- Geometry settings DS exposure followed backend Mesh translation. +- Render Settings LOP remains partial/WIP because AOV UI must not expose non-working production AOVs. + +Why it worked: + +- Users did not get dead controls. +- Houdini UI stayed tied to inspectable USD/RDLA state. + +Future work should copy: + +- For every UI parm, list the authored USD property and consumed MoonRay attribute. +- Preserve original MoonRay metadata labels/help where possible. +- Use explicit controls for renderer-specific modes. + +Future work should avoid: + +- UI checkboxes for AOVs whose production buffers are zero. +- Restoring old controls only because they used to exist. +- Default-authoring `moonray:class` for standard Solaris lights. + +## Pattern 5: Validate Semantic Classes And Attributes + +Rule: validate class and attribute names against the runtime metadata/source rather than guessing. + +Evidence: + +- Explicit `moonray:class = SpotLight` became valid after checking the installed MoonRay SceneClass registry. +- Invalid `moonray:class = PointLight` remains invalid and falls back instead of creating missing DSO errors. +- SpotLight UI menus use valid installed light classes. +- Geometry and render settings work uses real MoonRay metadata names. + +Why it worked: + +- Normal Solaris lights stopped producing missing `PointLight` DSO errors. +- Valid native overrides could work without weakening validation. + +Future work should copy: + +- Query or inspect installed metadata first. +- Keep invalid explicit overrides warning and falling back cleanly. + +Future work should avoid: + +- Silent replacement of invalid classes with arbitrary classes. +- Fake PointLight support unless MoonRay core gains a real PointLight SceneClass. + +## Pattern 6: Reuse Existing Lifecycle Patterns + +Rule: before inventing object deletion/rebuild behavior, inspect how hdMoonray and RDL2 expect object lifetimes to work. + +Evidence: + +- SpotLight class swap cleanup moved into `resetLightObject()` in `f5331a8`. +- `resetLightObject()` is shared by `Sync()` and `Finalize()`. +- Old RDL light objects are made inert/unlinked rather than assuming direct delete/remove support. + +Why it worked: + +- Live class changes no longer required toggling unrelated Solaris spotlight controls. +- Cleanup followed an existing inert-object lifecycle expectation. + +Future work should copy: + +- Search for existing cleanup/finalize code first. +- Build narrow helpers when the same cleanup is needed in live update and finalization. + +Future work should avoid: + +- Creating class-suffixed replacement objects without cleanup. +- Assuming SceneObjects can be deleted from RDL2 during interactive Hydra updates without source proof. + +## Pattern 7: Prove With RDLA, Render, And UI Evidence + +Rule: a feature is not proven by authored USD alone. + +Evidence: + +- DomeLight and SpotLight work used authored USD, RDLA/RDL class proof, render proof, and no missing DSO errors. +- Geometry/subdivision work includes a tracked fixture and README. +- AOV work showed that RenderVar metadata and RDLA RenderOutput creation were not enough; production buffers were still zero. + +Why it worked: + +- Multi-layer validation caught failures that single-layer checks missed. + +Future work should copy: + +- Validate USD, RDLA/RDL, render output, and Houdini UI behavior when relevant. +- For image buffers, require EXR stats proving nonzero/nonconstant data. + +Future work should avoid: + +- Promoting a feature because EXR channels exist but pixels are empty. +- Treating debug renderer behavior as production renderer behavior. + +## Pattern 8: Keep WIP Work Clearly Marked + +Rule: partial progress should be documented, but not presented as supported behavior. + +Evidence: + +- Render Settings LOP has useful source-generated HDA and RenderSettings/Product foundation, but remains WIP. +- Beauty buffer support is partial because non-beauty AOVs remain unresolved. +- `cameraDepth` is explicitly documented as a diagnostic target and failure contrast. +- Unit scale policy is documented as deferred. + +Why it worked: + +- Future work can build from useful findings without accidentally promising unsupported behavior. + +Future work should copy: + +- Maintain status tables. +- Separate stable evidence from scratch/manual evidence. +- Record exact blockers. + +Future work should avoid: + +- Over-documenting AOV support. +- Exposing unresolved AOVs in artist UI. +- Adding broad fixes during unrelated foundation work. + +## Feature Pattern Checklist + +Before implementing: + +- What is the native MoonRay/RDL concept? +- What is the USD/Hydra/Solaris concept? +- Where does the existing hdMoonray translation boundary live? +- Which repo owns the change? +- What is the pre-change baseline? +- What files should remain untouched? +- What would be the unsafe broad fix? +- What evidence will prove the change? + +Before exposing UI: + +- Is the backend implemented? +- Is the authored USD inspectable? +- Does RDLA/RDL prove the value? +- Does H20.5 production behavior prove it? +- Are original MoonRay labels/defaults/help preserved where possible? +- Are defaults and UI ranges separate? + +Before committing: + +- Are generated files regenerated from source? +- Are only intended files changed? +- Are submodule pins left alone until the submodule commit is pushed? +- Is WIP explicitly marked? + diff --git a/docs/hdMoonray_stable_vs_wip_matrix.md b/docs/hdMoonray_stable_vs_wip_matrix.md new file mode 100644 index 0000000..d0a1668 --- /dev/null +++ b/docs/hdMoonray_stable_vs_wip_matrix.md @@ -0,0 +1,71 @@ +# hdMoonray Stable vs WIP Matrix + +This matrix records the current evidence-backed status of recent hdMoonray and Houdini Solaris integration work. It is meant to keep future work honest about what is proven, what is partial, and what should only be used as cautionary evidence. + +Public PRs are useful anchors, but local submodule history is the source of truth for the exact state audited here: + +- OpenMoonRay/openmoonray PR #228: Houdini/macOS compatibility and superproject integration context. +- OpenMoonRay/moonray_dcc_plugins PR #3: Houdini 20.5 MoonRay material builder context. +- Parent checkpoint pin `be43cbe6a77ec584ecea005101cdd78b0cd1e663`. +- hdMoonray SpotLight checkpoint `27201a47dae82a7df1eb4588dafa2c4c046a6623`. +- dcc SpotLight UI checkpoint `9bf4945815b3e6c4a39a62702ef89be0453ecc28`. + +## Status Categories + +| Status | Meaning | +|---|---| +| Stable / proven | The behavior is implemented narrowly, maps to native MoonRay/USD/Houdini concepts, and has validation evidence from USD/RDLA/render/UI/source review. | +| Stable infrastructure | Useful foundational work that should be reused, but is too broad to copy as a single feature pattern. | +| Partial / WIP | Useful work exists, but behavior is incomplete, not fully validated, or still changing. | +| Experimental / failure contrast | Do not copy as an implementation pattern. Use only for lessons and future validation requirements. | + +## Audited Areas + +| Area | Status | Main commits / ranges | Primary files | Evidence | Notes | +|---|---|---|---|---|---| +| Material builder / native material subnet | Stable / proven | `moonray_dcc_plugins` `952eea3` | `houdini/python3.11libs/moonray_material_builder.py`, `houdini/toolbar/MoonRayTools.shelf` | Uses native Houdini VOP subnet workflow, creates real MoonRay VOP material/displacement nodes, and installs a shelf tool. H20.5 fixture generation exists in hdMoonray `77a673e`. | Good example of using native Houdini constructs instead of inventing a locked one-off material abstraction. | +| Material nodes | Stable / proven as existing dependency | Pre-existing MoonRay VOP node definitions plus material builder work | Houdini VOP node definitions and generated DS/OTL assets in dcc plugin | Material builder relies on real installed MoonRay node types such as DwaBaseMaterial and NormalDisplacement. | Do not document this as a new backend feature; document it as a stable dependency that the material subnet integrates. | +| DomeLight / EnvLight | Stable / proven | `moonray_dcc_plugins` `9cfd73f`, hdMoonray `77a673e` | `Light.cc`, `PrimTypeUtils.cc`, `PrimTypeUtils.h`, `moonray_Light.ds`, `HdMoonrayRendererPlugin_Light.ds` | Handles Houdini/USD dome aliases such as `DomeLight_1` and `UsdLuxDomeLight_1`, maps to native MoonRay `EnvLight`, includes H20.5 isolation fixtures. | Good example of canonicalizing Solaris/Hydra prim type aliases instead of adding ad hoc UI-only fixes. | +| SpotLight backend native override | Stable / proven for explicit override | `65e457b`, `27201a4`, `f5331a8` | `Light.cc`, `Light.h` | Explicit `moonray:class = "SpotLight"` validates against installed MoonRay SceneClass metadata; invalid `PointLight` remains invalid; class swaps share `resetLightObject()` cleanup used by `Sync()` and `Finalize()`. | Good example of validating semantic MoonRay classes instead of using a stale allowlist. | +| SpotLight UI/helper native mode | Stable / proven | `dbf22fa`, `b01d7e0`, `9bf4945` | `moonray_Light.ds`, `HdMoonrayRendererPlugin_Light.ds` | `moonray:class_control` default remains `none`; helper checkbox explicitly authors `moonray:class_control = set` and `moonray:class = SpotLight`; helper only clears its own override. | Good example of explicit renderer-specific override UI without default-authoring invalid renderer state. | +| SpotLight / UsdLux ShapingAPI connection | Partial but useful | `65e457b`, `4396439` | `Light.cc` | USD cone half-angle is converted to MoonRay SpotLight full apex angle with comments and formula; cone softness maps to inner cone. | Treat SphereLight/DiskLight shaping-to-SpotLight as current behavior to re-audit, not a final universal policy. | +| RectLight ShapingAPI behavior | Stable / proven | `4396439` | `Light.cc` | RectLight with authored ShapingAPI cone attrs remains MoonRay `RectLight`; cone angle maps to native `spread` as `clamp(angle, 0, 90) / 90`. | Strong example of preserving authored light class when MoonRay has a native equivalent. | +| Geometry render settings exposure | Stable / proven | `d85ec55`, `24bac7e`, `moonray_dcc_plugins` `78ca757` | `Mesh.cc`, geometry DS files, geometry fixture README/USD | Exposes real MoonRay geometry/subdivision attributes and preserves authored primvars through `isPrimvarUsed()` checks. | Good example of backend defaults plus prim-level override controls rather than global renderer settings. | +| Subdivision / tessellation exposure | Stable / proven | `d85ec55`, `24bac7e`, `78ca757` | `Mesh.cc`, `HdMoonrayRendererPlugin_Geometry.ds`, `testSuite/geometry/moonray_geometry_settings/*` | Maps USD subdivision/tessellation intent to native MoonRay mesh settings with tracked fixture evidence. | Future geometry work should follow this prim-level, metadata-backed pattern. | +| H20.5 Solaris architecture / infrastructure | Stable infrastructure | `77a673e` | `PrimTypeUtils.*`, `ValueConverter.cc`, `RenderSettings.*`, `UsdRenderers.json`, adapter compatibility doc, fixtures | Adds H20.5 compatibility, canonical prim type handling, value conversion, render settings plumbing, and test fixtures. | Reuse utilities and compatibility patterns, but do not treat the whole commit as a narrow feature recipe. | +| Render Settings LOP | Partial / WIP | `moonray_dcc_plugins` `8c995f8` through `6cd0361` | `moonray_render_settings.py`, generated HDA, `moonray_render_settings_lop_audit.md`, validation script | Source-generated HDA, USD RenderSettings/Product/RenderVar foundation, and owned USD Render ROP lifecycle are useful. AOV integration and final UX remain unsettled. | Good process evidence for source-of-truth HDA generation; not yet a stable renderer-settings pattern. | +| Beauty buffer / USD Render ROP support | Partial / WIP | hdMoonray `2eb0808` | `ArrasRenderer.cc`, `UsdRenderers.json` | Beauty path is useful progress, but non-beauty AOVs remain blocked. | Do not claim full image-buffer/AOV support from this commit. | +| AOV / cameraDepth investigation | Experimental / failure contrast | `docs/hdMoonray_aov_audit.md` plus scratch artifacts | `RenderBuffer.cc`, `RenderPass.cc`, `ArrasRenderer.cc`, MoonRay transport source, audit doc | `cameraDepth` debug path works, production payload arrives zero-filled; PackTiles/weight path remains unresolved. | Use as a process warning: authoring metadata and RDLA creation are not enough; production EXR stats are required. | +| Unit policy / normalized lights / SSS scale | WIP / deferred | `8d1ded4` | `docs/units-and-scale-notes.md` | Documents that `metersPerUnit` is ignored, `nonrayscene_scale` behavior is unresolved, and SSS distances pass raw. | Do not fix during render settings or AOV work without a dedicated unit-policy pass. | + +## What Is Safe To Use As A Pattern + +Use these as primary examples: + +- Material builder / native material subnet. +- DomeLight alias-to-EnvLight support. +- Explicit native SpotLight override and UI helper. +- RectLight ShapingAPI-to-`spread` preservation. +- Geometry/subdivision/tessellation prim-level exposure. + +Use these only as supporting infrastructure examples: + +- H20.5 Solaris architecture commit `77a673e`. +- RenderSettings source-generated HDA mechanics. + +Use these only as cautionary examples: + +- Non-beauty AOV and `cameraDepth` transport work. +- Any behavior validated only in debug renderer but not production H20.5. + +## Evidence Rules For Changing Status + +An area may move to stable only when the relevant layer has proof: + +- USD authoring or Hydra input is correct. +- hdMoonray maps it to native MoonRay/RDL concepts. +- RDLA/RDL proves the intended SceneObject, attribute, SceneVariable, or RenderOutput. +- H20.5 production render or Houdini UI behavior proves the user-facing result. +- EXR stats prove image data when the feature is image-buffer/AOV related. +- The repo, build, install, and runtime path were verified. + diff --git a/docs/hdMoonray_validation_workflow.md b/docs/hdMoonray_validation_workflow.md new file mode 100644 index 0000000..4ee1e34 --- /dev/null +++ b/docs/hdMoonray_validation_workflow.md @@ -0,0 +1,213 @@ +# hdMoonray Validation Workflow + +This workflow records the validation levels that actually caught or proved recent hdMoonray changes. Use it before promoting a feature from WIP to stable. + +## Validation Layers + +| Layer | What it proves | Tools / evidence | Required for | +|---|---|---|---| +| Source audit | The change maps to real code paths and native concepts. | `rg`, `git show`, local metadata, MoonRay/OpenUSD/SideFX docs | All changes | +| USD authoring | Houdini/Solaris authored the intended prims, attrs, rels, or custom properties. | `usdcat`, `usdview`, hython/HOM, exported USDA | UI, RenderSettings, lights, geometry | +| Hydra/hdMoonray translation | hdMoonray receives and translates the intended values. | source tracing, logs, debug fixtures, `hd_usd2rdl` if applicable | Backend changes | +| RDLA/RDL proof | MoonRay scene objects and attributes exist with expected values. | debug RDLA/RDL output, `rdlOutput`, renderer logs | Lights, geometry, render settings, AOV declarations | +| Render proof | The feature affects an actual render correctly. | husk, USD Render ROP, image output | Lights, materials, geometry, render settings | +| EXR/image stats | Pixels are filled and plausible. | `oiiotool --stats`, channel inspection | AOVs, render buffer work | +| Houdini UI proof | The installed UI behaves correctly without unlocking or hidden stale state. | H20.5 fresh session, HOM, screenshots where useful | DS/HDA/shelf work | +| Runtime provenance | The installed runtime loaded the rebuilt code. | shasum, `strings`, `otool`, branch/hash checks | Compiled code, diagnostics | + +## Minimum Source Checks + +Run these in the relevant repo before and after work: + +```sh +git status --short +git rev-parse --abbrev-ref HEAD +git rev-parse HEAD +git diff --stat +git diff +``` + +For submodules, also check the parent: + +```sh +git -C /Applications/MoonRay/source/openmoonray status --short +git -C /Applications/MoonRay/source/openmoonray diff --submodule +``` + +## Runtime Provenance Checks + +Use these whenever compiled code or installed Houdini plugins are involved: + +```sh +pwd -P +cmake --build /Applications/MoonRay/build --target --config Release +shasum +shasum +strings | rg "" +``` + +The AOV audit showed that stale installed dylibs can invalidate an otherwise careful investigation. Remove diagnostic strings and prove they are gone before final reporting. + +## Light Validation + +For light work, validate each layer: + +1. Export the Solaris-authored USD. +2. Confirm standard USD attributes are present and renderer-specific overrides are absent unless explicitly enabled. +3. Render or dump RDLA. +4. Confirm the RDL light class. +5. Confirm translated attributes. +6. Confirm no missing DSO errors. +7. Confirm live updates when the UI changes class-affecting state. + +Stable examples: + +- Default Sphere/Point intent no longer authors `moonray:class = PointLight`. +- Explicit `moonray:class = SpotLight` validates and creates MoonRay `SpotLight`. +- Invalid `moonray:class = PointLight` warns and falls back. +- RectLight plus ShapingAPI stays `RectLight` and maps to `spread`. +- DomeLight aliases map to `EnvLight`. + +Special cases: + +- SpotLight orientation and class swaps require render or transform/RDLA proof. +- SphereLight/DiskLight ShapingAPI behavior should be re-audited before broad policy changes. +- RectLight ShapingAPI should not become SpotLight unless the native override is explicit. + +## Geometry / Subdivision Validation + +For geometry settings: + +1. Check USD topology/subdivision inputs. +2. Check `primvars:moonray:*` or namespaced attrs if used. +3. Confirm backend does not override explicitly authored primvars. +4. Dump RDLA and inspect mesh attributes. +5. Render a small fixture. + +Stable evidence: + +- `d85ec55` adds Mesh translation and a tracked fixture under `testSuite/geometry/moonray_geometry_settings`. +- `24bac7e` improves float conversion evidence for SceneVariables/geometry settings. +- `78ca757` exposes geometry controls in Houdini DS. + +Avoid: + +- Global render settings for prim-specific geometry behavior. +- Defaults that stomp authored primvars. + +## Material Builder Validation + +For material subnet work: + +1. Create the tool in a fresh H20.5 session. +2. Confirm it creates a Houdini-native editable material subnet. +3. Confirm real MoonRay VOP nodes exist. +4. Confirm surface and displacement outputs are connected. +5. Render or generate a fixture through Solaris. + +Stable evidence: + +- `moonray_dcc_plugins` `952eea3` adds `moonray_material_builder.py` and shelf tool support. +- hdMoonray `77a673e` includes `generate_moonray_material_builder_fixture.py`. + +Avoid: + +- Fake material wrappers. +- Locked HDAs when a native editable subnet pattern fits Houdini better. + +## Render Settings Validation + +Render Settings LOP is partial/WIP. Use this workflow before declaring any part stable: + +1. Confirm the generation script is source of truth. +2. Regenerate the HDA. +3. Confirm the HDA loads in H20.5. +4. Confirm no manual HDA-only drift. +5. Export USDA and inspect: + - RenderSettings prim. + - RenderProduct prim. + - RenderVar prims. + - camera relationship. + - products relationship. + - orderedVars relationship. + - resolution. + - productName. + - `moonray:sceneVariable:*` attrs. +6. Render via direct husk or USD Render ROP. +7. Confirm output path and resolution. + +Do not validate viewport/IPR resolution with offline RenderSettings assumptions. Viewport/IPR framing is viewport-driven unless a supported Houdini/Hydra mechanism proves otherwise. + +## AOV Validation + +AOVs require the strictest validation. + +An AOV is not production-working unless: + +- USD RenderVar authoring is correct. +- RenderProduct `orderedVars` contains the RenderVar. +- hdMoonray creates the corresponding render buffer and MoonRay RenderOutput. +- RDLA declares the expected RenderOutput. +- Production H20.5 render writes the EXR channel/subimage. +- EXR stats prove nonzero/nonconstant plausible data. + +Do not promote an AOV based only on: + +- EXR metadata. +- Channel names. +- Debug renderer success. +- RDLA RenderOutput declaration. +- Local PackTiles probes. + +Known WIP/failure contrast: + +- `cameraDepth` debug path works. +- Production `cameraDepth/Pz` has existed but been constant zero. +- Sender-side mcrt had finite depth values. +- Transport/weight/decode semantics remain unresolved. +- The next AOV baseline should start with native mapped targets such as `depth`, `Z`, `N`, `P`, and `Ng`, not diagnostic `cameraDepth`. + +## Unit / Scale Validation + +Unit behavior is deferred. Do not mix it into unrelated feature work. + +Known facts from `units-and-scale-notes.md`: + +- Houdini Solaris defaults to `metersPerUnit = 1.0`. +- Houdini `unitlength` changes authored USD `metersPerUnit`. +- hdMoonRay currently does not read/apply `metersPerUnit`. +- `metersPerUnit` changes alone produced identical RDL values and EXR stats. +- SSS/material distance attrs are passed raw. +- Houdini `nonrayscene_scale` / Apply Scene Scale for normalized lights remains unresolved. + +Future unit work must use controlled fixtures and compare Houdini/Karma expectations against hdMoonRay/MoonRay behavior. + +## Reporting Template + +Every feature report should include: + +- Repos and branches. +- Exact commits/ranges inspected. +- Source files changed. +- Native MoonRay/RDL source or metadata consulted. +- USD/Hydra/Solaris schema or Houdini source consulted. +- Authored USD evidence. +- RDLA/RDL evidence. +- Render or EXR evidence. +- H20.5 UI evidence if applicable. +- Files intentionally untouched. +- WIP/deferred items. +- Final git statuses. + +## Cleanup Checklist + +Before ending a pass: + +- Temporary diagnostics removed. +- Installed runtime is non-crashing. +- Diagnostic strings absent from installed binaries. +- Scratch artifacts kept outside the repo. +- Only intended files changed. +- No generated files are stale. +- Parent submodule pins untouched unless explicitly requested. + From 8d15dc07f511ebebf8804bb79ab0312fb86719db Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:19:20 +0200 Subject: [PATCH 12/15] hdMoonray: document native camera controls pattern Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- docs/hdMoonray_development_guide.md | 46 +++++++- ...nray_development_patterns_from_features.md | 100 +++++++++++++++++- ...dMoonray_feature_implementation_pattern.md | 53 +++++++++- docs/hdMoonray_stable_vs_wip_matrix.md | 5 +- docs/hdMoonray_validation_workflow.md | 69 +++++++++++- 5 files changed, 268 insertions(+), 5 deletions(-) diff --git a/docs/hdMoonray_development_guide.md b/docs/hdMoonray_development_guide.md index 62bc14d..484a16d 100644 --- a/docs/hdMoonray_development_guide.md +++ b/docs/hdMoonray_development_guide.md @@ -118,6 +118,49 @@ For DS files: - Do not use a default value to fake a UI range. - Keep standard USD controls separate from renderer-specific overrides. +### Renderer-Specific LOP DS Discovery + +Camera controls exposed a useful DCC integration trap. Houdini's MoonRay +`pythonrc.py` overrides `loputils.addRendererParmFolders()` and searches for +renderer parameter files using: + +```text +soho/parameters/_.ds +``` + +Because the hook prepends the forced renderer name `moonray`, Camera LOP +renderer properties look for: + +```text +soho/parameters/moonray_Camera.ds +``` + +That file is the expected source DS file for the normal Solaris Camera LOP +Moonray folder. Light and geometry follow the same pattern with +`moonray_Light.ds`, `HdMoonrayRendererPlugin_Light.ds`, +`moonray_Geometry.ds`, and `HdMoonrayRendererPlugin_Geometry.ds`. + +Source-path HOM validation is useful but not sufficient. During the native +camera controls pass, source-path validation found `moonray_Camera.ds`, but a +normal H20.5 session loaded MoonRay DCC files from: + +```text +/Applications/MoonRay/installs/openmoonray/plugin/houdini +``` + +The installed tree initially did not contain `moonray_Camera.ds`, so the normal +Camera LOP UI had no Moonray folder. The existing DCC install script synced the +source file into the active package path: + +```sh +cmake -P /Applications/MoonRay/build/moonray/moonray_dcc_plugins/houdini/cmake_install.cmake +``` + +After that sync, the installed DS matched source byte-for-byte and a fresh +H20.5 Camera LOP showed the Moonray folder. Existing Houdini sessions may need +a restart or a fresh node because parameter templates can be cached at node +creation time. + ## Commit And Parent Pin Discipline For submodule work: @@ -144,6 +187,7 @@ Stable/proven patterns: - Explicit native SpotLight override. - RectLight ShapingAPI to native spread. - Geometry/subdivision/tessellation exposure. +- Native MoonRay camera controls for `mb_shutter_bias` and bokeh parameters. Partial/WIP patterns: @@ -162,9 +206,9 @@ Avoid these patterns: - Inventing renderer names, AOV names, or scene classes before checking MoonRay metadata/source. - Default-authoring renderer-specific overrides for standard Solaris lights. - Exposing Houdini controls before backend/RDLA/render proof exists. +- Trusting source-path HOM UI validation without also testing the installed package path used by artists. - Using a hardcoded allowlist where installed MoonRay SceneClass metadata can be queried. - Rewriting broad renderer transport code from a single failing AOV. - Treating debug renderer success as production renderer success. - Mixing H20.5 and H21 validation evidence. - Leaving temporary diagnostics in installed runtime binaries. - diff --git a/docs/hdMoonray_development_patterns_from_features.md b/docs/hdMoonray_development_patterns_from_features.md index da9da20..dd64d5d 100644 --- a/docs/hdMoonray_development_patterns_from_features.md +++ b/docs/hdMoonray_development_patterns_from_features.md @@ -163,6 +163,105 @@ Avoid this: - Do not put geometry tessellation controls in global render settings. - Do not replace authored prim-specific values with renderer defaults. +## Native MoonRay Camera Controls + +Status: stable / proven for the first low-risk Camera LOP controls. + +Evidence: + +- File: `moonray_dcc_plugins/houdini/soho/parameters/moonray_Camera.ds`. +- Backend source: `Camera.cc`. +- Installed metadata: `coredata/PerspectiveCamera.json` and + `coredata/OrthographicCamera.json`. +- H20.5.584 validation in a normal installed package path after DCC install + sync. + +Native contract: + +- MoonRay `PerspectiveCamera` and `OrthographicCamera` already have native + attributes for `mb_shutter_bias`, `bokeh`, `bokeh_sides`, `bokeh_image`, + `bokeh_angle`, `bokeh_weight_location`, and `bokeh_weight_strength`. +- hdMoonray already has generic `moonray:*` pass-through for camera attributes + that exist on the active camera SceneClass. +- The `PerspectiveCamera("primaryCamera")` copy path already copies the selected + motion-blur and bokeh attributes where needed. +- Standard USD camera attributes still own DOF through `fStop` and + `focusDistance`. + +Why it worked: + +- The change stayed DCC-only because the backend was already consuming the + selected native attributes. +- The UI controls default to `none`, so a default Camera LOP does not author + `moonray:*` camera attributes. +- Explicit UI values author inspectable custom USD camera attrs such as + `moonray:bokeh_sides` and `moonray:mb_shutter_bias`. +- RDLA proves both the authored camera and `primaryCamera` receive the values + when USD DOF is enabled. +- The DOF-off probe showed bokeh attrs may remain on the authored camera object, + but are not copied to `primaryCamera`; the controls should therefore be + described as requiring USD DOF/fStop/focusDistance to be visibly effective. + +Copy this: + +- Use native camera metadata and existing backend pass-through before adding + new camera translation code. +- Keep standard USD camera controls separate from renderer-specific `moonray:*` + overrides. +- Use renderer DS control parms so defaults author nothing and explicit values + remain inspectable in USD. +- Validate the visible folder in a fresh normal H20.5 session using the installed + package path, not only a source-path HOM override. + +Avoid this: + +- Do not expose `moonray:class` or native camera class switching as part of this + quick-win pattern. +- Do not expose FisheyeCamera, SphericalCamera, DomeMaster3DCamera, BakeCamera, + stereo controls, `pixel_sample_map`, medium/material/projector, or bake-camera + workflows without a separate backend/UI audit. +- Do not claim bokeh controls are visibly effective when USD DOF is off. + +## Camera LOP DS Registration + +Status: stable DCC discovery pattern. + +Evidence: + +- File: `moonray_dcc_plugins/houdini/python3.11libs/pythonrc.py`. +- Source DS: `moonray_dcc_plugins/houdini/soho/parameters/moonray_Camera.ds`. +- Installed package path: + `/Applications/MoonRay/installs/openmoonray/plugin/houdini`. + +Native contract: + +- `pythonrc.py` overrides `loputils.addRendererParmFolders()`. +- The hook searches for `soho/parameters/_.ds`. +- With the forced renderer name `moonray`, Camera LOP renderer properties + resolve to `soho/parameters/moonray_Camera.ds`. + +Why it mattered: + +- The first source-path HOM validation passed because the source + `HOUDINI_PATH` could find `moonray_Camera.ds`. +- A normal Houdini session did not show the controls because it loaded the + installed plugin tree, where the new DS file was missing. +- Running the existing DCC CMake install script synced the DS into the active + installed package and made the normal Camera LOP folder visible. + +Copy this: + +- For renderer-specific LOP UI work, identify the exact DS filename Houdini + expects before editing. +- Validate `hou.findFile()` in the same package/runtime path artists use. +- Confirm the installed DS matches source byte-for-byte after install sync. + +Avoid this: + +- Do not treat source-path HOM validation as enough proof for installed UI + behavior. +- Do not manually hack installed DS files outside the source/install path. + ## H20.5 Solaris Architecture Infrastructure Status: stable infrastructure evidence. @@ -267,4 +366,3 @@ Avoid this: - Do not start UI exposure from authoring-only success. - Do not claim AOV support from metadata/channels alone. - Do not leave unsafe diagnostics installed. - diff --git a/docs/hdMoonray_feature_implementation_pattern.md b/docs/hdMoonray_feature_implementation_pattern.md index 54da386..fd8799e 100644 --- a/docs/hdMoonray_feature_implementation_pattern.md +++ b/docs/hdMoonray_feature_implementation_pattern.md @@ -12,6 +12,9 @@ Evidence: - RectLight shaping commit `4396439` maps USD cone angle to the native MoonRay `RectLight.spread` attribute. - Geometry/subdivision commit `d85ec55` maps to native MoonRay mesh settings instead of inventing global renderer controls. - Material builder commit `952eea3` creates real MoonRay VOP material/displacement nodes rather than a fake material wrapper. +- Native Camera LOP controls use MoonRay `PerspectiveCamera` / + `OrthographicCamera` metadata for `mb_shutter_bias` and bokeh attributes, with + no new backend translation needed. Why it worked: @@ -97,6 +100,9 @@ Evidence: - SpotLight native controls were restored only after verifying MoonRay SpotLight attributes and backend consumption. - The native SpotLight helper authors `moonray:class_control = set` and `moonray:class = SpotLight` only when explicitly enabled. - Geometry settings DS exposure followed backend Mesh translation. +- Native Camera controls followed existing `Camera.cc` generic `moonray:*` + pass-through and `primaryCamera` copy support; the DCC DS only exposed already + consumed native attrs. - Render Settings LOP remains partial/WIP because AOV UI must not expose non-working production AOVs. Why it worked: @@ -109,12 +115,15 @@ Future work should copy: - For every UI parm, list the authored USD property and consumed MoonRay attribute. - Preserve original MoonRay metadata labels/help where possible. - Use explicit controls for renderer-specific modes. +- For DCC/DS work, prove the folder is visible in a fresh normal H20.5 session + using the installed package path. Future work should avoid: - UI checkboxes for AOVs whose production buffers are zero. - Restoring old controls only because they used to exist. - Default-authoring `moonray:class` for standard Solaris lights. +- Treating source-path HOM validation as installed UI proof. ## Pattern 5: Validate Semantic Classes And Attributes @@ -191,6 +200,47 @@ Future work should avoid: - Promoting a feature because EXR channels exist but pixels are empty. - Treating debug renderer behavior as production renderer behavior. +## Pattern 7A: Prove Installed Houdini UI Discovery + +Rule: for DS/HDA work, the UI is not proven until Houdini loads the same +package/runtime path artists use. + +Evidence: + +- Native MoonRay Camera controls were first validated with a source + `HOUDINI_PATH`, but a normal H20.5 Camera LOP still had no Moonray folder. +- `pythonrc.py` searches for `soho/parameters/_.ds`; for + the forced renderer name `moonray` and Camera LOP parm group this means + `soho/parameters/moonray_Camera.ds`. +- Normal H20.5 loaded DCC files from + `/Applications/MoonRay/installs/openmoonray/plugin/houdini`, where + `moonray_Camera.ds` was initially absent. +- Running + `cmake -P /Applications/MoonRay/build/moonray/moonray_dcc_plugins/houdini/cmake_install.cmake` + synced the source DS into the installed package. A fresh normal Camera LOP + then showed the Moonray folder and exported the expected USD attrs. + +Why it worked: + +- The validation target changed from "can source find the DS file?" to "does + normal Houdini load and display the DS file?". +- The fix used the existing source/install packaging path instead of a manual + installed-file edit. + +Future work should copy: + +- Run `hou.findFile()` for the expected DS file in the normal package path. +- Create a fresh node after install sync, because parameter templates can be + cached at node creation time. +- Confirm source and installed DS files match after the install step. + +Future work should avoid: + +- Adding a correct DS file only in source and reporting UI success from a custom + `HOUDINI_PATH`. +- Adding duplicate renderer DS files without understanding whether + `pythonrc.py` will load both. + ## Pattern 8: Keep WIP Work Clearly Marked Rule: partial progress should be documented, but not presented as supported behavior. @@ -237,6 +287,8 @@ Before exposing UI: - Is the authored USD inspectable? - Does RDLA/RDL prove the value? - Does H20.5 production behavior prove it? +- Does a fresh normal H20.5 session using the installed package path show the + intended parameter folder and controls? - Are original MoonRay labels/defaults/help preserved where possible? - Are defaults and UI ranges separate? @@ -246,4 +298,3 @@ Before committing: - Are only intended files changed? - Are submodule pins left alone until the submodule commit is pushed? - Is WIP explicitly marked? - diff --git a/docs/hdMoonray_stable_vs_wip_matrix.md b/docs/hdMoonray_stable_vs_wip_matrix.md index d0a1668..0cfbce3 100644 --- a/docs/hdMoonray_stable_vs_wip_matrix.md +++ b/docs/hdMoonray_stable_vs_wip_matrix.md @@ -32,6 +32,8 @@ Public PRs are useful anchors, but local submodule history is the source of trut | RectLight ShapingAPI behavior | Stable / proven | `4396439` | `Light.cc` | RectLight with authored ShapingAPI cone attrs remains MoonRay `RectLight`; cone angle maps to native `spread` as `clamp(angle, 0, 90) / 90`. | Strong example of preserving authored light class when MoonRay has a native equivalent. | | Geometry render settings exposure | Stable / proven | `d85ec55`, `24bac7e`, `moonray_dcc_plugins` `78ca757` | `Mesh.cc`, geometry DS files, geometry fixture README/USD | Exposes real MoonRay geometry/subdivision attributes and preserves authored primvars through `isPrimvarUsed()` checks. | Good example of backend defaults plus prim-level override controls rather than global renderer settings. | | Subdivision / tessellation exposure | Stable / proven | `d85ec55`, `24bac7e`, `78ca757` | `Mesh.cc`, `HdMoonrayRendererPlugin_Geometry.ds`, `testSuite/geometry/moonray_geometry_settings/*` | Maps USD subdivision/tessellation intent to native MoonRay mesh settings with tracked fixture evidence. | Future geometry work should follow this prim-level, metadata-backed pattern. | +| Native Camera controls: shutter bias and bokeh | Stable / proven for exposed controls only | Current local DCC quick win | `Camera.cc`, `moonray_Camera.ds`, `coredata/PerspectiveCamera.json`, `coredata/OrthographicCamera.json` | Camera LOP Moonray folder exposes `moonray:mb_shutter_bias` and bokeh attrs; defaults author nothing; explicit values author USD `moonray:*`; RDLA proves `PerspectiveCamera("primaryCamera")` receives shutter bias, USD DOF, and bokeh attrs when DOF is enabled. | Stable only for `mb_shutter_bias`, `bokeh`, `bokeh_sides`, `bokeh_image`, `bokeh_angle`, `bokeh_weight_location`, and `bokeh_weight_strength`. Camera class switching, alternate camera classes, stereo, `pixel_sample_map`, and bake/projector workflows remain deferred. | +| Camera LOP DS discovery / installed UI validation | Stable DCC infrastructure pattern | Current local DCC quick win | `pythonrc.py`, `moonray_Camera.ds`, DCC CMake install script | `pythonrc.py` searches `soho/parameters/_.ds`; Camera LOP with forced renderer `moonray` expects `moonray_Camera.ds`. Normal H20.5 loads `/Applications/MoonRay/installs/openmoonray/plugin/houdini`, so source-path HOM validation was insufficient until CMake install synced the DS file. | Future DS/HDA work must validate fresh normal H20.5 UI visibility against the installed package path. | | H20.5 Solaris architecture / infrastructure | Stable infrastructure | `77a673e` | `PrimTypeUtils.*`, `ValueConverter.cc`, `RenderSettings.*`, `UsdRenderers.json`, adapter compatibility doc, fixtures | Adds H20.5 compatibility, canonical prim type handling, value conversion, render settings plumbing, and test fixtures. | Reuse utilities and compatibility patterns, but do not treat the whole commit as a narrow feature recipe. | | Render Settings LOP | Partial / WIP | `moonray_dcc_plugins` `8c995f8` through `6cd0361` | `moonray_render_settings.py`, generated HDA, `moonray_render_settings_lop_audit.md`, validation script | Source-generated HDA, USD RenderSettings/Product/RenderVar foundation, and owned USD Render ROP lifecycle are useful. AOV integration and final UX remain unsettled. | Good process evidence for source-of-truth HDA generation; not yet a stable renderer-settings pattern. | | Beauty buffer / USD Render ROP support | Partial / WIP | hdMoonray `2eb0808` | `ArrasRenderer.cc`, `UsdRenderers.json` | Beauty path is useful progress, but non-beauty AOVs remain blocked. | Do not claim full image-buffer/AOV support from this commit. | @@ -47,11 +49,13 @@ Use these as primary examples: - Explicit native SpotLight override and UI helper. - RectLight ShapingAPI-to-`spread` preservation. - Geometry/subdivision/tessellation prim-level exposure. +- Native Camera LOP shutter-bias and bokeh controls. Use these only as supporting infrastructure examples: - H20.5 Solaris architecture commit `77a673e`. - RenderSettings source-generated HDA mechanics. +- Camera LOP DS discovery and installed-package validation. Use these only as cautionary examples: @@ -68,4 +72,3 @@ An area may move to stable only when the relevant layer has proof: - H20.5 production render or Houdini UI behavior proves the user-facing result. - EXR stats prove image data when the feature is image-buffer/AOV related. - The repo, build, install, and runtime path were verified. - diff --git a/docs/hdMoonray_validation_workflow.md b/docs/hdMoonray_validation_workflow.md index 4ee1e34..53c16fe 100644 --- a/docs/hdMoonray_validation_workflow.md +++ b/docs/hdMoonray_validation_workflow.md @@ -48,6 +48,24 @@ strings | rg "" The AOV audit showed that stale installed dylibs can invalidate an otherwise careful investigation. Remove diagnostic strings and prove they are gone before final reporting. +For Houdini DCC/DS work, also prove the installed package path. Source-path HOM +validation can find a new DS file that artists do not actually load. In the +native Camera controls pass, a source `HOUDINI_PATH` found +`moonray_Camera.ds`, but a normal H20.5 session loaded: + +```text +/Applications/MoonRay/installs/openmoonray/plugin/houdini +``` + +and initially had no `moonray_Camera.ds`. The existing install step: + +```sh +cmake -P /Applications/MoonRay/build/moonray/moonray_dcc_plugins/houdini/cmake_install.cmake +``` + +synced the source DS into the installed package. The final UI proof came from a +fresh normal H20.5 Camera LOP, not from the source-path check alone. + ## Light Validation For light work, validate each layer: @@ -115,6 +133,56 @@ Avoid: - Fake material wrappers. - Locked HDAs when a native editable subnet pattern fits Houdini better. +## Camera Controls Validation + +Native Camera controls are stable only for the first exposed MoonRay attrs: + +- `moonray:mb_shutter_bias` +- `moonray:bokeh` +- `moonray:bokeh_sides` +- `moonray:bokeh_image` +- `moonray:bokeh_angle` +- `moonray:bokeh_weight_location` +- `moonray:bokeh_weight_strength` + +For camera UI work: + +1. Check native MoonRay camera metadata, such as + `coredata/PerspectiveCamera.json` and `coredata/OrthographicCamera.json`. +2. Check `Camera.cc` for existing standard USD camera mapping and generic + `moonray:*` pass-through. +3. Confirm the selected attrs are copied to `primaryCamera` if the active render + path uses `primaryCamera`. +4. Identify the correct Camera LOP renderer DS file. With the current + `pythonrc.py` hook, Camera LOP Moonray properties resolve to + `soho/parameters/moonray_Camera.ds`. +5. Validate with the normal installed H20.5 package path: + `hou.findFile("soho/parameters/moonray_Camera.ds")` should resolve under + `/Applications/MoonRay/installs/openmoonray/plugin/houdini`. +6. Create a fresh Camera LOP after install sync and confirm the Moonray folder + and controls are visible. +7. Export default USD and confirm no `moonray:*` camera attrs are authored. +8. Explicitly set the controls and confirm the expected custom USD attrs. +9. Dump RDLA/RDL and confirm `PerspectiveCamera("primaryCamera")` receives + `mb_shutter_bias`, `dof`, `dof_aperture`, `dof_focus_distance`, and the + bokeh attrs when USD DOF is enabled. + +DOF dependency: + +- Bokeh controls require standard USD DOF, usually `fStop` plus + `focusDistance`, to affect `primaryCamera` and the render visibly. +- With DOF off, bokeh attrs may exist on the authored camera object but are not + copied to `primaryCamera`; do not present them as visibly effective in that + state. + +Deferred camera work: + +- `moonray:class` validation against `INTERFACE_CAMERA`. +- FisheyeCamera, SphericalCamera, DomeMaster3DCamera, and BakeCamera. +- Stereo controls. +- `pixel_sample_map`. +- Medium/material/projector/bake camera workflows. + ## Render Settings Validation Render Settings LOP is partial/WIP. Use this workflow before declaring any part stable: @@ -210,4 +278,3 @@ Before ending a pass: - Only intended files changed. - No generated files are stale. - Parent submodule pins untouched unless explicitly requested. - From 636c69ff8f72e0cfad772e52ee1c3eee9135d039 Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:48:49 +0200 Subject: [PATCH 13/15] hdMoonray: light filter adaptation for exposure Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/LightFilter.cc | 11 ++++++++- plugin/adapters/MoonrayLightFilterAdapter.cc | 26 ++++++++++++++++++++ plugin/adapters/MoonrayLightFilterAdapter.h | 10 ++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/hydramoonray/LightFilter.cc b/lib/hydramoonray/LightFilter.cc index 7170d37..14e01c0 100644 --- a/lib/hydramoonray/LightFilter.cc +++ b/lib/hydramoonray/LightFilter.cc @@ -25,6 +25,7 @@ namespace { using scene_rdl2::logging::Logger; pxr::TfToken moonrayClassToken("moonray:class"); +pxr::TfToken infoIdToken("info:id"); pxr::TfToken fallbackClass("DecayLightFilter"); #if PXR_VERSION >= 2005 @@ -68,6 +69,10 @@ LightFilter::syncParams(const pxr::SdfPath& id, } else { pxr::TfToken moonrayName("moonray:" + attrName); pxr::VtValue val = sceneDelegate->GetLightParamValue(id, moonrayName); + if (val.IsEmpty()) { + pxr::TfToken inputName("inputs:" + attrName); + val = sceneDelegate->GetLightParamValue(id, inputName); + } if (val.IsEmpty()) { ValueConverter::setDefault(mLightFilter, *it); } else { @@ -212,9 +217,14 @@ LightFilter::getOrCreateFilter(pxr::HdSceneDelegate *sceneDelegate, std::lock_guard lock(mCreateMutex); if (not mLightFilter) { pxr::VtValue vtClass = sceneDelegate->GetLightParamValue(id, moonrayClassToken); + if (vtClass.IsEmpty()) { + vtClass = sceneDelegate->GetLightParamValue(id, infoIdToken); + } pxr::TfToken classToken; if (vtClass.IsHolding()) { classToken = vtClass.UncheckedGet(); + } else if (vtClass.IsHolding()) { + classToken = pxr::TfToken(vtClass.UncheckedGet()); } else { classToken = fallbackClass; Logger::warn("hdMoonray: Unspecified LightFilter type : creating ", classToken); @@ -284,4 +294,3 @@ LightFilter::getFilter(pxr::HdSceneDelegate* sceneDelegate, } } - diff --git a/plugin/adapters/MoonrayLightFilterAdapter.cc b/plugin/adapters/MoonrayLightFilterAdapter.cc index f96216e..d2179e3 100644 --- a/plugin/adapters/MoonrayLightFilterAdapter.cc +++ b/plugin/adapters/MoonrayLightFilterAdapter.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include "pxr/imaging/hd/tokens.h" @@ -28,6 +29,24 @@ MoonrayLightFilterAdapter::~MoonrayLightFilterAdapter() { } +bool +MoonrayLightFilterAdapter::IsSupported( + UsdImagingIndexProxy const* index) const +{ + return index->IsSprimTypeSupported(HdPrimTypeTokens->lightFilter); +} + +SdfPath +MoonrayLightFilterAdapter::Populate(UsdPrim const& prim, + UsdImagingIndexProxy* index, + UsdImagingInstancerContext const* instancerContext) +{ + index->InsertSprim(HdPrimTypeTokens->lightFilter, prim.GetPath(), prim); + HD_PERF_COUNTER_INCR(UsdImagingTokens->usdPopulatedPrimCount); + + return prim.GetPath(); +} + VtValue MoonrayLightFilterAdapter::Get( UsdPrim const& prim, @@ -58,5 +77,12 @@ MoonrayLightFilterAdapter::Get( return BaseAdapter::Get(prim, cachePath, key, time, outIndices); } +void +MoonrayLightFilterAdapter::_RemovePrim(SdfPath const& cachePath, + UsdImagingIndexProxy* index) +{ + index->RemoveSprim(HdPrimTypeTokens->lightFilter, cachePath); +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/adapters/MoonrayLightFilterAdapter.h b/plugin/adapters/MoonrayLightFilterAdapter.h index 642bbd9..25e2076 100644 --- a/plugin/adapters/MoonrayLightFilterAdapter.h +++ b/plugin/adapters/MoonrayLightFilterAdapter.h @@ -25,12 +25,22 @@ class MoonrayLightFilterAdapter : public UsdImagingLightFilterAdapter { virtual ~MoonrayLightFilterAdapter(); + virtual SdfPath Populate(UsdPrim const& prim, + UsdImagingIndexProxy* index, + UsdImagingInstancerContext const* instancerContext = NULL); + + virtual bool IsSupported(UsdImagingIndexProxy const* index) const; + virtual VtValue Get(UsdPrim const& prim, SdfPath const& cachePath, TfToken const& key, UsdTimeCode time, VtIntArray* outIndices) const; +protected: + virtual void _RemovePrim(SdfPath const& cachePath, + UsdImagingIndexProxy* index) final; + }; PXR_NAMESPACE_CLOSE_SCOPE From f3d66dc0fb1ec4d1f5176a9f2f3d89a9e2cdef7c Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:00:34 +0200 Subject: [PATCH 14/15] hdMoonray: expand light filter ramp interpolation tokens Solaris authors Houdini ramp interpolation as a scalar token while ramp positions and values are authored as arrays. The existing token to IntVector fallback used the RDL default vector length, which worked for fixed-size material ramps such as iridescence but failed for editable light filter ramps once the ramp point count exceeded the RDL default. Expand light filter interpolation tokens to the authored ramp point count before passing them to ValueConverter. Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/LightFilter.cc | 124 ++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/lib/hydramoonray/LightFilter.cc b/lib/hydramoonray/LightFilter.cc index 14e01c0..554cc2a 100644 --- a/lib/hydramoonray/LightFilter.cc +++ b/lib/hydramoonray/LightFilter.cc @@ -17,7 +17,10 @@ #include #include #include +#include +#include #include +#include using namespace scene_rdl2::rdl2; @@ -28,6 +31,126 @@ pxr::TfToken moonrayClassToken("moonray:class"); pxr::TfToken infoIdToken("info:id"); pxr::TfToken fallbackClass("DecayLightFilter"); +std::string +normalizedRampToken(const std::string& value) +{ + std::string result = value; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { + if (c == '-' || c == ' ') return '_'; + return static_cast(std::tolower(c)); + }); + return result; +} + +bool +rampInterpolationTokenToInt(const std::string& token, int* out) +{ + const std::string key = normalizedRampToken(token); + + if (key == "none" || key == "constant") { + *out = 0; + return true; + } + if (key == "linear") { + *out = 1; + return true; + } + if (key == "exponential_up" || key == "ease_in") { + *out = 2; + return true; + } + if (key == "exponential_down" || key == "ease_out") { + *out = 3; + return true; + } + if (key == "smooth" || key == "smoothstep" || key == "hermite" || + key == "bezier" || key == "bspline" || key == "b_spline") { + *out = 4; + return true; + } + if (key == "catmull_rom" || key == "catmullrom") { + *out = 5; + return true; + } + if (key == "monotone_cubic" || key == "monotonecubic") { + *out = 6; + return true; + } + return false; +} + +int +getAuthoredRampCount(const pxr::SdfPath& id, + const std::string& attrName, + pxr::HdSceneDelegate* sceneDelegate) +{ + pxr::VtValue countVal = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:ramp")); + if (countVal.IsHolding()) { + return countVal.UncheckedGet(); + } + if (countVal.IsHolding()) { + return static_cast(countVal.UncheckedGet()); + } + if (countVal.IsHolding()) { + return static_cast(countVal.UncheckedGet()); + } + + if (attrName == "interpolation_types") { + pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:distances")); + if (positions.IsHolding>()) { + return static_cast(positions.UncheckedGet>().size()); + } + } else if (attrName == "ramp_interpolation_types") { + pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:ramp_in_distances")); + if (positions.IsHolding>()) { + return static_cast(positions.UncheckedGet>().size()); + } + } else if (attrName == "density_remap_interpolation_types") { + pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:density_remap_inputs")); + if (positions.IsHolding>()) { + return static_cast(positions.UncheckedGet>().size()); + } + } + + return 0; +} + +pxr::VtValue +expandRampInterpolationToken(const pxr::VtValue& val, + const pxr::SdfPath& id, + const std::string& attrName, + pxr::HdSceneDelegate* sceneDelegate) +{ + if (attrName.find("interpolation") == std::string::npos) { + return val; + } + + std::string token; + if (val.IsHolding()) { + token = val.UncheckedGet().GetString(); + } else if (val.IsHolding()) { + token = val.UncheckedGet(); + } else { + return val; + } + + int interpolation = 0; + if (!rampInterpolationTokenToInt(token, &interpolation)) { + return val; + } + + const int count = getAuthoredRampCount(id, attrName, sceneDelegate); + if (count <= 0) { + return val; + } + + pxr::VtArray values; + values.resize(count); + std::fill(values.begin(), values.end(), interpolation); + return pxr::VtValue(values); +} + #if PXR_VERSION >= 2005 # define lightFilterToken (pxr::HdPrimTypeTokens->lightFilter) # define lightFilterLinkToken (pxr::HdTokens->lightFilterLink) @@ -76,6 +199,7 @@ LightFilter::syncParams(const pxr::SdfPath& id, if (val.IsEmpty()) { ValueConverter::setDefault(mLightFilter, *it); } else { + val = expandRampInterpolationToken(val, id, attrName, sceneDelegate); ValueConverter::setAttribute(mLightFilter, *it, val); } } From 2ec784869483836affdddc24fa071d5ec7f23e8b Mon Sep 17 00:00:00 2001 From: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:41:24 +0200 Subject: [PATCH 15/15] hdMoonray: stabilize light filter lifecycle and links Signed-off-by: Jakub Svoboda <132791205+rolledhand@users.noreply.github.com> --- lib/hydramoonray/Light.cc | 2 +- lib/hydramoonray/LightFilter.cc | 119 +++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 35 deletions(-) diff --git a/lib/hydramoonray/Light.cc b/lib/hydramoonray/Light.cc index f917c16..d6f3d32 100644 --- a/lib/hydramoonray/Light.cc +++ b/lib/hydramoonray/Light.cc @@ -301,7 +301,7 @@ Light::syncParams(const pxr::SdfPath& id, // assumes that the geom uses the same delegate as the light, but I don't think // there is an alternative in Hydra 1 pxr::SdfPath geomPath = relval.UncheckedGet(); - geomPath.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); + geomPath = geomPath.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); pxr::HdRprim* prim = const_cast(sceneDelegate->GetRenderIndex().GetRprim(geomPath)); Mesh* mesh = dynamic_cast(prim); if (mesh) { diff --git a/lib/hydramoonray/LightFilter.cc b/lib/hydramoonray/LightFilter.cc index 554cc2a..2f22003 100644 --- a/lib/hydramoonray/LightFilter.cc +++ b/lib/hydramoonray/LightFilter.cc @@ -31,6 +31,22 @@ pxr::TfToken moonrayClassToken("moonray:class"); pxr::TfToken infoIdToken("info:id"); pxr::TfToken fallbackClass("DecayLightFilter"); +void +makeLightFilterInert(scene_rdl2::rdl2::LightFilter* lightFilter, + hdMoonray::RenderDelegate& renderDelegate) +{ + if (!lightFilter) { + return; + } + + hdMoonray::UpdateGuard guard(renderDelegate, lightFilter); + const SceneClass& sceneClass = lightFilter->getSceneClass(); + const Attribute* onAttr = sceneClass.getAttribute("on"); + if (onAttr && onAttr->getType() == TYPE_BOOL) { + lightFilter->set(AttributeKey(*onAttr), false); + } +} + std::string normalizedRampToken(const std::string& value) { @@ -85,6 +101,25 @@ getAuthoredRampCount(const pxr::SdfPath& id, const std::string& attrName, pxr::HdSceneDelegate* sceneDelegate) { + // Prefer the authored ramp position array length because it is the value + // that must match the ramp value and interpolation vectors. inputs:ramp is + // authored by Houdini as the UI point count and is only used as a fallback. + const char* positionsName = nullptr; + if (attrName == "interpolation_types") { + positionsName = "inputs:distances"; + } else if (attrName == "ramp_interpolation_types") { + positionsName = "inputs:ramp_in_distances"; + } else if (attrName == "density_remap_interpolation_types") { + positionsName = "inputs:density_remap_inputs"; + } + + if (positionsName) { + pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken(positionsName)); + if (positions.IsHolding>()) { + return static_cast(positions.UncheckedGet>().size()); + } + } + pxr::VtValue countVal = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:ramp")); if (countVal.IsHolding()) { return countVal.UncheckedGet(); @@ -96,23 +131,6 @@ getAuthoredRampCount(const pxr::SdfPath& id, return static_cast(countVal.UncheckedGet()); } - if (attrName == "interpolation_types") { - pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:distances")); - if (positions.IsHolding>()) { - return static_cast(positions.UncheckedGet>().size()); - } - } else if (attrName == "ramp_interpolation_types") { - pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:ramp_in_distances")); - if (positions.IsHolding>()) { - return static_cast(positions.UncheckedGet>().size()); - } - } else if (attrName == "density_remap_interpolation_types") { - pxr::VtValue positions = sceneDelegate->GetLightParamValue(id, pxr::TfToken("inputs:density_remap_inputs")); - if (positions.IsHolding>()) { - return static_cast(positions.UncheckedGet>().size()); - } - } - return 0; } @@ -140,6 +158,10 @@ expandRampInterpolationToken(const pxr::VtValue& val, return val; } + // ValueConverter can expand a scalar ramp token using the RDL scene-class + // default vector size. Solaris light filter ramps can author a different + // editable point count on the prim, so expand to that authored count here + // before generic conversion. const int count = getAuthoredRampCount(id, attrName, sceneDelegate); if (count <= 0) { return val; @@ -218,7 +240,7 @@ LightFilter::syncProjector(const pxr::SdfPath& id, pxr::VtValue val = sceneDelegate->Get(id, projectorToken); // supplied by adapter if (val.IsHolding()) { pxr::SdfPath path = val.UncheckedGet(); - path.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); + path = path.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); SceneObject* so = hdMoonray::Camera::createCamera(sceneDelegate, renderDelegate, path); mLightFilter->set("projector", so); if (not so) { @@ -270,7 +292,7 @@ LightFilter::syncCombineFilters(const pxr::SdfPath& id, pxr::SdfPathVector pathVec = val.UncheckedGet(); for (const pxr::SdfPath& cpath : pathVec) { pxr::SdfPath path(cpath); - path.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); + path = path.ReplacePrefix(pxr::SdfPath::AbsoluteRootPath(), sceneDelegate->GetDelegateID()); SceneObject* so = LightFilter::getFilter(sceneDelegate, renderDelegate, path); if (so) { rdlObjects.push_back(so); @@ -339,21 +361,40 @@ LightFilter::getOrCreateFilter(pxr::HdSceneDelegate *sceneDelegate, const pxr::SdfPath& id) { std::lock_guard lock(mCreateMutex); + + pxr::VtValue vtClass = sceneDelegate->GetLightParamValue(id, moonrayClassToken); + if (vtClass.IsEmpty()) { + vtClass = sceneDelegate->GetLightParamValue(id, infoIdToken); + } + + pxr::TfToken classToken; + if (vtClass.IsHolding()) { + classToken = vtClass.UncheckedGet(); + } else if (vtClass.IsHolding()) { + classToken = pxr::TfToken(vtClass.UncheckedGet()); + } else { + classToken = fallbackClass; + Logger::warn("hdMoonray: Unspecified LightFilter type : creating ", classToken); + } + + if (mLightFilter && mLightFilter->getSceneClass().getName() != classToken.GetString()) { + makeLightFilterInert(mLightFilter, renderDelegate); + renderDelegate.releaseCategory(mLightFilter, RenderDelegate::CategoryType::FilterLink, mLightFilterCategory); + mLightFilter = nullptr; + mLightFilterCategory = pxr::TfToken(); + } + if (not mLightFilter) { - pxr::VtValue vtClass = sceneDelegate->GetLightParamValue(id, moonrayClassToken); - if (vtClass.IsEmpty()) { - vtClass = sceneDelegate->GetLightParamValue(id, infoIdToken); + SceneObject* sceneObject = renderDelegate.createSceneObject(classToken.GetString(), id); + if (!sceneObject) { + Logger::error(id, ": failed to create MoonRay LightFilter class '", classToken, "'"); + return nullptr; } - pxr::TfToken classToken; - if (vtClass.IsHolding()) { - classToken = vtClass.UncheckedGet(); - } else if (vtClass.IsHolding()) { - classToken = pxr::TfToken(vtClass.UncheckedGet()); - } else { - classToken = fallbackClass; - Logger::warn("hdMoonray: Unspecified LightFilter type : creating ", classToken); + mLightFilter = sceneObject->asA(); + if (!mLightFilter) { + Logger::error(id, ": MoonRay scene object class '", classToken, "' is not a LightFilter"); + return nullptr; } - mLightFilter = renderDelegate.createSceneObject(classToken.GetString(), id)->asA(); // See Light.cc for explanation... pxr::VtValue val = sceneDelegate->GetLightParamValue(id, lightFilterLinkToken); @@ -374,11 +415,15 @@ LightFilter::Sync(pxr::HdSceneDelegate *sceneDelegate, hdmLogSyncStart("LightFilter", id, dirtyBits); RenderDelegate& renderDelegate(RenderDelegate::get(renderParam)); - getOrCreateFilter(sceneDelegate,renderDelegate,id); + if (!getOrCreateFilter(sceneDelegate, renderDelegate, id)) { + *dirtyBits = pxr::HdChangeTracker::Clean; + hdmLogSyncEnd(id); + return; + } if ((*dirtyBits) & pxr::HdChangeTracker::DirtyParams) { UpdateGuard guard(renderDelegate, mLightFilter); - syncParams(id,sceneDelegate, renderDelegate); + syncParams(id, sceneDelegate, renderDelegate); } *dirtyBits = pxr::HdChangeTracker::Clean; @@ -388,8 +433,14 @@ LightFilter::Sync(pxr::HdSceneDelegate *sceneDelegate, void LightFilter::Finalize(pxr::HdRenderParam *renderParam) { + if (!mLightFilter) { + return; + } RenderDelegate& renderDelegate(RenderDelegate::get(renderParam)); - renderDelegate.releaseCategory(mLightFilter,RenderDelegate::CategoryType::FilterLink,mLightFilterCategory); + makeLightFilterInert(mLightFilter, renderDelegate); + renderDelegate.releaseCategory(mLightFilter, RenderDelegate::CategoryType::FilterLink, mLightFilterCategory); + mLightFilter = nullptr; + mLightFilterCategory = pxr::TfToken(); } /*static*/ LightFilter*