-
Notifications
You must be signed in to change notification settings - Fork 407
Description
Proposal: Functional Node Definitions (Non-Graph Implementations)
This proposal extends the former proposal to support functional nodedefs to include non-graph implementations.
Motivation
The main motivations are still:
-
To support encapsulation via noe definitions which are atomic
Elements containing both the interface and implementation declaration. Hence making it easier to manage and distribute node definitions as atomic units. -
To support further consolidation of definition via any "templating" used to handle polymorphic node definitions.
-
To use as a base for future OpenUSD schema definitions to encapsulate both interface and implementation.
We extend the original proposal to include non-graph implementations to further consolidate node definitions.
Generalization
Currently NodeGraphs are derived from Implementation Elements. The natural extension is to allow any Element which is either an Implementation or is derived from it (NodeGraph).
Non-nodegraph implementations generally have a target specifier to indicate that the implementation is specific to a target deployment or language. While it is not required to have full encapsulation a mechanism to allow including all "targets" implementations within a single functional nodedef is desirable.
Implementation Variants
-
nodedef: Both nodegraph and non-graph implementations can have a back-reference to theirnodedef(1) -
target: Implementations are per-target specific. -
file: Implementations may reference external files (2)<implementation name="IM_rotate2d_vector2_genglsl" nodedef="ND_rotate2d_vector2" file="mx_rotate_vector2.glsl" function="mx_rotate_vector2" target="genglsl" />
-
sourcecode: Implementations may inline source code. Example:<implementation name="IM_floor_float_genglsl" nodedef="ND_floor_float" target="genglsl" sourcecode="floor({{in}})" />
-
if there is no source reference then these implementations fall into the category of having an externally defined implementation -- possibly as part of a code generator. (3)
Notes:
- This would no longer be required as the implementation is contained within the
nodedef. - Handling external file references is not part of this proposal. There have been separate discussions if and how this should be handled, including:
- Encoding the file content into something that is storable as
XMLutf-8 text which would be basically inlining the file content.
- Encoding the file content into something that is storable as
- Note that even though
MDLkeeps a module structure for it's actual code implementations, the implementation "glue" via<implementation>elements is the only association of concern. i.e. These would be repackaged as part of the functionalnodedefwithout affecting the actual MDL module source structure.
Examples:
Below is an example of the definition for float image which has per target non-graph implementations (ND_image_float):
Current separate definition:
<nodedef name="ND_image_float" node="image" nodegroup="texture2d">
<input name="file" type="filename" value="" uiname="Filename" uniform="true">
<input name="layer" type="string" value="" uiname="Layer" uniform="true">
<input name="default" type="float" value="0.0" uiname="Default Color">
<input name="texcoord" type="vector2" defaultgeomprop="UV0" uiname="Texture Coordinates">
<input name="uaddressmode" type="string" value="periodic" enum="constant,clamp,periodic,mirror" uiname="Address Mode U" uniform="true">
<input name="vaddressmode" type="string" value="periodic" enum="constant,clamp,periodic,mirror" uiname="Address Mode V" uniform="true">
<input name="filtertype" type="string" value="linear" enum="closest,linear,cubic" uiname="Filter Type" uniform="true">
<input name="framerange" type="string" value="" uiname="Frame Range" uniform="true">
<input name="frameoffset" type="integer" value="0" uiname="Frame Offset" uniform="true">
<input name="frameendaction" type="string" value="constant" enum="constant,clamp,periodic,mirror" uiname="Frame End Action" uniform="true">
<output name="out" type="float" default="0.0">
</nodedef>and one of the implementations (for genglsl):
<implementation name="IM_image_float_genglsl" nodedef="ND_image_float" file="mx_image_float.glsl" function="mx_image_float" target="genglsl">
<input name="default" type="float" implname="default_value" />
</implementation>The combined functional definition with all of default standard library "targets" added
would look like this:
<nodedef name="ND_image_float" node="image" nodegroup="texture2d">
<input name="file" type="filename" value="" uiname="Filename" uniform="true">
<input name="layer" type="string" value="" uiname="Layer" uniform="true">
<input name="default" type="float" value="0.0" uiname="Default Color">
<input name="texcoord" type="vector2" defaultgeomprop="UV0" uiname="Texture Coordinates">
<input name="uaddressmode" type="string" value="periodic" enum="constant,clamp,periodic,mirror" uiname="Address Mode U" uniform="true">
<input name="vaddressmode" type="string" value="periodic" enum="constant,clamp,periodic,mirror" uiname="Address Mode V" uniform="true">
<input name="filtertype" type="string" value="linear" enum="closest,linear,cubic" uiname="Filter Type" uniform="true">
<input name="framerange" type="string" value="" uiname="Frame Range" uniform="true">
<input name="frameoffset" type="integer" value="0" uiname="Frame Offset" uniform="true">
<input name="frameendaction" type="string" value="constant" enum="constant,clamp,periodic,mirror" uiname="Frame End Action" uniform="true">
<output name="out" type="float" default="0.0">
<implementation name="IM_image_float_genglsl" file="mx_image_float.glsl" function="mx_image_float" target="genglsl">
<input name="default" type="float" implname="default_value">
</implementation>
<implementation name="IM_image_float_genmdl" sourcecode="materialx::stdlib_{{MDL_VERSION_SUFFIX}}::mx_image_float({{file}}, {{layer}}, {{default}}, {{texcoord}}, {{uaddressmode}}, {{vaddressmode}}, {{filtertype}}, {{framerange}}, {{frameoffset}}, {{frameendaction}}, mxp_flip_v:{{flip_v}})" target="genmdl">
</implementation>
<input name="default" type="float" implname="default_value">
<implementation name="IM_image_float_genmsl" file="../genglsl/mx_image_float.glsl" function="mx_image_float" target="genmsl">
<input name="default" type="float" implname="default_value">
</implementation>
<implementation name="IM_image_float_genosl" file="mx_image_float.osl" function="mx_image_float" target="genosl">
<input name="default" type="float" implname="default_value">
</implementation>
<implementation name="IM_image_float_genslang" file="../genglsl/mx_image_float.glsl" function="mx_image_float" target="genslang">
<input name="default" type="float" implname="default_value">
</implementation>
</nodedef>Second example for ND_normal_vector3 with a mix of source code and external implementations
<nodedef name="ND_position_vector3" node="position" nodegroup="geometric">
<input name="space" type="string" value="object" enum="model,object,world" uniform="true">
<output name="out" type="vector3" default="0.0, 0.0, 0.0">
<implementation name="IM_position_vector3_genglsl" target="genglsl" />
<implementation name="IM_position_vector3_genmdl" sourcecode="materialx::stdlib_{{MDL_VERSION_SUFFIX}}::mx_position_vector3(mxp_space:{{space}})" target="genmdl" />
<implementation name="IM_position_vector3_genmsl" target="genmsl" />
<implementation name="IM_position_vector3_genosl" target="genosl" sourcecode="transform({{space}}, P)" />
<implementation name="IM_position_vector3_genslang" target="genslang" />
</nodedef>Mixed Graph and Non-Graph Implementations
It is possible to have both graph and non-graph implementations within a single functional nodedef. In this case, all the implementations would be added as children of the nodedef.
Precedence
- As this is an "additive" change, existing functional
nodedefs with separated implementations can still be used. - Un-encapsulated implementations have higher precedence over encapsulated ones. This is the same precedence logic as with functional
nodedefs using graph implementations.
Modifications Required
- The
Documentdefinition / implementation association cache would need to consider non-graph implementations. The final cached content would still look the same and all existing API logic and entry point interfaces would not change. - Any utilities to manage functional node definitions would need to be updated to handle non-graph implementations. This includes utilities to inline existing
implemtantions into functionalnodedefs.
Both of these changes are localized and straightforward to implement.
Further Consolidation
It is possible to have further consolidation by allow for multiple targets to be specified within a single implementation Element. In the example above the genglsl, genmsl, and genslang implementations could be combined, however if any of the implementations diverge in the future it would require explicitly splitting.
This can be considered as a future extension if there is a strong use case.
Appendix: Test Code
Below is sample logic on how to append to a node definition using any implementation variant. It was used to create the previous examples. (A variant of this is included
as part of the functional nodedef utilities in the previous propsal.)
def make_functional_definition(test_name, target=''):
"""
Add per-target implementations to a functional nodedef.
"""
test_def = stdlib.getNodeDef(test_name)
if test_def:
impl = test_def.getImplementation(target)
if impl:
new_impl = None
ngname = impl.getNodeDefString()
qualname = impl.getQualifiedName(test_def.getName())
if ngname == qualname:
new_impl_name = impl.getName()
new_impl = test_def.addChildOfCategory(impl.getCategory(), new_impl_name)
if not new_impl:
print("Failed to create new functional def child:", new_impl_name)
return None
else:
# Create new functional implementation
new_impl.copyContentFrom(impl)
new_impl.removeAttribute(mx.InterfaceElement.NODE_DEF_ATTRIBUTE)
# Clear out back-reference to nodedef on original impl
impl.removeAttribute(mx.InterfaceElement.NODE_DEF_ATTRIBUTE)
return test_def