OmniGraph Node Architects Guide

This outlines the code that will be generated behind the scenes by the node generator from a .ogn file. This includes the API presented to the node that is outlined in OGN User Guide.

As the details of the generated code are expected to change rapidly this guide does not go into specific details, it only provides the broad strokes, expecting the developer to go to the either the generated code or the code generation scripts for the current implementation details.

Helper Templates

A lot of the generated C++ code relies on templates defined in the helper files omni/graph/core/Ogn*.h, which contains code that assists in the registration and running of the node, classes to wrap the internal data used by the node for evaluation, and general type conversion assistance.

None of the code in there is compiled so there is no ABI compatibility problem. This is a critical feature of these wrappers.

See the files themselves for full documentation on what they handle.

C ABI Interface

The key piece of generated code is the one that links the external C ABI for the compute node with the class implementing a particular node’s evaluation algorithm. This lets the node writer focus on their business logic rather than boilerplate ABI conformity details.

The actual ABI implementation for the INodeType interface is handled by the templated helper class OmniGraphNode_ABI from OgnHelpers.h. It implements all of the functions required by the ABI, then uses the “tag-dispatching” technique to selectively call either the default implementation, or the one provided by the node writer.

In turn, that class has a derived class of RegisterOgnNode (defined in OgnHelpers.h). It adds handling of the automatic registration and deregistration of node types.

Instantiation of the ABI helper class for a given node type is handled by the REGISTER_OGN_NODE() macro, required at the end of every node implementation file.

Attribute Information Caching

ABI access requires attribute information be looked up by name, which can cause a lot of inefficiency. To prevent this, a templated helper class IAttributeOgnInfo is created for every attribute. It contains the attribute’s information, such as its name, its unique handle token, and default value (if any). This is information that is the same for the attribute in every instance of the node so there is only one static instance of it.

Fabric Access

This is the core of the benefit provided by the .ogn generated interface. Every node has a OgnMyNodeDatabase class generated for it, which contains accessors to all of the Fabric data in an intuitive form. The base class OmniGraphDatabase, from OgnHelpers.h, provides the common functionality for that access, including token conversion, and access to the context and node objects provided by the ABI.

The attribute data access is accomplished with two or three pieces of data for every attribute.

  1. A raw pointer to the data for that attribute residing in the Fabric

  2. (optional, for arrays only) A raw pointer to the element count for the Fabric array data

  3. A wrapper class, which will return a reference object from its operator() function

The data accessors are put inside structs named inputs and outputs so that data can be accessed by the actual attribute’s name, e.g. db.inputs.myInputAttribute().

Accessing Data

The general approach to accessing attribute data on a node is to wrap the Fabric data in a class that is suited to handle the movement of data between Fabric and the node in a way that is more natural to the node writer.

For consistency of access, the accessors all override the call operator (operator()) to return a wrapper class that provides access to the data in a natural form. For example, simple POD data will return a direct reference to the underlying Fabric data (e.g. a float&) for manipulation.

Inputs typically provide const access only. The declaration of the return values can always be simplified by using const auto& for inputs and auto& for outputs and state attributes.

More complex types will return wrappers tailored towards their own access. Where it gets tricky is with variable sized attributes, such as arrays or bundles. We cannot rely on standard data types to manage them as Fabric is the sole arbiter of memory management.

Such classes are returned as wrappers that operate as though they are standard types but are actually intermediates for translating those operations into Fabric manipulation.

For example instead of retrieving arrays as raw float* or std::vector<float> they will be retrieved as the wrapper class ogn::array or ogn::const_array. This allows all the same manipulations as a std::vector<float> including iteration for use in the STL algorithm library, through Fabric interfaces.

If you are familiar with the concept of std::span an array can be thought of in the same way, where the raw data is managed by Fabric.

Data Initialization

All of this data must be initialized before the compute() method can be called. This is done in the constructor of the generated database class, which is constructed by the ABI wrapper to the node’s compute method.

Here’s a pseudocode view of the calls that would result from a node compute request:

OmniGraphNode_ABI<MyNode, MyNodeDatabase>::compute(graphContext, node);
    OgnMyNodeDatabase db(graphContext, node);
        getAttributesR for all input attributes
        getAttributesW for all output attributes
        getDataR for all input attributes
        getElementCount for all input array attributes
        getDataW for all output attributes
        getElementCount for all output array attributes
    return db.validate() ? OgnMyNode::compute(db) : false;

Input Validation

The node will have specified certain conditions required in order for the compute function to be valid. In the simplest form it will require that a set of input and output attributes exist on the node. Rather than forcing every node writer to check these conditions before they run their algorithm this is handled by a generated validate() function.

This function checks the underlying Fabric data to make sure it conforms to the conditions required by the node.

In future versions this validation will perform more sophisticated tasks, such as confirming that two sets of input arrays have the same size, or that attribute values are within the legal ranges the node can recognize.

Python Support

Python support comes in two forms - support for C++ nodes, and ABI definition for Python node implementations.

For any node written in C++ there is a Python accessor class created that contains properties for accessing the attribute data on the node. The evaluation context is passed to the accessor class so it can only be created when one is available.

The accessor is geared for getting and setting values on the attributes of a node.

import omni.graph.core as og
from my.extension.ogn import OgnMyNodeDatabase

my_node = og.get_graph_by_path("/World/PushGraph").get_node("/World/PushGraph/MyNode")
my_db = OgnMyNodeDatabase(my_node)

print(f"The current value of my float attribute is {my_db.inputs.myFloat}")
my_db.inputs.myFloat = 5.0

Important

For Python node implementations the registration process is performed automatically when you load your extension by looking for the ogn/ subdirectory of your Python import path. The generated Python class performs all of the underlying management tasks such as registering the ABI methods, providing forwarding for any methods you might have overridden, creating attribute accessors, and initializing attributes and metadata.

In addition, all of your nodes will be automatically deregistered when the extension is disabled.