From acd9baa689aa5a92097afd3a65efbef04dddb1b8 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 2 Mar 2023 14:01:41 -0800 Subject: [PATCH 1/5] ShaderNetworkAlgo : Remove unused variables --- src/IECoreScene/ShaderNetworkAlgo.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 286b7f00b0..9670e87dbd 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -293,8 +293,6 @@ void ShaderNetworkAlgo::removeComponentConnectionAdapters( ShaderNetwork *networ { std::vector< IECore::InternedString > toRemove; - using ParameterMap = std::unordered_map; - ParameterMap outputConversions; for( const auto &s : network->shaders() ) { ConstBoolDataPtr labelValue = s.second->blindData()->member( componentConnectionAdapterLabel() ); @@ -434,8 +432,6 @@ void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network, // We have an OSL version that supports component connections. // But OSL uses `[0]` rather than `.r` suffix style, so translate the connection names - using ParameterMap = std::unordered_map; - ParameterMap outputConversions; for( const auto &s : network->shaders() ) { bool destIsOSL = boost::starts_with( s.second->getType(), "osl:" ); From 45606eeae504535366b6b5d507cf7876da943a9b Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Wed, 22 Mar 2023 20:59:29 -0700 Subject: [PATCH 2/5] StringAlgo/InternedString : Helpers for string ops without allocs --- include/IECore/InternedString.h | 1 + include/IECore/InternedString.inl | 5 ++ include/IECore/StringAlgo.h | 9 ++++ include/IECore/StringAlgo.inl | 78 +++++++++++++++++++++++++++++++ src/IECore/StringAlgo.cpp | 2 +- 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/include/IECore/InternedString.h b/include/IECore/InternedString.h index b2b7ba87a4..30c6d7de08 100644 --- a/include/IECore/InternedString.h +++ b/include/IECore/InternedString.h @@ -68,6 +68,7 @@ class IECORE_API InternedString inline InternedString( const std::string &value ); inline InternedString( const char *value ); inline InternedString( const char *value, size_t length ); + inline InternedString( const std::string_view &value ); InternedString( const InternedString &other ) = default; InternedString &operator= ( const InternedString &rhs ) = default; diff --git a/include/IECore/InternedString.inl b/include/IECore/InternedString.inl index 41c3d019d0..3e1af10c3c 100644 --- a/include/IECore/InternedString.inl +++ b/include/IECore/InternedString.inl @@ -58,6 +58,11 @@ inline InternedString::InternedString( const char *value, size_t length ) { } +inline InternedString::InternedString( const std::string_view &value ) + : m_value( internedString( value.data(), value.size() ) ) +{ +} + #if BOOST_VERSION > 105500 inline InternedString::InternedString( const boost::string_view &value ) diff --git a/include/IECore/StringAlgo.h b/include/IECore/StringAlgo.h index 6a9deed088..b3e26873cf 100644 --- a/include/IECore/StringAlgo.h +++ b/include/IECore/StringAlgo.h @@ -180,6 +180,15 @@ bool isUpperCase( const String &s ); template bool isLowerCase( const String &s ); +/// Convert a sequence of characters given as a string_view to an integer. +/// Throws an exception if the characters do not form a valid integer. +int toInt( const std::string_view &s ); + +/// Concatenate any mixture of strings, string_views, and string literals, efficiently into a new string. +/// Similar to the std::concat proposed for the standard by P1228, but not yet adopted. +template +std::string concat( StringsFoldType const& ... strs); + } // namespace StringAlgo } // namespace IECore diff --git a/include/IECore/StringAlgo.inl b/include/IECore/StringAlgo.inl index 80293617e5..24c746bb54 100644 --- a/include/IECore/StringAlgo.inl +++ b/include/IECore/StringAlgo.inl @@ -35,6 +35,9 @@ #ifndef IECORE_STRINGALGO_INL #define IECORE_STRINGALGO_INL +#include "IECore/Exception.h" + +#include #include namespace IECore @@ -171,6 +174,40 @@ inline bool matchInternal( const char *s, const char *&pattern, bool spaceTermin } } +template +constexpr auto stringData( const String& str ) +{ + if constexpr( std::is_array_v< String > ) + { + return str; + } + else if constexpr( std::is_pointer_v ) + { + return str; + } + else + { + return std::data( str ); + } +} + +template +constexpr size_t stringSize( const String & str ) +{ + if constexpr( std::is_array_v< String > ) + { + return std::char_traits< typename std::remove_all_extents< String >::type >::length( str ); + } + else if constexpr( std::is_pointer_v ) + { + return std::char_traits>::length( str ); + } + else + { + return std::size( str ); + } +} + } // namespace Detail namespace StringAlgo @@ -282,6 +319,47 @@ bool isLowerCase( const String &s ) return haveAlpha; } +inline int toInt( const std::string_view &s ) +{ + int result = 0; + + auto elementIdResult = std::from_chars( s.data(), s.data() + s.size(), result ); + if( elementIdResult.ec == std::errc::invalid_argument || elementIdResult.ptr != s.data() + s.size() ) + { + throw IECore::Exception( StringAlgo::concat( "Invalid integer ", s ) ); + } + + return result; +} + +template +std::string concat( StringsFoldType const& ... strs ) +{ + // Adapted from various posts on Stackoverflow linking to Godbolt links ... it's been passed + // around a bunch, not clear who first wrote it + + std::string result; + + // C++17 fold for summation + result.resize( ( 0 + ... + Detail::stringSize( strs ) ) ); + + size_t pos = 0; + + // C++17 fold for function calls. + ( + ( + std::copy( + Detail::stringData( strs ), + Detail::stringData( strs ) + Detail::stringSize( strs ), + result.data() + pos + ), + pos += Detail::stringSize(strs) + ), ... + ); + + return result; +} + } // namespace StringAlgo } // namespace IECore diff --git a/src/IECore/StringAlgo.cpp b/src/IECore/StringAlgo.cpp index b36753c81e..c1772262a6 100644 --- a/src/IECore/StringAlgo.cpp +++ b/src/IECore/StringAlgo.cpp @@ -421,7 +421,7 @@ int numericSuffix( const std::string &s, std::string *stem ) { *stem = match[1]; } - return boost::lexical_cast( match[2] ); + return StringAlgo::toInt( std::string_view( s.data() + match.position(2), match.length( 2 ) ) ); } if( stem ) { From 878782c92c121fa12807457cbe03f53226bca050 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Fri, 3 Mar 2023 15:12:52 -0800 Subject: [PATCH 3/5] ShaderNetworkAlgo : Add expandSplines, collapseSplines, and ... convertToOSLConventions --- include/IECoreScene/ShaderNetworkAlgo.h | 46 +- src/IECoreScene/ShaderNetworkAlgo.cpp | 484 ++++++++++++++++-- .../bindings/ShaderNetworkAlgoBinding.cpp | 3 + test/IECoreScene/ShaderNetworkAlgoTest.py | 236 ++++++++- 4 files changed, 701 insertions(+), 68 deletions(-) diff --git a/include/IECoreScene/ShaderNetworkAlgo.h b/include/IECoreScene/ShaderNetworkAlgo.h index fbd5cee70a..9f39280cd3 100644 --- a/include/IECoreScene/ShaderNetworkAlgo.h +++ b/include/IECoreScene/ShaderNetworkAlgo.h @@ -76,12 +76,27 @@ IECORESCENE_API void removeComponentConnectionAdapters( ShaderNetwork *network ) /// The name of the boolean blindData label used by add/removeComponentConnectionAdapters IECORESCENE_API const IECore::InternedString &componentConnectionAdapterLabel(); +/// Converts various aspects of how shaders are stored to be ready to pass directly to OSL. +/// The `oslVersion` argument is used to determine how conversion is performed, and should be passed a +/// value of `OSL_VERSION`. Conversions include: +/// +/// - Connections involving the individual components of point/color parameters. +/// For OSL prior to 1.10, intermediate shaders are inserted to emulate connections between components. +/// For later versions, no new shaders are inserted, but components are renamed from our `.x, .y, .z` +/// suffixes to OSL's `[0], [1], [2]` suffixes. +/// - Splines +/// We support SplineData as a parameter type. For OSL, these must be converted to 3 parameters named +/// `Positions`, `Values` and `Basis`. We also support input +/// connections to spline Y values, specified as `[N].y`, which currently must be implemented +/// using an adapter shader. +IECORESCENE_API void convertToOSLConventions( ShaderNetwork *network, int oslVersion ); + /// Finds connections involving the individual components of point/color parameters, and converts them /// for use with OSL. The `oslVersion` argument is used to determine how conversion is performed, /// and should be passed a value of `OSL_VERSION`. For OSL prior to 1.10, intermediate shaders are /// inserted to emulate connections between components. For later versions, no new shaders are inserted, but /// components are renamed from our `.x, .y, .z` suffixes to OSL's `[0], [1], [2]` suffixes. -/// \todo Remove the version without the `oslVersion` argument. +/// \deprecated: Use convertToOSLConventions instead IECORESCENE_API void convertOSLComponentConnections( ShaderNetwork *network ); IECORESCENE_API void convertOSLComponentConnections( ShaderNetwork *network, int oslVersion ); @@ -89,19 +104,33 @@ IECORESCENE_API void convertOSLComponentConnections( ShaderNetwork *network, int IECORESCENE_API ShaderNetworkPtr convertObjectVector( const IECore::ObjectVector *network ); /// We use a convention where ramps are represented by a single SplineData in Cortex, but must be expanded -/// out into basic types when being passed to a renderer. We need two functions to convert back and forth. +/// out into basic types when being passed to a renderer. We need two functions to convert back and forth. + -/// Look for parameters matching our spline convention, for any possible : +/// Look throughout the network for parameters matching our spline convention, for any possible : /// Positions, a float vector parameter /// Values, a vector of a value type, such as float or color /// Basis, a string parameter /// For each set of parameters found matching this convention, the 3 parameters will be replaced with one -/// spline parameter named . If none are found, the input is passed through unchanged. -IECORESCENE_API IECore::ConstCompoundDataPtr collapseSplineParameters( const IECore::ConstCompoundDataPtr& parameters ); +/// spline parameter named . If input connections are represented using an adapter shader, they +/// will be converted to direct connections to the spline using our support for spline element +/// connections. +/// If `targetPrefix` is given, only translates connections to shaders with a type starting with this string +IECORESCENE_API void collapseSplines( ShaderNetwork *network, std::string targetPrefix = "" ); + +/// Look throughout the network for spline parameters. If any are found, they will be expanded out into +/// 3 parameters named Positions, Values and Basis. +/// We also support input connections to spline Y values, specified as `[N].y`, which currently +/// must be implemented by inserting an adapter shader. +/// If `targetPrefix` is given, only translates connections to shaders with a type starting with this string +IECORESCENE_API void expandSplines( ShaderNetwork *network, std::string targetPrefix = "" ); + -/// Look for spline parameters. If any are found, they will be expanded out into 3 parameters named -/// Positions, Values and Basis. If none are found, the input is passed through unchanged. -IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECore::ConstCompoundDataPtr& parameters ); +/// \deprecated: Use collapseSplines on the whole network, which can handle input connections +IECORESCENE_API IECore::ConstCompoundDataPtr collapseSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); + +/// \deprecated: Use expandSplines on the whole network, which can handle input connections +IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); } // namespace ShaderNetworkAlgo @@ -109,5 +138,4 @@ IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECor } // namespace IECoreScene #include "IECoreScene/ShaderNetworkAlgo.inl" - #endif // IECORESCENE_SHADERNETWORKALGO_H diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 9670e87dbd..3402b7c252 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -37,8 +37,10 @@ #include "IECoreScene/ShaderNetworkAlgo.h" #include "IECore/SimpleTypedData.h" +#include "IECore/StringAlgo.h" #include "IECore/SplineData.h" #include "IECore/VectorTypedData.h" +#include "IECore/MessageHandler.h" #include "boost/algorithm/string/predicate.hpp" #include "boost/algorithm/string/replace.hpp" @@ -131,6 +133,8 @@ const InternedString g_inParameterName( "in" ); const InternedString g_outParameterName( "out" ); const InternedString g_packInParameterNames[4] = { "in1", "in2", "in3", "in4" }; const boost::regex g_componentRegex( "^(.*)\\.([rgbaxyz])$" ); +const boost::regex g_splineElementRegex( "^(.*)\\[(.*)\\]\\.y(.*)$" ); +const boost::regex g_splineAdapterInRegex( "^in([0-9]+)(\\..*)?$" ); const char *g_vectorComponents[3] = { "x", "y", "z" }; const char *g_colorComponents[4] = { "r", "g", "b", "a" }; @@ -155,6 +159,21 @@ ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter ); } + +const int maxArrayInputAdapterSize = 32; +const InternedString g_arrayInputNames[maxArrayInputAdapterSize] = { + "in0", "in1", "in2", "in3", "in4", "in5", "in6", "in7", "in8", "in9", + "in10", "in11", "in12", "in13", "in14", "in15", "in16", "in17", "in18", "in19", + "in20", "in21", "in22", "in23", "in24", "in25", "in26", "in27", "in28", "in29", + "in30", "in31" +}; +const InternedString g_arrayOutputNames[maxArrayInputAdapterSize + 1] = { + "unused", "out1", "out2", "out3", "out4", "out5", "out6", "out7", "out8", "out9", + "out10", "out11", "out12", "out13", "out14", "out15", "out16", "out17", "out18", "out19", + "out20", "out21", "out22", "out23", "out24", "out25", "out26", "out27", "out28", "out29", + "out30", "out31", "out32" +}; + } // namespace void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, std::string targetPrefix ) @@ -556,12 +575,35 @@ ShaderNetworkPtr ShaderNetworkAlgo::convertObjectVector( const ObjectVector *net namespace { +std::string_view stringViewFromMatch( const std::string &s, const boost::smatch &match, int index ) +{ + return std::string_view( s.data() + match.position( index ), match.length( index ) ); +} + +template +std::pair< size_t, size_t > getEndPointDuplication( const T &basis ) +{ + if( basis == T::linear() ) + { + // OSL discards the first and last segment of linear curves + // "To maintain consistency with the other spline types" + // so we need to duplicate the end points to preserve all provided segments + return std::make_pair( 1, 1 ); + } + else if( basis == T::constant() ) + { + // Also, "To maintain consistency", "constant splines ignore the first and the two last + // data values." + return std::make_pair( 1, 2 ); + } + + return std::make_pair( 0, 0 ); +} + template void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters ) { const char *basis = "catmull-rom"; - size_t duplicateStartPoints = 0; - size_t duplicateEndPoints = 0; if( spline.basis == Spline::Basis::bezier() ) { basis = "bezier"; @@ -572,21 +614,15 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat } else if( spline.basis == Spline::Basis::linear() ) { - // OSL discards the first and last segment of linear curves - // "To maintain consistency with the other spline types" - // so we need to duplicate the end points to preserve all provided segments - duplicateStartPoints = 1; - duplicateEndPoints = 1; basis = "linear"; } else if( spline.basis == Spline::Basis::constant() ) { // Also, "To maintain consistency", "constant splines ignore the first and the two last // data values." - duplicateStartPoints = 1; - duplicateEndPoints = 2; basis = "constant"; } + auto [ duplicateStartPoints, duplicateEndPoints ] = getEndPointDuplication( spline.basis ); typedef TypedData< vector > XTypedVectorData; typename XTypedVectorData::Ptr positionsData = new XTypedVectorData(); @@ -682,14 +718,367 @@ IECore::DataPtr loadSpline( return resultData; } +const std::string g_oslShader( "osl:shader" ); + +const std::string g_colorToArrayAdapter( "Utility/__ColorToArray" ); +const std::string g_floatToArrayAdapter( "Utility/__FloatToArray" ); + +template< typename TypedSpline > +std::pair< InternedString, int > createSplineInputAdapter( + ShaderNetwork *network, const TypedData *splineData, + const IECore::CompoundDataMap &newParameters, const IECore::InternedString &splineParameterName, + const ShaderNetwork::Parameter &destination +) +{ + using ValueVectorData = TypedData< std::vector< typename TypedSpline::YType > >; + + IECore::InternedString splineValuesName = splineParameterName.string() + "Values"; + auto findValues = newParameters.find( splineValuesName ); + const ValueVectorData *splineValuesData = findValues != newParameters.end() ? runTimeCast( findValues->second.get() ) : nullptr; + if( !splineValuesData ) + { + throw IECore::Exception( "Internal failure in convertToOSLConventions - expandSpline did not create values." ); + } + + const std::vector< typename TypedSpline::YType > &splineValues = splineValuesData->readable(); + + if( splineValues.size() > maxArrayInputAdapterSize ) + { + throw IECore::Exception( + "Cannot handle input to " + + destination.shader.string() + "." + destination.name.string() + + " : expanded spline has " + std::to_string( splineValues.size() ) + + " control points, but max input adapter size is " + std::to_string( maxArrayInputAdapterSize ) + ); + } + + // Using this adapter depends on Gaffer being available, but I guess we don't really + // care about use cases outside Gaffer ( and in terms of using exported USD elsewhere, + // this spline representation is only used in Gaffer's spline shaders, so it's not very + // useful if you don't have access to Gaffer shaders anyway ). + ShaderPtr adapter = new Shader( + std::is_same< TypedSpline, SplinefColor3f >::value ? g_colorToArrayAdapter : g_floatToArrayAdapter, + g_oslShader + ); + + for( unsigned int i = 0; i < splineValues.size(); i++ ) + { + adapter->parameters()[ g_arrayInputNames[i] ] = new TypedData< typename TypedSpline::YType >( splineValues[i] ); + } + + InternedString adapterHandle = network->addShader( destination.shader.string() + "_" + splineParameterName.string() + "InputArrayAdapter", std::move( adapter ) ); + network->addConnection( ShaderNetwork::Connection( + { adapterHandle, g_arrayOutputNames[ splineValues.size() ] }, + { destination.shader, splineValuesName } + ) ); + + return std::make_pair( adapterHandle, getEndPointDuplication( splineData->readable().basis ).first ); +} + +void ensureParametersCopy( + const IECore::CompoundDataMap ¶meters, + IECore::CompoundDataPtr ¶metersDataCopy, CompoundDataMap *¶metersCopy +) +{ + if( !parametersDataCopy ) + { + parametersDataCopy = new CompoundData(); + parametersCopy = ¶metersDataCopy->writable(); + *parametersCopy = parameters; + } +} + } // namespace -IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const IECore::ConstCompoundDataPtr ¶meters ) +void ShaderNetworkAlgo::convertToOSLConventions( ShaderNetwork *network, int oslVersion ) +{ + expandSplines( network, "osl:" ); + + // \todo - it would be a bit more efficient to integrate this, and only traverse the network once, + // but I don't think it's worth duplicated the code - fix this up once this call is standard and we + // deprecate and remove convertOSLComponentConnections + convertOSLComponentConnections( network, oslVersion); + +} + +void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string targetPrefix ) { + std::vector< IECore::InternedString > adapters; + + for( auto [name, shader] : network->shaders() ) + { + if( !boost::starts_with( shader->getType(), targetPrefix ) ) + { + continue; + } + + bool isSplineAdapter = shader->getType() == g_oslShader && ( + shader->getName() == g_colorToArrayAdapter || shader->getName() == g_floatToArrayAdapter + ); + + if( isSplineAdapter ) + { + adapters.push_back( name ); + continue; + } + + // For nodes which aren't spline adapters, we just need to deal with any parameters that are splines + ConstCompoundDataPtr collapsed = collapseSplineParameters( shader->parametersData() ); + if( collapsed != shader->parametersData() ) + { + // \todo - this const_cast is ugly, although safe because if the return from collapseSplineParameters + // doesn't match the input, it is freshly allocated. Once collapseSplineParameters is fully + // deprecated, and no longer visible publicly, an internal version of collapseSplineParameters could + // just return a non-const new parameter data, or nullptr if no changes are needed. + network->setShader( name, std::move( new Shader( shader->getName(), shader->getType(), const_cast< CompoundData *>( collapsed.get() ) ) ) ); + } + } + + for( InternedString &name : adapters ) + { + // For all adapters we create, there will be a single output, but it doesn't hurt to have the + // generality of this being a loop just in case. + for( auto output : network->outputConnections( name ) ) + { + const std::string &splineValuesName = output.destination.name.string(); + if( !boost::ends_with( splineValuesName, "Values" ) ) + { + IECore::msg( + Msg::Error, "ShaderNetworkAlgo", "Invalid spline plug name \"" + splineValuesName + "\"" + ); + continue; + } + + InternedString splineName = string_view( splineValuesName ).substr( 0, splineValuesName.size() - 6 ); + + const IECoreScene::Shader *targetShader = network->getShader( output.destination.shader ); + if( !targetShader ) + { + throw IECore::Exception( + "Invalid connection to shader that doesn't exist \"" + output.destination.shader.string() + "\"" + ); + } + const IECore::CompoundDataMap &targetParameters = targetShader->parameters(); + + int targetSplineKnotOffset = -1; + auto targetParameterIt = targetParameters.find( splineName ); + if( targetParameterIt != targetParameters.end() ) + { + if( const SplineffData *findSplineff = runTimeCast( targetParameterIt->second.get() ) ) + { + targetSplineKnotOffset = getEndPointDuplication( findSplineff->readable().basis ).first; + } + else if( const SplinefColor3fData *findSplinefColor3f = runTimeCast( targetParameterIt->second.get() ) ) + { + targetSplineKnotOffset = getEndPointDuplication( findSplinefColor3f->readable().basis ).first; + } + else if( const SplinefColor4fData *findSplinefColor4f = runTimeCast( targetParameterIt->second.get() ) ) + { + targetSplineKnotOffset = getEndPointDuplication( findSplinefColor4f->readable().basis ).first; + } + } + + if( targetSplineKnotOffset == -1 ) + { + IECore::msg( + Msg::Error, "ShaderNetworkAlgo", + "Invalid connection to spline parameter that doesn't exist \"" + + output.destination.shader.string() + "." + output.destination.name.string() + "\"" + ); + continue; + } + + for( auto input : network->inputConnections( name ) ) + { + + const std::string &adapterDestName = input.destination.name.string(); + boost::smatch match; + if( !boost::regex_match( adapterDestName, match, g_splineAdapterInRegex ) ) + { + IECore::msg( + Msg::Error, "ShaderNetworkAlgo", "Invalid spline adapter input name \"" + adapterDestName + "\"" + ); + continue; + } + + int elementId = + StringAlgo::toInt( stringViewFromMatch( adapterDestName, match, 1 ) ) + - targetSplineKnotOffset; + + InternedString origDestName; + if( match[2].matched ) + { + origDestName = StringAlgo::concat( + splineName.string(), "[", std::to_string( elementId ), "].y", + stringViewFromMatch( adapterDestName, match, 2 ) + ); + } + else + { + origDestName = StringAlgo::concat( + splineName.string(), "[", std::to_string( elementId ), "].y" + ); + } + + network->addConnection( { + { input.source.shader, input.source.name}, + { output.destination.shader, origDestName } + } ); + } + } + network->removeShader( name ); + } +} + +void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targetPrefix ) +{ + for( const auto &s : network->shaders() ) + { + if( !boost::starts_with( s.second->getType(), targetPrefix ) ) + { + continue; + } + + const CompoundDataMap &origParameters = s.second->parameters(); + + CompoundDataPtr newParametersData; + CompoundDataMap *newParameters = nullptr; + + for( const auto &[name, value] : origParameters ) + { + if( const SplinefColor3fData *colorSpline = runTimeCast( value.get() ) ) + { + ensureParametersCopy( origParameters, newParametersData, newParameters ); + newParameters->erase( name ); + expandSpline( name, colorSpline->readable(), *newParameters ); + } + else if( const SplineffData *floatSpline = runTimeCast( value.get() ) ) + { + ensureParametersCopy( origParameters, newParametersData, newParameters ); + newParameters->erase( name ); + expandSpline( name, floatSpline->readable(), *newParameters ); + } + } + + if( !newParameters ) + { + // No splines to convert + continue; + } + + // currentSplineArrayAdapters holds array adaptors that we need to use to hook up inputs to + // spline plugs. It is indexed by the name of a spline parameter for the shader, and holds + // the name of the adapter shader, and the offset we need to use when accessing the knot + // vector. + + std::map< IECore::InternedString, std::pair< IECore::InternedString, size_t > > currentSplineArrayAdapters; + + std::vector< ShaderNetwork::Connection > connectionsToAdd; + ShaderNetwork::ConnectionRange inputConnections = network->inputConnections( s.first ); + for( ShaderNetwork::ConnectionIterator it = inputConnections.begin(); it != inputConnections.end(); ) + { + // Copy and increment now so we still have a valid iterator + // if we remove the connection. + const ShaderNetwork::Connection connection = *it++; + + const std::string &destName = connection.destination.name.string(); + boost::smatch splineElementMatch; + if( !boost::regex_match( destName, splineElementMatch, g_splineElementRegex) ) + { + continue; + } + + IECore::InternedString parameterName( stringViewFromMatch( destName, splineElementMatch, 1 ) ); + auto findParameter = origParameters.find( parameterName ); + if( findParameter == origParameters.end() ) + { + continue; + } + + const SplinefColor3fData* colorSplineData = runTimeCast( findParameter->second.get() ); + const SplineffData* floatSplineData = runTimeCast( findParameter->second.get() ); + + if( !( colorSplineData || floatSplineData ) ) + { + continue; + } + + int numPoints = colorSplineData ? colorSplineData->readable().points.size() : floatSplineData->readable().points.size(); + + // Insert a conversion shader to handle connection to component + auto [ adapterIter, newlyInserted ] = currentSplineArrayAdapters.insert( { parameterName, std::make_pair( IECore::InternedString(), 0 ) } ); + if( newlyInserted ) + { + if( colorSplineData ) + { + adapterIter->second = createSplineInputAdapter( + network, colorSplineData, *newParameters, parameterName, connection.destination + ); + } + else + { + adapterIter->second = createSplineInputAdapter( + network, floatSplineData, *newParameters, parameterName, connection.destination + ); + } + } + + const auto [ adapterHandle, knotOffset ] = adapterIter->second; + + int elementId; + std::string_view elementIdString( stringViewFromMatch( destName, splineElementMatch, 2 ) ); + try + { + elementId = StringAlgo::toInt( elementIdString ); + } + catch( ... ) + { + throw IECore::Exception( StringAlgo::concat( "Invalid spline point index ", elementIdString ) ); + } + + if( elementId < 0 || elementId >= numPoints ) + { + throw IECore::Exception( "Spline index " + std::to_string( elementId ) + " is out of range in spline with " + std::to_string( numPoints ) + " points." ); + } + + // We form only a single connection, even if we are at an endpoint which is duplicated during + // expandSpline. This is OK because the end points that are duplicated by expandSpline are ignored + // by OSL. + // + // An aside : the X values of the ignored points do need to be non-decreasing sometimes. There are + // two contraditory claims in the OSL spec, that: + // "Results are undefined if the knots ... not ... monotonic" + // and + // "constant splines ignore the first and the two last data values." + // This statements combine to make it ambiguous whether the duplicated value is completely + // ignored, or whether it must be monotonic ... in practice, it seems to cause problems for + // constant, but not linear interpolation. + // + // In any case, we only make connections to the Y value, so there is no problem with ignoring + // the duplicated values + + InternedString destinationName = g_arrayInputNames[elementId + knotOffset]; + if( splineElementMatch.length( 3 ) ) + { + destinationName = StringAlgo::concat( destinationName.string(), stringViewFromMatch( destName, splineElementMatch, 3 ) ); + } + + network->removeConnection( connection ); + network->addConnection( { connection.source, { adapterHandle, destinationName } } ); + } + + network->setShader( s.first, std::move( new Shader( s.second->getName(), s.second->getType(), newParametersData.get() ) ) ); + + } +} + +IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) +{ + const CompoundDataMap ¶meters( parametersData->readable() ); CompoundDataPtr newParametersData; + CompoundDataMap *newParameters = nullptr; - const auto &parms = parameters->readable(); - for( const auto &maybeBasis : parameters->readable() ) + for( const auto &maybeBasis : parameters ) { if( !boost::ends_with( maybeBasis.first.string(), "Basis" ) ) { @@ -704,10 +1093,10 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const std::string prefix = maybeBasis.first.string().substr( 0, maybeBasis.first.string().size() - 5 ); IECore::InternedString positionsName = prefix + "Positions"; - const auto positionsIter = parms.find( positionsName ); + const auto positionsIter = parameters.find( positionsName ); const FloatVectorData *floatPositions = nullptr; - if( positionsIter != parms.end() ) + if( positionsIter != parameters.end() ) { floatPositions = runTimeCast( positionsIter->second.get() ); } @@ -718,10 +1107,10 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const } IECore::InternedString valuesName = prefix + "Values"; - const auto valuesIter = parms.find( valuesName ); + const auto valuesIter = parameters.find( valuesName ); IECore::DataPtr foundSpline; - if( valuesIter != parms.end() ) + if( valuesIter != parameters.end() ) { if( const FloatVectorData *floatValues = runTimeCast( valuesIter->second.get() ) ) { @@ -739,17 +1128,11 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const if( foundSpline ) { - if( !newParametersData ) - { - newParametersData = new IECore::CompoundData(); - newParametersData->writable() = parameters->readable(); - } - auto &newParameters = newParametersData->writable(); - - newParameters[prefix] = foundSpline; - newParameters.erase( maybeBasis.first ); - newParameters.erase( positionsName ); - newParameters.erase( valuesName ); + ensureParametersCopy( parameters, newParametersData, newParameters ); + (*newParameters)[prefix] = foundSpline; + newParameters->erase( maybeBasis.first ); + newParameters->erase( positionsName ); + newParameters->erase( valuesName ); } } @@ -759,46 +1142,39 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const } else { - return parameters; + return parametersData; } } -IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IECore::ConstCompoundDataPtr ¶meters ) +IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) { - bool hasSplines = false; - for( const auto &i : parameters->readable() ) - { - if( runTimeCast( i.second.get() ) || - runTimeCast( i.second.get() ) ) - { - hasSplines = true; - break; - } - } + const CompoundDataMap ¶meters( parametersData->readable() ); - if( !hasSplines ) - { - return parameters; - } - - CompoundDataPtr newParametersData = new CompoundData(); - CompoundDataMap &newParameters = newParametersData->writable(); + CompoundDataPtr newParametersData; + CompoundDataMap *newParameters = nullptr; - for( const auto &i : parameters->readable() ) + for( const auto &i : parameters ) { if( const SplinefColor3fData *colorSpline = runTimeCast( i.second.get() ) ) { - expandSpline( i.first, colorSpline->readable(), newParameters ); + ensureParametersCopy( parameters, newParametersData, newParameters ); + newParameters->erase( i.first ); + expandSpline( i.first, colorSpline->readable(), *newParameters ); } else if( const SplineffData *floatSpline = runTimeCast( i.second.get() ) ) { - expandSpline( i.first, floatSpline->readable(), newParameters ); - } - else - { - newParameters[ i.first ] = i.second; + ensureParametersCopy( parameters, newParametersData, newParameters ); + newParameters->erase( i.first ); + expandSpline( i.first, floatSpline->readable(), *newParameters ); } } - return newParametersData; + if( newParametersData ) + { + return newParametersData; + } + else + { + return parametersData; + } } diff --git a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp index 5f02f1d283..af9c2097e0 100644 --- a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp +++ b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp @@ -79,8 +79,11 @@ void IECoreSceneModule::bindShaderNetworkAlgo() def( "addComponentConnectionAdapters", &ShaderNetworkAlgo::addComponentConnectionAdapters, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); def( "removeComponentConnectionAdapters", &ShaderNetworkAlgo::removeComponentConnectionAdapters, ( arg( "network" ) ) ); def( "componentConnectionAdapterLabel", &componentConnectionAdapterLabelWrapper ); + def( "convertToOSLConventions", &ShaderNetworkAlgo::convertToOSLConventions ); def( "convertOSLComponentConnections", &convertOSLComponentConnectionsWrapper, ( arg( "network" ), arg( "oslVersion" ) = 10900 ) ); def( "convertObjectVector", &ShaderNetworkAlgo::convertObjectVector ); + def( "collapseSplines", &ShaderNetworkAlgo::collapseSplines, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); + def( "expandSplines", &ShaderNetworkAlgo::expandSplines, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); def( "collapseSplineParameters", &collapseSplineParametersWrapper ); def( "expandSplineParameters", &expandSplineParametersWrapper ); diff --git a/test/IECoreScene/ShaderNetworkAlgoTest.py b/test/IECoreScene/ShaderNetworkAlgoTest.py index 60fe84a1dd..d84b5bcb3f 100644 --- a/test/IECoreScene/ShaderNetworkAlgoTest.py +++ b/test/IECoreScene/ShaderNetworkAlgoTest.py @@ -400,7 +400,14 @@ def testSplineConversion( self ): parms["testffconstant"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.constant(), ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) ) ) - parmsExpanded = IECoreScene.ShaderNetworkAlgo.expandSplineParameters( parms ) + shaderNetworkOrig = IECoreScene.ShaderNetwork( + shaders = { "test" : IECoreScene.Shader( "test", "osl:shader", parms ) }, + output = "test" + ) + shaderNetwork = shaderNetworkOrig.copy() + IECoreScene.ShaderNetworkAlgo.expandSplines( shaderNetwork ) + + parmsExpanded = shaderNetwork.outputShader().parameters self.assertEqual( set( parmsExpanded.keys() ), set( [ i + suffix for suffix in [ "Basis", "Values", "Positions" ] for i in parms.keys() ] ) ) self.assertEqual( type( parmsExpanded["testffbSplineBasis"] ), IECore.StringData ) @@ -417,19 +424,238 @@ def testSplineConversion( self ): ]: self.assertEqual( len( parms[name].value.keys() ) + extra, len( parmsExpanded[name + "Positions"] ) ) - parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded ) + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetwork ) + + self.assertEqual( shaderNetwork, shaderNetworkOrig ) + + # Collapsing can fail in various ways where we just print an error, maybe these could be exceptions + # instead ... I'm picturing a scenario where one of our USDs has been run through an unknown translation + # process that might corrupt it + shaderNetworkExpandedGood = IECoreScene.ShaderNetwork( + shaders = { + "source" : IECoreScene.Shader( "source" ), + "adapt" : IECoreScene.Shader( "Utility/__ColorToArray", "osl:shader" ), + "test" : IECoreScene.Shader( "test", "osl:shader", parmsExpanded ) + }, + output = "test" + ) + + shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notASpline" ) ) ) + + with IECore.CapturingMessageHandler() as mh : + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + self.assertEqual( len( mh.messages ), 1 ) + self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) + self.assertEqual( mh.messages[0].message, 'Invalid spline plug name "notASpline"' ) + + shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notASplineValues" ) ) ) - self.assertEqual( parmsCollapsed, parms ) + with IECore.CapturingMessageHandler() as mh : + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + self.assertEqual( len( mh.messages ), 1 ) + self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) + self.assertEqual( mh.messages[0].message, 'Invalid connection to spline parameter that doesn\'t exist "test.notASplineValues"' ) + shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "testffbSplineValues" ) ) ) + shaderNetworkBadSplineConnection.addConnection( ( ( "source", "out" ), ( "adapt", "inX" ) ) ) + + with IECore.CapturingMessageHandler() as mh : + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + self.assertEqual( len( mh.messages ), 1 ) + self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) + self.assertEqual( mh.messages[0].message, 'Invalid spline adapter input name "inX"' ) + + + # Test with partial splines that don't have the right parameters to be valid ( these silently do + # nothing, because maybe you just happen to have some parameters that look a bit like splines ) del parmsExpanded["testffbSplineBasis"] del parmsExpanded["testffbezierValues"] del parmsExpanded["testfColor3fcatmullRomPositions"] del parmsExpanded["testfColor3flinearBasis"] del parmsExpanded["testffconstantPositions"] - parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded ) + shaderNetworkInvalidOrig = IECoreScene.ShaderNetwork( + shaders = { "test" : IECoreScene.Shader( "test", "osl:shader", parmsExpanded ) }, + output = "test" + ) + shaderNetworkInvalid = shaderNetworkInvalidOrig.copy() + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkInvalid ) + self.assertEqual( shaderNetworkInvalid, shaderNetworkInvalidOrig ) + + def testSplineInputs( self ): + + fC3fcatmullRom = IECore.SplinefColor3fData( IECore.SplinefColor3f( + IECore.CubicBasisf.catmullRom(), + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ) + ) ) + fC3flinear = IECore.SplinefColor3fData( IECore.SplinefColor3f( + IECore.CubicBasisf.linear(), + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ) ) + ) ) + ffconstant = IECore.SplineffData( IECore.Splineff( + IECore.CubicBasisf.constant(), + ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) + ) ) + + nOrig = IECoreScene.ShaderNetwork( + shaders = { + "testSplines" : IECoreScene.Shader( + "testSplines", "osl:shader", + parameters = { + "fC3fcatmullRom" : fC3fcatmullRom, + "fC3flinear" : fC3flinear, + "ffconstant" : ffconstant, + } + ), + "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), + "colorSource" : IECoreScene.Shader( "colorSource", "osl:shader" ), + }, + connections = [ + ( ( "colorSource", "out" ), ( "testSplines", "fC3fcatmullRom[1].y" ) ), + ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[3].y.b" ) ), + ( ( "floatSource", "out" ), ( "testSplines", "fC3flinear[0].y.g" ) ), + ( ( "floatSource", "out" ), ( "testSplines", "ffconstant[2].y" ) ), + ], + output = "testSplines" + ) + + n = nOrig.copy() + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) + self.assertEqual( len( nOrig ), 3 ) + self.assertEqual( len( n ), 6 ) + + convertedSplineParameters = n.getShader( "testSplines" ).parameters + self.assertEqual( convertedSplineParameters["fC3fcatmullRomPositions"], IECore.FloatVectorData( [ 0, 10, 20, 30, 40, 50 ] ) ) + self.assertEqual( convertedSplineParameters["fC3flinearPositions"], IECore.FloatVectorData( [ 0, 0, 10, 10 ] ) ) + self.assertEqual( convertedSplineParameters["ffconstantPositions"], IECore.FloatVectorData( [ 0, 0, 0.2, 0.3, 0.3, 0.3 ] ) ) + + + self.assertEqual( n.input( ( "testSplines", "fC3fcatmullRomValues" ) ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "out6" ) ) + self.assertEqual( n.input( ( "testSplines", "fC3flinearValues" ) ), ( "testSplines_fC3flinearInputArrayAdapter", "out4" ) ) + self.assertEqual( n.input( ( "testSplines", "ffconstantValues" ) ), ( "testSplines_ffconstantInputArrayAdapter", "out6" ) ) + + adapterParameters = n.getShader( "testSplines_fC3fcatmullRomInputArrayAdapter" ).parameters + self.assertEqual( + [ adapterParameters["in%i"%i].value for i in range( 6 ) ], + [ imath.Color3f(1), imath.Color3f(2), imath.Color3f(0), imath.Color3f(5), imath.Color3f(2), imath.Color3f(6) ] + ) + self.assertEqual( len( n.inputConnections( "testSplines_fC3fcatmullRomInputArrayAdapter" ) ), 2 ) + self.assertEqual( n.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in1" ) ), ( "colorSource", "out" ) ) + self.assertEqual( n.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3[2]" ) ), ( "floatSource", "out" ) ) + + adapter1Parameters = n.getShader( "testSplines_fC3flinearInputArrayAdapter" ).parameters + self.assertEqual( + [ adapter1Parameters["in%i"%i].value for i in range( 4 ) ], + [ imath.Color3f(1), imath.Color3f(1), imath.Color3f(2), imath.Color3f(2) ] + ) + self.assertEqual( len( n.inputConnections( "testSplines_fC3flinearInputArrayAdapter" ) ), 1 ) + self.assertEqual( n.input( ( "testSplines_fC3flinearInputArrayAdapter", "in1[1]" ) ), ( "floatSource", "out" ) ) + + adapter2Parameters = n.getShader( "testSplines_ffconstantInputArrayAdapter" ).parameters + self.assertEqual( + [ adapter2Parameters["in%i"%i].value for i in range( 6 ) ], + [ 1, 1, 6, 7, 7, 7 ] + ) + self.assertEqual( len( n.inputConnections( "testSplines_ffconstantInputArrayAdapter" ) ), 1 ) + self.assertEqual( n.input( ( "testSplines_ffconstantInputArrayAdapter", "in3" ) ), ( "floatSource", "out" ) ) + + # Check that we can get the same results using expandSplines, aside from convertToOSLConventions + # also changing the component syntax + nSplinesOnly = nOrig.copy() + IECoreScene.ShaderNetworkAlgo.expandSplines( nSplinesOnly ) + self.assertEqual( nSplinesOnly.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3.b" ) ), ( "floatSource", "out" ) ) + self.assertEqual( nSplinesOnly.input( ( "testSplines_fC3flinearInputArrayAdapter", "in1.g" ) ), ( "floatSource", "out" ) ) + nSplinesOnly.removeConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3.b" ) ) ) + nSplinesOnly.removeConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3flinearInputArrayAdapter", "in1.g" ) ) ) + nSplinesOnly.addConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3[2]" ) ) ) + nSplinesOnly.addConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3flinearInputArrayAdapter", "in1[1]" ) ) ) + self.assertEqual( n, nSplinesOnly ) + + # But expandSplines won't do anything if the prefix is wrong + nWrongPrefix = nOrig.copy() + IECoreScene.ShaderNetworkAlgo.expandSplines( nWrongPrefix, "foo:" ) + self.assertEqual( nOrig, nWrongPrefix ) + + # Check that collapseSplines does the reverse + nReverse = nOrig.copy() + IECoreScene.ShaderNetworkAlgo.expandSplines( nReverse ) + IECoreScene.ShaderNetworkAlgo.collapseSplines( nReverse ) + self.assertEqual( nOrig, nReverse ) + + n = IECoreScene.ShaderNetwork( + shaders = { + "testSplines" : IECoreScene.Shader( + "testSplines", "osl:shader", + parameters = { + "fC3fcatmullRom" : IECore.SplinefColor3fData( + IECore.SplinefColor3f( IECore.CubicBasisf.catmullRom(), [ ( 0, imath.Color3f(0) ) ] * 33 ) + ), + } + ), + "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), + }, + connections = [ + ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[0].y.b" ) ), + ], + output = "testSplines" + ) + + with six.assertRaisesRegex( self, Exception, r".*Cannot handle input to testSplines.fC3fcatmullRom\[0\].y.b : expanded spline has 33 control points, but max input adapter size is 32.*" ): + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) + + n = IECoreScene.ShaderNetwork( + shaders = { + "testSplines" : IECoreScene.Shader( + "testSplines", "osl:shader", + parameters = { "fC3fcatmullRom" : fC3fcatmullRom } + ), + "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), + }, + connections = [ + ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[xx].y.b" ) ), + ], + output = "testSplines" + ) + + with six.assertRaisesRegex( self, Exception, "Invalid spline point index xx" ): + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) + + n = IECoreScene.ShaderNetwork( + shaders = { + "testSplines" : IECoreScene.Shader( + "testSplines", "osl:shader", + parameters = { "fC3fcatmullRom" : fC3fcatmullRom } + ), + "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), + }, + connections = [ + ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[-1].y.b" ) ), + ], + output = "testSplines" + ) + + with six.assertRaisesRegex( self, Exception, "Spline index -1 is out of range in spline with 6 points." ): + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) + + n = IECoreScene.ShaderNetwork( + shaders = { + "testSplines" : IECoreScene.Shader( + "testSplines", "osl:shader", + parameters = { "fC3fcatmullRom" : fC3fcatmullRom } + ), + "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), + }, + connections = [ + ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[100].y.b" ) ), + ], + output = "testSplines" + ) - self.assertEqual( parmsCollapsed, parmsExpanded ) + with six.assertRaisesRegex( self, Exception, "Spline index 100 is out of range in spline with 6 points." ): + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) def testColor4ComponentConnections( self ) : From 36867606f03edbdbf336119b62ea64342d2a058c Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Mon, 20 Mar 2023 15:41:36 -0700 Subject: [PATCH 4/5] IECoreAppleseed : Support spline input connections --- .../src/IECoreAppleseed/ShaderNetworkAlgo.cpp | 16 ++++------------ .../IECoreAppleseed/ShaderNetworkAlgoTest.py | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/contrib/IECoreAppleseed/src/IECoreAppleseed/ShaderNetworkAlgo.cpp b/contrib/IECoreAppleseed/src/IECoreAppleseed/ShaderNetworkAlgo.cpp index e308ecb82c..aed34d232c 100644 --- a/contrib/IECoreAppleseed/src/IECoreAppleseed/ShaderNetworkAlgo.cpp +++ b/contrib/IECoreAppleseed/src/IECoreAppleseed/ShaderNetworkAlgo.cpp @@ -65,13 +65,9 @@ namespace ShaderNetworkAlgo renderer::ShaderGroup *convert( const IECoreScene::ShaderNetwork *shaderNetwork ) { - ShaderNetworkPtr networkCopy; - if( true ) // todo : make conditional on OSL < 1.10 - { - networkCopy = shaderNetwork->copy(); - IECoreScene::ShaderNetworkAlgo::convertOSLComponentConnections( networkCopy.get() ); - shaderNetwork = networkCopy.get(); - } + ShaderNetworkPtr networkCopy = shaderNetwork->copy(); + IECoreScene::ShaderNetworkAlgo::convertToOSLConventions( networkCopy.get(), 10900 ); + shaderNetwork = networkCopy.get(); asf::auto_release_ptr shaderGroup; shaderGroup = asr::ShaderGroupFactory::create( "shader_group" ); @@ -89,11 +85,7 @@ renderer::ShaderGroup *convert( const IECoreScene::ShaderNetwork *shaderNetwork shaderType += 4; } - IECore::ConstCompoundDataPtr expandedParameters = IECoreScene::ShaderNetworkAlgo::expandSplineParameters( - shader->parametersData() - ); - - asr::ParamArray params( ParameterAlgo::convertShaderParameters( expandedParameters->readable() ) ); + asr::ParamArray params( ParameterAlgo::convertShaderParameters( shader->parametersData()->readable() ) ); shaderGroup->add_shader( shaderType, shader->getName().c_str(), handle.c_str(), params ); for( const auto &c : shaderNetwork->inputConnections( handle ) ) diff --git a/contrib/IECoreAppleseed/test/IECoreAppleseed/ShaderNetworkAlgoTest.py b/contrib/IECoreAppleseed/test/IECoreAppleseed/ShaderNetworkAlgoTest.py index b46eda4a8e..e4e15b5d64 100644 --- a/contrib/IECoreAppleseed/test/IECoreAppleseed/ShaderNetworkAlgoTest.py +++ b/contrib/IECoreAppleseed/test/IECoreAppleseed/ShaderNetworkAlgoTest.py @@ -48,7 +48,7 @@ def testSplines( self ) : n = IECoreScene.ShaderNetwork( shaders = { - "test" : IECoreScene.Shader( "test", "test", + "test" : IECoreScene.Shader( "test", "osl:shader", IECore.CompoundData( { "testColorSpline" : IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(), ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) ) ), From db60740ab593b37e2de5af31c34cc1da32f0110d Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 23 Mar 2023 14:35:46 -0700 Subject: [PATCH 5/5] IECoreUSD::ShaderAlgo : Use new expandSplines / collapseSplines --- .../IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp | 10 +++----- .../IECoreUSD/test/IECoreUSD/USDSceneTest.py | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index 79ce800741..3ffc012914 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -187,8 +187,6 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co readAdditionalLightParameters( usdShader.GetPrim(), parameters ); - parametersData = boost::const_pointer_cast< IECore::CompoundData >( IECoreScene::ShaderNetworkAlgo::collapseSplineParameters( parametersData ) ); - IECoreScene::ShaderPtr newShader = new IECoreScene::Shader( shaderName, shaderType, parametersData ); pxr::VtValue metadataValue; if( usdShader.GetPrim().GetMetadata( g_adapterLabelToken, &metadataValue ) && metadataValue.Get() ) @@ -224,6 +222,7 @@ IECoreScene::ShaderNetwork::Parameter readShaderNetworkWalk( const pxr::SdfPath pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer ) { IECoreScene::ShaderNetworkPtr shaderNetworkWithAdapters = shaderNetwork->copy(); + IECoreScene::ShaderNetworkAlgo::expandSplines( shaderNetworkWithAdapters.get() ); IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( shaderNetworkWithAdapters.get() ); IECoreScene::ShaderNetwork::Parameter networkOutput = shaderNetworkWithAdapters->getOutput(); @@ -258,11 +257,7 @@ pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene } usdShader.SetShaderId( pxr::TfToken( typePrefix + shader.second->getName() ) ); - - IECore::ConstCompoundDataPtr expandedParameters = IECoreScene::ShaderNetworkAlgo::expandSplineParameters( - shader.second->parametersData() - ); - for( const auto &p : expandedParameters->readable() ) + for( const auto &p : shader.second->parametersData()->readable() ) { pxr::UsdShadeInput input = usdShader.CreateInput( toUSDParameterName( p.first ), @@ -355,6 +350,7 @@ IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const px result->setOutput( outputHandle ); IECoreScene::ShaderNetworkAlgo::removeComponentConnectionAdapters( result.get() ); + IECoreScene::ShaderNetworkAlgo::collapseSplines( result.get() ); return result; } diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 10998d84a2..0cd74ca274 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -2632,6 +2632,12 @@ def testShaders( self ) : dest.parameters["a"] = IECore.Color3fData( imath.Color3f( 0.0 ) ) dest.parameters["b"] = IECore.Color3fData( imath.Color3f( 0.0 ) ) dest.parameters["c"] = IECore.FloatData( 0.0 ) + dest.parameters["sf"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.catmullRom(), + ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 30, 1 ) ) + ) ) + dest.parameters["sc"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(), + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) + ) ) componentConnectionNetwork = IECoreScene.ShaderNetwork() componentConnectionNetwork.addShader( "source1", add1 ) @@ -2664,6 +2670,24 @@ def testShaders( self ) : ) ) componentConnectionNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "dest", "" ) ) + # Float to spline element connection + componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( + IECoreScene.ShaderNetwork.Parameter( "source1", "out" ), + IECoreScene.ShaderNetwork.Parameter( "dest", "sf[3].y" ) + ) ) + + # Color to spline element connection + componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( + IECoreScene.ShaderNetwork.Parameter( "source3", "out" ), + IECoreScene.ShaderNetwork.Parameter( "dest", "sc[2].y" ) + ) ) + + # Float to spline element component connection + componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( + IECoreScene.ShaderNetwork.Parameter( "source1", "out" ), + IECoreScene.ShaderNetwork.Parameter( "dest", "sc[0].y.g" ) + ) ) + # If we manually create the shaders that are used as adapters for component connections, # they should not be automatically removed on import. ( This is implemented using # a label for automatically created adapters, stored as blindData in Cortex that is