Custom plugin and NdrRegistry

Hi guys,

I am creating a custom NdrRegistry with a nodeDefs.usda (containing definition of different nodes but all the same type, like Shader for UsdShadeShader).
I have a discoveryPlugin as well as a parser plugin that feed my custom NdrRegistry. But, at some point, I want to define a prim (one of my custom node in the nodeDefs.usda) into my stage with all the attributes, default values properties…
What is the best way ?

I can either use UsdPrim DefinePrim(const SdfPath &path, const TfToken &typeName=TfToken()); with my custom type and then I need to copy all relationships / attributes / properties that is inside my nodesDef.usda. So I need to keep in cache the stage to query the primspec from the name ?

Or use Sdf.CopySpec ? Not sure if this gonna work.

Thank you,

Baptiste

Hi Baptiste,

What’s your reasoning for choosing NdrRegistry over SdrRegistry? In less than a week we’ll be notifying people of the deprecation of Ndr in favor of Sdr, so your question comes at a good time :slight_smile:

In order for you to define a prim of your node type, you must first register it as a schema – usdgenschemafromsdr does exactly this:

It looks like you’ve already found this, but with Sdr, you can follow the pattern in usdShaders to register shader definitions with the Sdr registry.

Then once you have an Sdr node, the utility UpdateSchemaFromSdrNode helps generate this typed schema usda from this Shader definition usda. There are more examples in the directories I just linked. usdgenschemafromsdr is the generalization of UpdateSchemaFromSdrNode . This generalized script takes in a schemaConfig.json to select which shaders should be extracted to generate schema from; for example, to generate schema for “UsdUVTexture” your config may look like

{
  "sdrNodes": 
  {
    "Usd": [
      "UsdUVTexture"
  }
}

Does that work for what you’re trying to do?

Hi @anwang,

First thank you for your answer.
In my first post, it wasn’t clear, but I’m not doing shading graph. Instead, I’m trying to do a visual scripting language with general purposes nodes (constant int, add, multiply …) as well as GPU nodes (create render target, buffer …). usdShaders was a good starting point but this is why I chose NdrRegistry over SdrRegistry.

This is my pipeline for now (very early so I can still change some codes).
I have a nodeDefs.usda with for example

#usda 1.0

def LmNode "ConstantInt"(
    lmnMetadata = { 
		token category = "Constants"
		token label = "Constant Int"}
    doc = """Constant integer"""
)
{
    int outputs:value = 0
}

def LmNode "ConstantFloat"(
	lmnMetadata = { 
		token category = "Constants"
		token label = "Constant Float"}
    doc = """Constant float"""
)
{
    float outputs:value = 0.0
}

Then a discoveryPlugin / parser feed my custom registry (inheriting from NdrRegistry) to retrieve all my nodes.
When I want to instanciate a prim in my stage with a nodeName, what am doing right now, is to pick up the resolvedImplmementatiounURI and reopen the stage to do a SdfCopySpec from the nodeDefs.usda to my layer.
So, I realized that I am using the registry just to get the resolvedImplementationURI form a nodeName. (But it lets open another plugin that is not a usda to feed my node registry, and I can still use the NdrProperty to feed the inputs and outputs). So will have only LmNode in my graph. If I understand correcly, what you suggest is to create a custom schema for every node.
In your opinion, what are the pros and cons of each method ?
I think in my case, there is less code but maybe for complex prim, having a generic LmNode won’t be enough. Maybe I can do a hybrid approach, with both custom schema and nodeDefs.

Thank you!

Baptiste

I believe we’re thinking about Shaders in slightly different ways. On the USD side, we treat a shader as “any arbitrary piece of code that runs on a GPU” (edit) “any piece of code to be executed by a non-USD subsystem” (we need to improve documentation on USD to convey this properly). For example, usdLux uses SdrRegistry even though it is for “lighting” and not necessarily “texturing an object”.

So, I realized that I am using the registry just to get the resolvedImplementationURI form a nodeName. But it lets open another plugin that is not a usda to feed my node registry, and I can still use the NdrProperty to feed the inputs and outputs

Yep, it sounds like you don’t need a subclass registry in this case. Wherever you need it, I think you can use existing methods to do something like

node = registry.GetNodeByName(...)
node.GetResolvedDefinitionURI()

You also don’t need a custom registry in order to register a plugin that reads non-usda files. For example, the usdShaders discovery plugin does read from usda to propagate results, but you could change the DiscoverNodes function in your own plugin to return whichever NodeDiscoveryResults you want for your implementation. Now I realize these are all Ndr classes, but the parser plugin is the point at which you register the SdrShaderNode. SdrShaderProperty should have all NdrProperty methods, so inputs/outputs should still be trackable with Sdr.

When I want to instanciate a prim in my stage with a nodeName, what am doing right now, is to pick up the resolvedImplmementatiounURI and reopen the stage to do a SdfCopySpec from the nodeDefs.usda to my layer.

If I understand correcly, what you suggest is to create a custom schema for every node.
In your opinion, what are the pros and cons of each method ?

Your current approach

  • con: The copies, reopening the stage, and reading from nodeDefs.usda is more expensive than usdSchemaRegistry already knowing about the types via the schema registration process.
  • pro: This is potentially more flexible than the alternative, allowing for types to be controlled dynamically by just updating the source nodeDefs.usda.

As for the approach I’ve suggested above

  • pro: If we have the schemas registered via usdgenschemafromsdr, then generatedSchema.usda encapsulates all the necessary information from the nodeDefs.usda for USD to know the contents of any registered “nodeName” type when defining the prim. For instance, with a generated schema DefinePrim would know what the ConstantInt, ConstantFloat, AddOp, MultiplyOp types are and what they contain without requiring you to copy all the properties over with ResolveImplementationURI magic.
  • con: There is an extra step to perform, usdgenschemafromsdr. The upside is that you won’t have to rebuild USD, since the schemas are codeless.

Just to provide some input on we handle this at Meta for Avatars, we utilize NDR for node registration also (we’ll need to migrate to SDR it seems but we are also on an older version of USD), our node registration is tied to our build system as opposed to pulling in other artifacts, we register C++ code based node definitions at runtime. Our usecase is building an artist defined content pipeline.

This has been helpful as we don’t have multiple sources of truth for our nodes, sources don’t go stale. It’s also helped with development velocity as engineers only have to worry about one place to define nodes (the code)

I believe that none of the benefits you’ve described should change with the migration to SDR @Burkard but do let us know if you run into additional issues in upgrading USD versions. The NDR deprecation notification/guide is live now at Deprecation of pxr/usd/ndr

1 Like