Skip to content

Commit 72e5433

Browse files
Merge branch 'RB-10.6'
2 parents 2e5a4a9 + 0225ead commit 72e5433

4 files changed

Lines changed: 199 additions & 47 deletions

File tree

Changes

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
10.7.x.x (relative to 10.7.0.0a11)
22
========
33

4+
Improvements
5+
------------
46

7+
- USDScene : Added `IECOREUSD_WRITE_CONFORMANT_OSL_SHADERS` environment variable. If set to a value of `1`, this causes OSL shaders to be written in the format expected by RenderMan. Set the `RMAN_SHADERPATH` environment variable appropriately to ensure RenderMan can find the shaders [^1].
8+
9+
[^1]: To be omitted from the notes for the final 10.7.0.0 release.
510

611
10.7.0.0a11 (relative to 10.7.0.0a10)
712
===========
@@ -223,10 +228,18 @@ Breaking Changes
223228
- Removed deprecated functions componentConnectionAdapterLabel, convertOSLComponentConnections, collapseSplineParameters, expandSplineParameters.
224229
- Switch support for Spline parameters to support for Ramp parameters.
225230

226-
10.6.x.x (relative to 10.6.5.0)
231+
10.6.x.x (relative to 10.6.6.0)
232+
========
233+
234+
235+
236+
10.6.6.0 (relative to 10.6.5.0)
227237
========
228238

239+
Improvements
240+
------------
229241

242+
- USDScene : Added `IECOREUSD_WRITE_CONFORMANT_OSL_SHADERS` environment variable. If set to a value of `1`, this causes OSL shaders to be written in the format expected by RenderMan. Set the `RMAN_SHADERPATH` environment variable appropriately to ensure RenderMan can find the shaders.
230243

231244
10.6.5.0 (relative to 10.6.4.0)
232245
========

contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp

Lines changed: 112 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,78 @@
5555
#include "boost/algorithm/string/replace.hpp"
5656
#include "boost/pointer_cast.hpp"
5757

58+
#include <filesystem>
5859
#include <regex>
5960

6061
namespace
6162
{
6263

6364
const pxr::TfToken g_blindDataToken( "cortex:blindData" );
65+
const pxr::TfToken g_shaderNameToken( "cortex:shaderName" );
66+
const pxr::TfToken g_shaderTypeToken( "cortex:shaderType" );
6467

6568
// Hardcoded to match an old name used by Cortex when writing USD with OSL version earlier than 1.10 (
6669
// ie. a pre-2021 version of gafferDependencies ). We no longer expose this in the API, but I'm not sure
6770
// if we're ready to drop support for loading these old files.
6871
IECore::InternedString g_legacyAdapterLabelString( "cortex_autoAdapter" );
6972
pxr::TfToken g_legacyAdapterLabelToken( g_legacyAdapterLabelString.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
else if( auto light = pxr::UsdLuxLightAPI( connectable ) )
81125
{
82-
light.GetShaderIdAttr().Get( &id );
83-
type = "light";
126+
return { light.GetShaderId( {} ).GetString(), "light" };
84127
}
85128

86-
return std::make_pair( id, type );
129+
return { "", "" };
87130
}
88131

89132
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
225268
return handle;
226269
}
227270

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.
246272

247273
IECore::CompoundDataPtr parametersData = new IECore::CompoundData();
248274
IECore::CompoundDataMap &parameters = parametersData->writable();
@@ -288,6 +314,9 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co
288314

289315
readNonStandardLightParameters( usdShader.GetPrim(), parameters );
290316

317+
// Create shader.
318+
319+
auto [shaderName, shaderType] = shaderNameAndType( usdShader );
291320
IECoreScene::ShaderPtr newShader = new IECoreScene::Shader( shaderName, shaderType, parametersData );
292321

293322
// General purpose support for any Cortex blind data.
@@ -388,35 +417,73 @@ pxr::UsdShadeConnectableAPI createShaderPrim( const IECoreScene::Shader *shader,
388417
throw IECore::Exception( "Could not create shader at " + path.GetAsString() );
389418
}
390419

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.
392430

393-
std::string typePrefix;
394431
if( boost::starts_with( shader->getName(), "Pxr" ) || boost::starts_with( shader->getName(), "Lama" ) )
395432
{
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+
}
399471
}
400472
else
401473
{
402-
size_t typeColonPos = type.find( ":" );
474+
const std::string &type = shader->getType();
475+
const size_t typeColonPos = type.find( ":" );
403476
if( typeColonPos != std::string::npos )
404477
{
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() ) );
417485
}
418486
}
419-
usdShader.SetShaderId( pxr::TfToken( typePrefix + shader->getName() ) );
420487

