5656#include " boost/algorithm/string/replace.hpp"
5757#include " boost/pointer_cast.hpp"
5858
59+ #include < filesystem>
5960#include < regex>
6061
6162#if PXR_VERSION < 2102
@@ -66,26 +67,68 @@ namespace
6667{
6768
6869const pxr::TfToken g_blindDataToken ( " cortex:blindData" );
70+ const pxr::TfToken g_shaderNameToken ( " cortex:shaderName" );
71+ const pxr::TfToken g_shaderTypeToken ( " cortex:shaderType" );
6972pxr::TfToken g_legacyAdapterLabelToken ( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel().string() );
7073
71- std::pair<pxr::TfToken, std::string> shaderIdAndType ( const pxr::UsdShadeConnectableAPI &connectable )
74+ bool writeConformantOSLShaders ()
75+ {
76+ if ( const char *e = getenv ( " IECOREUSD_WRITE_CONFORMANT_OSL_SHADERS" ) )
77+ {
78+ return strcmp ( e, " 0" );
79+ }
80+ return false ;
81+ }
82+
83+ // Recover a Cortex-style shader name and type, essentially reversing the
84+ // transformation done by `createShaderPrim()`.
85+ std::pair<std::string, std::string> shaderNameAndType ( const pxr::UsdShadeConnectableAPI &connectable )
7286{
73- pxr::TfToken id;
74- std::string type;
7587 if ( auto shader = pxr::UsdShadeShader ( connectable ) )
7688 {
77- shader.GetShaderId ( &id );
78- type = " surface" ;
89+ std::string name;
90+ std::string type;
91+
92+ pxr::VtValue nameVtValue = shader.GetPrim ().GetCustomDataByKey ( g_shaderNameToken );
93+ if ( !nameVtValue.IsEmpty () )
94+ {
95+ name = nameVtValue.Get <std::string>();
96+ }
97+ else
98+ {
99+ pxr::TfToken id;
100+ shader.GetShaderId ( &id );
101+ name = id.GetString ();
102+ type = " surface" ;
103+ const size_t colonPos = name.find ( " :" );
104+ if ( colonPos != std::string::npos )
105+ {
106+ std::string prefix = name.substr ( 0 , colonPos );
107+ name = name.substr ( colonPos + 1 );
108+ if ( prefix == " arnold" )
109+ {
110+ prefix = " ai" ;
111+ }
112+ type = prefix + " :shader" ;
113+ }
114+ }
115+
116+ pxr::VtValue typeVtValue = shader.GetPrim ().GetCustomDataByKey ( g_shaderTypeToken );
117+ if ( !typeVtValue.IsEmpty () )
118+ {
119+ type = typeVtValue.Get <std::string>();
120+ }
121+
122+ return { name, type };
79123 }
80124#if PXR_VERSION >= 2111
81125 else if ( auto light = pxr::UsdLuxLightAPI ( connectable ) )
82126 {
83- light.GetShaderIdAttr ().Get ( &id );
84- type = " light" ;
127+ return { light.GetShaderId ( {} ).GetString (), " light" };
85128 }
86129#endif
87130
88- return std::make_pair ( id, type ) ;
131+ return { " " , " " } ;
89132}
90133
91134bool writeNonStandardLightParameter ( const std::string &name, const IECore::Data *value, pxr::UsdShadeConnectableAPI usdShader )
@@ -232,24 +275,7 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co
232275 return handle;
233276 }
234277
235- auto [id, shaderType] = shaderIdAndType ( usdShader );
236- std::string shaderName = " defaultsurface" ;
237- if ( id.size () )
238- {
239- std::string name = id.GetString ();
240- size_t colonPos = name.find ( " :" );
241- if ( colonPos != std::string::npos )
242- {
243- std::string prefix = name.substr ( 0 , colonPos );
244- name = name.substr ( colonPos + 1 );
245- if ( prefix == " arnold" )
246- {
247- prefix = " ai" ;
248- }
249- shaderType = prefix + " :shader" ;
250- }
251- shaderName = name;
252- }
278+ // Read parameter values and connections.
253279
254280 IECore::CompoundDataPtr parametersData = new IECore::CompoundData ();
255281 IECore::CompoundDataMap ¶meters = parametersData->writable ();
@@ -295,6 +321,9 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co
295321
296322 readNonStandardLightParameters ( usdShader.GetPrim (), parameters );
297323
324+ // Create shader.
325+
326+ auto [shaderName, shaderType] = shaderNameAndType ( usdShader );
298327 IECoreScene::ShaderPtr newShader = new IECoreScene::Shader ( shaderName, shaderType, parametersData );
299328
300329 // General purpose support for any Cortex blind data.
@@ -395,35 +424,76 @@ pxr::UsdShadeConnectableAPI createShaderPrim( const IECoreScene::Shader *shader,
395424 throw IECore::Exception ( " Could not create shader at " + path.GetAsString () );
396425 }
397426
398- const std::string type = shader->getType ();
427+ // We need to declare the shader in a form corresponding to entries in USD's
428+ // Sdr registry, so that if rendered in `usdview` or another Hydra-based
429+ // app, the render delegates can find the shaders. We could potentially do
430+ // that done by finding a shader in the Sdr registry by name and then using
431+ // `SdrShaderNode.GetIdentifier()` or
432+ // `SdrShaderNode.GetResolvedImplementationURI()`, although it's not clear
433+ // how you'd decide which to use. Anyway, for now at least, we want to be
434+ // able to operate in environments without renderer-specific USD plugins
435+ // installed. So instead of querying the registry we use some heuristics of
436+ // our own.
399437
400- std::string typePrefix;
401438 if ( boost::starts_with ( shader->getName (), " Pxr" ) || boost::starts_with ( shader->getName (), " Lama" ) )
402439 {
403- // Leave the type prefix empty. This should be the default, but we are currently only doing this
404- // for a small number of shaders that we can be completely confident require it, in order to
405- // preserve backwards compatibility.
440+ // Could be either an OSL or a C++ shader, but either way, RenderMan
441+ // registers it in the SdrRegistry by name.
442+ usdShader.SetShaderId ( pxr::TfToken ( shader->getName () ) );
443+ }
444+ else if ( boost::starts_with ( shader->getType (), " ai:" ) )
445+ {
446+ // Arnold registers all plugins at startup, so also only needs the name
447+ // to be able to create a shader. It uses an `arnold:` prefix to avoid
448+ // clashes with the names of other shaders.
449+ usdShader.SetShaderId ( pxr::TfToken ( " arnold:" + shader->getName () ) );
450+ }
451+ else if (
452+ boost::starts_with ( shader->getType (), " osl:" ) &&
453+ writeConformantOSLShaders ()
454+ )
455+ {
456+ // Arbitrary OSL shader. Arnold would want that written as an
457+ // `arnold:osl` shader with `input:shadername` pointing to the shader.
458+ // But that will never work in another renderer. RenderMan takes a
459+ // slightly more generic approach by registering each OSL shader from
460+ // `RMAN_SHADERPATH` into the Sdr registry, so we follow that in the hope
461+ // that Arnold and other renderers might fall in line in future.
462+ if ( shader->getName ().find ( ' /' ) != std::string::npos )
463+ {
464+ // Unfortunately, RenderMan's SdrDiscoveryPlugin uses only the leaf
465+ // name, even though the Riley API will accept `{directory}/{file}`.
466+ // So we write the leaf name, but also record the full name so we
467+ // can round-trip back to Cortex exactly.
468+ usdShader.SetShaderId ( pxr::TfToken ( std::filesystem::path ( shader->getName () ).stem () ) );
469+ usdShader.GetPrim ().SetCustomDataByKey ( g_shaderNameToken, pxr::VtValue ( shader->getName () ) );
470+ }
471+ else
472+ {
473+ usdShader.SetShaderId ( pxr::TfToken ( shader->getName () ) );
474+ }
475+ // We've lost the fact that it was an OSL shader, so store that information
476+ // as custom data for use when loading.
477+ usdShader.GetPrim ().SetCustomDataByKey ( g_shaderTypeToken, pxr::VtValue ( shader->getType () ) );
478+ // / \todo We don't want to be resorting to storing custom data for the name or
479+ // / the type. Rejig things so that we only use leaf names for shaders, and don't
480+ // / rely on the type anywhere, and then remove the custom data.
406481 }
407482 else
408483 {
409- size_t typeColonPos = type.find ( " :" );
484+ const std::string &type = shader->getType ();
485+ const size_t typeColonPos = type.find ( " :" );
410486 if ( typeColonPos != std::string::npos )
411487 {
412- // According to our current understanding, this is almost completely wrong. Renderer's like
413- // PRMan won't accept shaders with type prefixes, and Arnold apparently requires all shaders
414- // to be prefixed with "arnold:", including OSL. This code prefixes OSL shaders with "osl:",
415- // which fails in all renderers we're aware of - we're keeping this behaviour for now for
416- // backwards compatibility reasons.
417- typePrefix = type.substr ( 0 , typeColonPos ) + " :" ;
418-
419- // This is the one case that actually works
420- if ( typePrefix == " ai:" )
421- {
422- typePrefix = " arnold:" ;
423- }
488+ // We don't currently know of any renderers where this is right, but
489+ // it is our historic behaviour, which we are keeping until we know better.
490+ usdShader.SetShaderId ( pxr::TfToken ( type.substr ( 0 , typeColonPos ) + " :" + shader->getName () ) );
491+ }
492+ else
493+ {
494+ usdShader.SetShaderId ( pxr::TfToken ( shader->getName () ) );
424495 }
425496 }
426- usdShader.SetShaderId ( pxr::TfToken ( typePrefix + shader->getName () ) );
427497
428498 return usdShader.ConnectableAPI ();
429499}
0 commit comments