From a40506c73f5e02d06c490087b7a9c6179dcbeb85 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 16 Jan 2026 11:27:04 +0000 Subject: [PATCH 1/2] ShaderNetworkAlgo : Add render adaptor API This has been lifted from IECoreArnold, because we now want to provide it in all renderer backends. In IECoreArnold it was described as a "substitutions" API, but I've characterised it as a "render adaptor" API here as it isn't limited to substitutions and we want to make it clear that it applies at render time. There are no Python bindings because doing anything with Python would be a performance issue when translating scenes at scale. Test coverage will be provided by IECoreArnold, IECoreRenderMan etc. --- Changes | 6 ++ include/IECoreScene/ShaderNetworkAlgo.h | 32 +++++++++ src/IECoreScene/ShaderNetworkAlgo.cpp | 89 +++++++++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/Changes b/Changes index cfa6696709..3fd5048420 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,12 @@ 10.5.x.x (relative to 10.5.15.4) ======== +Improvements +------------ + +- ShaderNetworkAlgo : Added render adaptor API, allowing just-in-time modifications to + ShaderNetworks during translation for rendering. + 10.5.15.4 (relative to 10.5.15.3) ======== diff --git a/include/IECoreScene/ShaderNetworkAlgo.h b/include/IECoreScene/ShaderNetworkAlgo.h index e7e7ef445c..a97363090c 100644 --- a/include/IECoreScene/ShaderNetworkAlgo.h +++ b/include/IECoreScene/ShaderNetworkAlgo.h @@ -154,6 +154,38 @@ IECORESCENE_API IECore::ConstCompoundDataPtr collapseSplineParameters( const IEC /// \deprecated: Use expandSplines on the whole network, which can handle input connections IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); +/// Render Adaptors +/// =============== +/// +/// Render adaptors are functions called to make "just in time" modifications to +/// shader networks prior to rendering. They have access to a fully resolved +/// attribute state, which can be used to drive the modifications. There is currently +/// a single built-in adaptor, which uses `ShaderNetworkAlgo::applySubstitutions()` +/// to allow strings such as texture paths to be substituted in from attributes that +/// define them. +/// +/// Adaptors are performance critical, so are deliberately not available via +/// the Python API. They must be implemented in C++ only. + +/// A function that adapts the shader network for rendering, given the full +/// inherited `attributes` for an object. Must be threadsafe. +using RenderAdaptorFunction = void (*)( ShaderNetwork *shaderNetwork, IECore::InternedString attributeName, const IECore::CompoundObject *attributes ); +/// A function that appends to `hash` to uniquely identify the work that will be +/// performed by an AdaptorFunction. Particular attention must be paid to +/// the performance of any such function, as it will be called frequently. If an +/// adaptor will be a no-op, then nothing should be appended to the hash. +/// Must be threadsafe. +using RenderAdaptorHashFunction = void (*)( const ShaderNetwork *shaderNetwork, IECore::InternedString attributeName, const IECore::CompoundObject *attributes, IECore::MurmurHash &hash ); + +/// Registers an adaptor. +IECORESCENE_API void registerRenderAdaptor( const std::string &name, RenderAdaptorHashFunction hashFunction, RenderAdaptorFunction adaptorFunction ); +/// Removes a previously registered adaptor with the specified name. +IECORESCENE_API void deregisterRenderAdaptor( const std::string &name ); + +/// Hashes all the currently registered adaptors for `shaderNetwork`. +IECORESCENE_API void hashRenderAdaptors( const ShaderNetwork *shaderNetwork, IECore::InternedString attributeName, const IECore::CompoundObject *attributes, IECore::MurmurHash &hash ); +/// Applies all the currently registered adaptors to `shaderNetwork`. +IECORESCENE_API void applyRenderAdaptors( ShaderNetwork *shaderNetwork, IECore::InternedString attributeName, const IECore::CompoundObject *attributes ); } // namespace ShaderNetworkAlgo diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 51230dc994..f88496ae81 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -1394,3 +1394,92 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IE return parametersData; } } + + +////////////////////////////////////////////////////////////////////////// +// Render Adaptors +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +struct RenderAdaptor +{ + std::string name; + IECoreScene::ShaderNetworkAlgo::RenderAdaptorHashFunction hash; + IECoreScene::ShaderNetworkAlgo::RenderAdaptorFunction apply; +}; + +using RenderAdaptors = std::vector; +RenderAdaptors &renderAdaptors() +{ + static RenderAdaptors g_renderAdaptors; + return g_renderAdaptors; +} + +bool g_stringSubstitutionsRegistration = [] () { + + IECoreScene::ShaderNetworkAlgo::registerRenderAdaptor( + "stringSubstitution", + // Hash + [] ( const IECoreScene::ShaderNetwork *shaderNetwork, InternedString attributeName, const IECore::CompoundObject *attributes, IECore::MurmurHash &hash ) { + shaderNetwork->hashSubstitutions( attributes, hash ); + }, + // Apply + [] ( IECoreScene::ShaderNetwork *shaderNetwork, InternedString attributeName, const IECore::CompoundObject *attributes ) { + shaderNetwork->applySubstitutions( attributes ); + } + ); + + return true; +} (); + +} // namespace + +void ShaderNetworkAlgo::registerRenderAdaptor( const std::string &name, RenderAdaptorHashFunction hashFunction, RenderAdaptorFunction adaptorFunction ) +{ + // Replace existing adaptor if it exists. + RenderAdaptors &a = renderAdaptors(); + for( auto &x : a ) + { + if( x.name == name ) + { + x.hash = hashFunction; + x.apply = adaptorFunction; + return; + } + } + // Otherwise add new adaptor. + a.push_back( { name, hashFunction, adaptorFunction } ); +} + +void ShaderNetworkAlgo::deregisterRenderAdaptor( const std::string &name ) +{ + RenderAdaptors &a = renderAdaptors(); + a.erase( + std::remove_if( + a.begin(), + a.end(), + [&] ( const RenderAdaptor &x ) { + return x.name == name; + } + ), + a.end() + ); +} + +void ShaderNetworkAlgo::hashRenderAdaptors( const IECoreScene::ShaderNetwork *shaderNetwork, InternedString attributeName, const IECore::CompoundObject *attributes, IECore::MurmurHash &hash ) +{ + for( const auto &x : renderAdaptors() ) + { + x.hash( shaderNetwork, attributeName, attributes, hash ); + } +} + +void ShaderNetworkAlgo::applyRenderAdaptors( IECoreScene::ShaderNetwork *shaderNetwork, InternedString attributeName, const IECore::CompoundObject *attributes ) +{ + for( const auto &x : renderAdaptors() ) + { + x.apply( shaderNetwork, attributeName, attributes ); + } +} From 6420b6fb851e9477ed08f44a83f35185a525753d Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 16 Jan 2026 12:56:20 +0000 Subject: [PATCH 2/2] SConstruct : Remove unused imports This is critical on Windows CI, because GitHub has just updated the default Python version to 3.12, which no longer includes `distutils`. See https://github.com/actions/runner-images/issues/13468. --- SConstruct | 3 --- 1 file changed, 3 deletions(-) diff --git a/SConstruct b/SConstruct index f4188a56d5..fe4d4c102c 100644 --- a/SConstruct +++ b/SConstruct @@ -41,15 +41,12 @@ # ########################################################################## -import SCons -import shutil import glob import sys import os import re import subprocess import platform -import distutils EnsureSConsVersion( 3, 0, 2 ) # Substfile is a default builder as of 3.0.2 SConsignFile()