|
55 | 55 | #include "boost/algorithm/string/replace.hpp" |
56 | 56 | #include "boost/pointer_cast.hpp" |
57 | 57 |
|
| 58 | +#include <filesystem> |
58 | 59 | #include <regex> |
59 | 60 |
|
60 | 61 | namespace |
61 | 62 | { |
62 | 63 |
|
63 | 64 | const pxr::TfToken g_blindDataToken( "cortex:blindData" ); |
| 65 | +const pxr::TfToken g_shaderNameToken( "cortex:shaderName" ); |
| 66 | +const pxr::TfToken g_shaderTypeToken( "cortex:shaderType" ); |
64 | 67 |
|
65 | 68 | // Hardcoded to match an old name used by Cortex when writing USD with OSL version earlier than 1.10 ( |
66 | 69 | // ie. a pre-2021 version of gafferDependencies ). We no longer expose this in the API, but I'm not sure |
67 | 70 | // if we're ready to drop support for loading these old files. |
68 | 71 | IECore::InternedString g_legacyAdapterLabelString( "cortex_autoAdapter" ); |
69 | 72 | pxr::TfToken g_legacyAdapterLabelToken( g_legacyAdapterLabelString.string() ); |
70 | 73 |
|
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 ) |
72 | 86 | { |
73 | | - pxr::TfToken id; |
74 | | - std::string type; |
75 | 87 | if( auto shader = pxr::UsdShadeShader( connectable ) ) |
76 | 88 | { |
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 }; |
79 | 123 | } |
80 | 124 | else if( auto light = pxr::UsdLuxLightAPI( connectable ) ) |
81 | 125 | { |
82 | | - light.GetShaderIdAttr().Get( &id ); |
83 | | - type = "light"; |
| 126 | + return { light.GetShaderId( {} ).GetString(), "light" }; |
84 | 127 | } |
85 | 128 |
|
86 | | - return std::make_pair( id, type ); |
| 129 | + return { "", "" }; |
87 | 130 | } |
88 | 131 |
|
89 | 132 | bool writeNonStandardLightParameter( const std::string &name, const IECore::Data *value, pxr::UsdShadeConnectableAPI usdShader ) |
@@ -225,24 +268,7 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co |
225 | 268 | return handle; |
226 | 269 | } |
227 | 270 |
|
228 | | - auto [id, shaderType] = shaderIdAndType( usdShader ); |
229 | | - std::string shaderName = "defaultsurface"; |
230 | | - if( id.size() ) |
231 | | - { |
232 | | - std::string name = id.GetString(); |
233 | | - size_t colonPos = name.find( ":" ); |
234 | | - if( colonPos != std::string::npos ) |
235 | | - { |
236 | | - std::string prefix = name.substr( 0, colonPos ); |
237 | | - name = name.substr( colonPos + 1 ); |
238 | | - if( prefix == "arnold" ) |
239 | | - { |
240 | | - prefix = "ai"; |
241 | | - } |
242 | | - shaderType = prefix + ":shader"; |
243 | | - } |
244 | | - shaderName = name; |
245 | | - } |
| 271 | + // Read parameter values and connections. |
246 | 272 |
|
247 | 273 | IECore::CompoundDataPtr parametersData = new IECore::CompoundData(); |
248 | 274 | IECore::CompoundDataMap ¶meters = parametersData->writable(); |
@@ -288,6 +314,9 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co |
288 | 314 |
|
289 | 315 | readNonStandardLightParameters( usdShader.GetPrim(), parameters ); |
290 | 316 |
|
| 317 | + // Create shader. |
| 318 | + |
| 319 | + auto [shaderName, shaderType] = shaderNameAndType( usdShader ); |
291 | 320 | IECoreScene::ShaderPtr newShader = new IECoreScene::Shader( shaderName, shaderType, parametersData ); |
292 | 321 |
|
293 | 322 | // General purpose support for any Cortex blind data. |
@@ -388,35 +417,73 @@ pxr::UsdShadeConnectableAPI createShaderPrim( const IECoreScene::Shader *shader, |
388 | 417 | throw IECore::Exception( "Could not create shader at " + path.GetAsString() ); |
389 | 418 | } |
390 | 419 |
|
391 | | - const std::string type = shader->getType(); |
| 420 | + // We need to declare the shader in a form corresponding to entries in USD's |
| 421 | + // Sdr registry, so that if rendered in `usdview` or another Hydra-based |
| 422 | + // app, the render delegates can find the shaders. We could potentially do |
| 423 | + // that done by finding a shader in the Sdr registry by name and then using |
| 424 | + // `SdrShaderNode.GetIdentifier()` or |
| 425 | + // `SdrShaderNode.GetResolvedImplementationURI()`, although it's not clear |
| 426 | + // how you'd decide which to use. Anyway, for now at least, we want to be |
| 427 | + // able to operate in environments without renderer-specific USD plugins |
| 428 | + // installed. So instead of querying the registry we use some heuristics of |
| 429 | + // our own. |
392 | 430 |
|
393 | | - std::string typePrefix; |
394 | 431 | if( boost::starts_with( shader->getName(), "Pxr" ) || boost::starts_with( shader->getName(), "Lama" ) ) |
395 | 432 | { |
396 | | - // Leave the type prefix empty. This should be the default, but we are currently only doing this |
397 | | - // for a small number of shaders that we can be completely confident require it, in order to |
398 | | - // preserve backwards compatibility. |
| 433 | + // Could be either an OSL or a C++ shader, but either way, RenderMan |
| 434 | + // registers it in the SdrRegistry by name. |
| 435 | + usdShader.SetShaderId( pxr::TfToken( shader->getName() ) ); |
| 436 | + } |
| 437 | + else if( boost::starts_with( shader->getType(), "ai:" ) ) |
| 438 | + { |
| 439 | + // Arnold registers all plugins at startup, so also only needs the name |
| 440 | + // to be able to create a shader. It uses an `arnold:` prefix to avoid |
| 441 | + // clashes with the names of other shaders. |
| 442 | + usdShader.SetShaderId( pxr::TfToken( "arnold:" + shader->getName() ) ); |
| 443 | + } |
| 444 | + else if( |
| 445 | + boost::starts_with( shader->getType(), "osl:" ) && |
| 446 | + writeConformantOSLShaders() |
| 447 | + ) |
| 448 | + { |
| 449 | + // Arbitrary OSL shader. Arnold would want that written as an |
| 450 | + // `arnold:osl` shader with `input:shadername` pointing to the shader. |
| 451 | + // But that will never work in another renderer. RenderMan takes a |
| 452 | + // slightly more generic approach by registering each OSL shader from |
| 453 | + // `RMAN_SHADERPATH` into the Sdr registry, so we follow that in the hope |
| 454 | + // that Arnold and other renderers might fall in line in future. |
| 455 | + if( shader->getName().find( '/' ) != std::string::npos ) |
| 456 | + { |
| 457 | + // Unfortunately, RenderMan's SdrDiscoveryPlugin uses only the leaf |
| 458 | + // name, even though the Riley API will accept `{directory}/{file}`. |
| 459 | + // So we write the leaf name, and accept that we can no longer |
| 460 | + // round-trip exactly (we are also losing the shader type). |
| 461 | + // |
| 462 | + // Our plan is that in future we'll flatten our shader libraries |
| 463 | + // into a single directory, and remove the few remaining spots in |
| 464 | + // the codebase that rely on `Shader::getType()`. |
| 465 | + usdShader.SetShaderId( pxr::TfToken( std::filesystem::path( shader->getName() ).stem().string() ) ); |
| 466 | + } |
| 467 | + else |
| 468 | + { |
| 469 | + usdShader.SetShaderId( pxr::TfToken( shader->getName() ) ); |
| 470 | + } |
399 | 471 | } |
400 | 472 | else |
401 | 473 | { |
402 | | - size_t typeColonPos = type.find( ":" ); |
| 474 | + const std::string &type = shader->getType(); |
| 475 | + const size_t typeColonPos = type.find( ":" ); |
403 | 476 | if( typeColonPos != std::string::npos ) |
404 | 477 | { |
405 | | - // According to our current understanding, this is almost completely wrong. Renderer's like |
406 | | - // PRMan won't accept shaders with type prefixes, and Arnold apparently requires all shaders |
407 | | - // to be prefixed with "arnold:", including OSL. This code prefixes OSL shaders with "osl:", |
408 | | - // which fails in all renderers we're aware of - we're keeping this behaviour for now for |
409 | | - // backwards compatibility reasons. |
410 | | - typePrefix = type.substr( 0, typeColonPos ) + ":"; |
411 | | - |
412 | | - // This is the one case that actually works |
413 | | - if( typePrefix == "ai:" ) |
414 | | - { |
415 | | - typePrefix = "arnold:"; |
416 | | - } |
| 478 | + // We don't currently know of any renderers where this is right, but |
| 479 | + // it is our historic behaviour, which we are keeping until we know better. |
| 480 | + usdShader.SetShaderId( pxr::TfToken( type.substr( 0, typeColonPos ) + ":" + shader->getName() ) ); |
| 481 | + } |
| 482 | + else |
| 483 | + { |
| 484 | + usdShader.SetShaderId( pxr::TfToken( shader->getName() ) ); |
417 | 485 | } |
418 | 486 | } |
419 | | - usdShader.SetShaderId( pxr::TfToken( typePrefix + shader->getName() ) ); |
420 | 487 |
|
421 | 488 | return usdShader.ConnectableAPI(); |
422 | 489 | } |
|
0 commit comments