421488
return usdShader.ConnectableAPI();
422489
}

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,6 @@ void populateMaterial( pxr::UsdShadeMaterial &mat, const boost::container::flat_
485485
for( const auto &[output, shaderNetwork] : shaders )
486486
{
487487
pxr::UsdShadeOutput matOutput = mat.CreateOutput( output, pxr::SdfValueTypeNames->Token );
488-
489488
std::string shaderContainerName = boost::replace_all_copy( output.GetString(), ":", "_" ) + "_shaders";
490489
pxr::UsdGeomScope shaderContainer = pxr::UsdGeomScope::Define( mat.GetPrim().GetStage(), mat.GetPath().AppendChild( pxr::TfToken( shaderContainerName ) ) );
491490
pxr::UsdShadeOutput networkOut = ShaderAlgo::writeShaderNetwork( shaderNetwork.get(), shaderContainer.GetPrim() );

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4967,5 +4967,78 @@ def testMaterialTerminalFromSubgraph( self ) :
49674967

49684968
self.assertEqual( shaderNetwork.outputShader().name, "LamaSurface" )
49694969

4970+
def testOSLShaderForHDPrman( self ) :
4971+
4972+
self.addCleanup( os.environ.__delitem__, "IECOREUSD_WRITE_CONFORMANT_OSL_SHADERS" )
4973+
4974+
for conformant in True, False :
4975+
4976+
with self.subTest( conformant = conformant ) :
4977+
4978+
os.environ["IECOREUSD_WRITE_CONFORMANT_OSL_SHADERS"] = str( int( conformant ) )
4979+
4980+
fileName = os.path.join( self.temporaryDirectory(), f"testConformance{conformant}.usda" )
4981+
4982+
shaderNetwork = IECoreScene.ShaderNetwork(
4983+
shaders = {
4984+
"output" : IECoreScene.Shader( "PxrDiffuse", "ri:surface" ),
4985+
"texture" : IECoreScene.Shader( "Pattern/Noise", "osl:shader" ),
4986+
"scale" : IECoreScene.Shader( "floatAttribute", "osl:shader" ),
4987+
},
4988+
connections = [
4989+
( ( "scale", "out" ), ( "texture", "scale" ) ),
4990+
( ( "texture", "out" ), ( "output", "diffuseColor" ) ),
4991+
],
4992+
output = ( "output", "bxdf_out" ),
4993+
)
4994+
4995+
# Test writing to USD.
4996+
4997+
scene = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write )
4998+
scene.createChild( "test" ).writeAttribute( "ri:surface", shaderNetwork, 0 )
4999+
del scene
5000+
5001+
stage = pxr.Usd.Stage.Open( fileName )
5002+
5003+
outputShader = pxr.UsdShade.Shader(
5004+
next( p for p in stage.Traverse() if p.IsA( pxr.UsdShade.Shader ) and p.GetName() == "output" )
5005+
)
5006+
self.assertEqual( outputShader.GetShaderId(), "PxrDiffuse" )
5007+
5008+
textureShader = pxr.UsdShade.Shader(
5009+
next( p for p in stage.Traverse() if p.IsA( pxr.UsdShade.Shader ) and p.GetName() == "texture" )
5010+
)
5011+
if conformant :
5012+
self.assertEqual( textureShader.GetShaderId(), "Noise" )
5013+
else :
5014+
self.assertEqual( textureShader.GetShaderId(), "osl:Pattern/Noise" )
5015+
5016+
scaleShader = pxr.UsdShade.Shader(
5017+
next( p for p in stage.Traverse() if p.IsA( pxr.UsdShade.Shader ) and p.GetName() == "scale" )
5018+
)
5019+
if conformant :
5020+
self.assertEqual( scaleShader.GetShaderId(), "floatAttribute" )
5021+
else :
5022+
self.assertEqual( scaleShader.GetShaderId(), "osl:floatAttribute" )
5023+
5024+
# Test loading back to Cortex.
5025+
5026+
scene = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
5027+
loadedShaderNetwork = scene.child( "test" ).readAttribute( "ri:surface", 0 )
5028+
5029+
if conformant :
5030+
# Can't round trip. We will need to adjust our usage to avoid shaders
5031+
# nested in directories.
5032+
self.assertEqual( loadedShaderNetwork.getShader( "texture" ).name, "Noise" )
5033+
self.assertEqual( loadedShaderNetwork.getShader( "texture" ).type, "surface" )
5034+
self.assertEqual( loadedShaderNetwork.getShader( "scale" ).name, "floatAttribute" )
5035+
self.assertEqual( loadedShaderNetwork.getShader( "scale" ).type, "surface" )
5036+
else :
5037+
# Round tripped exactly.
5038+
self.assertEqual( loadedShaderNetwork.getShader( "texture" ).name, "Pattern/Noise" )
5039+
self.assertEqual( loadedShaderNetwork.getShader( "texture" ).type, "osl:shader" )
5040+
self.assertEqual( loadedShaderNetwork.getShader( "scale" ).name, "floatAttribute" )
5041+
self.assertEqual( loadedShaderNetwork.getShader( "scale" ).type, "osl:shader" )
5042+
49705043
if __name__ == "__main__":
49715044
unittest.main()

0 commit comments

Comments
 (0)