OmniGraph Nodes
***************

The OmniGraph node provides an encapsulated piece of functionality that is used in a connected graph structure to
form larger more complex calculations.

One of the core strengths of the way the nodes are defined is that individual node writers do not have to deal with
the complexity of the entire system, while still reaping the benefits of it. Support code is generated for the node
type definitions that simplify the code necessary for implementing the algorithm, while also providing support for
efficient manipulation of data through the use of Fabric both on the CPU and on the GPU.

The user writes the description and code that define the operation of the node type and OmniGraph provides the
interfaces that incorporate it into the Omniverse runtime.

Node Type Implementations
=========================

The node type definition implements the functionality that will be used by the node using two key pieces
1. The *authoring* definition, which comes in the form of a JSON format file with the suffix *.ogn*
2. The *runtime* algorithm, which can be implemented in one of several languages.

The actual language of implementation is not important to OmniGraph and can be chosen based on the needs and abilities
of the node writer.

You can learn about the authoring description in the .ogn files by perusing the
:ref:`OGN Node Writer Guide<ogn_user_guide>`, with full details in the :ref:`OGN Reference Guide<ogn_reference_guide>`,
or you can follow along with some of the tutorials or node definitions seen below.

A common component of all node type implementations is the presence of a generated structure known as the node type
database. The database provides a simplified wrapper around the underlying OmniGraph functionality that hides all of the
required boilerplate code that makes the nodes operate so efficiently.

Python Implementation
---------------------
The easiest type of node to write is a Python node. You only need implement a class with a static *compute(db)* method
whose parameter is the generated database. The database provides access to the input values your node will use for its
computation and the output values you generate as a result of that computation.

+-------------------------------------------+
| :ref:`Example Below<ogn_example_node_py>` |
+-------------------------------------------+

C++ Implementation
------------------
While the Python nodes are easier to write they are not particularly efficient, and for some types of nodes
performance is critical. These node types can be implemented in C++. There is slightly more code to write, but you
gain the performance benefits of compiled code.

+--------------------------------------------+
| :ref:`Example Below<ogn_example_node_cpp>` |
+--------------------------------------------+

C++/CUDA Implementation
-----------------------
Sometimes you want to harness the power of the GPU to do some really complex or data-heavy computations. If you know
how to program in CUDA, NVIDIA's general purpose GPU computing language, then you can insert CUDA code into your
node's `compute()` method to move the computation over to the GPU.

+-------------------------------------------+
| :ref:`Example Below<ogn_example_node_cu>` |
+-------------------------------------------+

Warp Implementation
-------------------
If you don't know how to write CUDA code but are familiar with Python then you can use the Warp package to accelerate
your computations on the GPU as well. NVIDIA Warp is a Python framework for writing high-performance simulation and
graphics code in Omniverse, and in particular OmniGraph.

See the `Warp extension documentation <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_warp.html>`_
for details on how to write the implementation code for Warp nodes.

+---------------------------------------------+
| :ref:`Example Below<ogn_example_node_warp>` |
+---------------------------------------------+

Slang Implementation
--------------------
`Slang <https://shader-slang.com/slang/user-guide/index.html>`_ is a language developed for efficient implementation
of shaders, based on years of collaboration between researchers at NVIDIA, Carnegie Mellon University, and Stanford.

To bring the benefits of Slang into Omnigraph an extension was written that allows you to create OmniGraph node
implementations in the Slang language. To gain access to it you must enable the *omni.slangnode* extension, either
through the extension window or via the
`extension manager API <https://docs.omniverse.nvidia.com/py/kit/docs/api/core/omni.ext.html#omni.ext.ExtensionManager>`_

The Slang node type is slightly different from the others in that you do not define the authoring definition through
a *.ogn* file. There is a single *.ogn* file shared by all Slang node types and you write the implementation of the
node type's algorithm in the *inputs:code* attribute of an instantiation of that node type. You dynamically add
attributes to the node to define the authoring interface and then you can access the attribute data using some
generated Slang conventions.

Moreover, the Slang language has the capability of targeting either CPU or GPU when compiled, so once the graph supports
it the node will be able to run on either CPU or GPU, depending on which is more efficient on the available resources.

See the `Slang extension documentation <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph/slang-nodes/nodes/slang.html>`_
for details on how to write Slang nodes.

+----------------------------------------------+
| :ref:`Example Below<ogn_example_node_slang>` |
+----------------------------------------------+


AutoNode Implementation
-----------------------
AutoNode is a variation of the Python implementation that makes use of the Python AST to combine both the authoring
description and the runtime implementation into a single Python class or function. While not yet fully functional,
it provides a rapid method of implementing simple node types with minimal overhead.

+---------------------------------------------+
| :ref:`Example Below<ogn_example_node_auto>` |
+---------------------------------------------+


Sample Node With Different Implementations
==========================================
To illustrate the difference in implementations a simple node type definition will be shown that takes in two arrays
of points and outputs a weighted blend of them.

.. note::

   The implementations are not necessarily the most efficient, nor the most appropriate for the type of computation
   being performed. They are merely meant to be illustrative of the implementation options.

This *.ogn* file will be used by all but the Slang and AutoNode implementations. The first highlighted line is only
present in the Python and Warp implementations, as they are both Python-based. The remaining highlighted lines
are used only for Warp and CUDA implementations, being used to indicate that the memory for the attributes should be
retrieved from the GPU rather than the CPU.

.. code-block:: json
   :emphasize-lines: 4,8,9,15

   {
       "AddWeightedPoints": {
           "version": 1,
           "language": "python",
           "description": [
            "Add two sets of points together, using an alpha value to determine how much of each point set to",
            "include in the result. The weighting is alpha*firstSet + (1 - alpha)*secondSet.",
            "memoryType": "cuda",
            "cudaPointers": "cpu"
         ],
         "inputs": {
            "alpha": {
               "type": "double",
               "description": "Relative weights to give to each of the two point sets"
               "memoryType": "cpu"
            },
            "firstSet": {
               "type": "pointd[3][]",
               "description": "First set of points to blend together"
            },
            "secondSet": {
               "type": "pointd[3][]",
               "description": "Second set of points to blend together"
            },
         "outputs": {
            "result": {
               "type": "pointd[3][]",
               "description": "Weighted sum of the two sets of input points"
            }
         }
      }
   }


.. _ogn_example_node_py:

Python Version
--------------

.. code-block:: python

   class OgnAddWeightedPoints:
      @staticmethod
      def compute(db) -> bool:
         db.outputs.result_size = len(db.inputs.firstSet)
         db.outputs.result = db.inputs.firstSet * db.inputs.alpha + db.inputs.secondSet * (1.0 - db.inputs.alpha)
         return True

For illustration purposes here is the same code but with proper handling of unexpected input values, which is a good
practice no matter what the implementation language.

.. code-block:: python

   class OgnAddWeightedPoints:
      @staticmethod
      def compute(db) -> bool:
         first_set = db.inputs.firstSet
         second_set = db.inputs.secondSet

         if len(first_set) != len(second_set):
            db.log_error(f"Cannot blend two unequally sized point sets - {len(first_set)} and {len(second_set)}")
            return False

         if not (0.0 <= db.inputs.alpha <= 1.0):
            db.log_error(f"Alpha blend must be in the range [0.0, 1.0], not computing with {db.inputs.alpha})
            return False

         db.outputs.result_size = len(first_set)
         db.outputs.result = first_set * db.inputs.alpha + second_set * (1.0 - db.inputs.alpha)

         return True

To see further examples of the Python implementation code you can peruse the
:ref:`samples used by the user guide<ogn_code_samples_py>` or the :ref:`user guide itself<ogn_user_guide>` itself.


.. _ogn_example_node_cpp:

C++ Version
-----------

.. code-block:: cpp

   #include <OgnAddWeightedPointsDatabase.h>
   #include <algorithm>
   class OgnAddWeightedPoints
   {
   public:
      static bool compute(OgnAddWeightedPointsDatabase& db)
      {
         auto const& firstSet = db.inputs.firstSet();
         auto const& secondSet = db.inputs.secondSet();
         auto const& alpha = db.inputs.alpha();
         const& result = db.outputs.result();

         // Output arrays always have their size set before use, for efficiency
         result.resize(firstSet.size());

         std::transform(firstSet.begin(), firstSet.end(), secondSet.begin(), result.begin(),
                        [alpha](GfVec3d const& firstValue, GfVec3d const& secondValue) -> GfVec3d
                        { return firstValue * alpha + (1.0 - alpha) * secondValue; });
         return true;
      }
   };
   REGISTER_OGN_NODE()

To see further examples of the C++ implementation code you can peruse the
:ref:`samples used by the user guide<ogn_code_samples_py>` or the :ref:`user guide itself<ogn_user_guide>` itself.


.. _ogn_example_node_cu:

C++/Cuda Version
----------------
Nodes using CUDA are split into two parts - the `.cpp` implementation that sets up the CUDA execution, and the `.cu`
file containing the actual GPU functions.

.. code-block:: cpp

   #include <OgnAddWeightedPointsDatabase.h>

   // The generated code gives helper types matching the attribute names to make passing data more intuitive.
   // This declares the CUDA function that applies the blend.
   extern "C" void applyBlendGPU(inputs::points_t firstSet,
                                 inputs::points_t secondSet,
                                 inputs::alpha_t alpha,
                                 outputs::points_t outputPoints,
                                 size_t numberOfPoints);

   class OgnAddWeightedPoints
   {
   public:
      static bool compute(OgnAddWeightedPointsDatabase& db)
      {
         // Output arrays always have their size set before use, for efficiency
         size_t numberOfPoints = db.inputs.firstSet.size();
         db.outputs.result.resize(numberOfPoints);

         if (numberOfPoints > 0)
         {
            auto const& alpha = db.inputs.alpha();
            applyBlendGPU(db.inputs.firstSet(), db.inputs.secondSet(), alpha, db.outputs.result(), numberOfPoints);
         }
         return true;
      }
   };

   REGISTER_OGN_NODE()

The corresponding `.cu` implementation is:

.. code-block:: cpp

   // Although it contains unsafe CUDA constructs the generated code has guards to let this inclusion work here too
   #include <OgnTutorialCudaDataDatabase.h>

   // CUDA compute implementation code will usually have two methods;
   //     fooGPU() - Host function to act as an intermediary between CPU and GPU code
   //     fooCUDA() - CUDA implementation of the actual node algorithm.

   __global__ void applyBlendCUDA(
      inputs::points_t firstSet,
      inputs::points_t secondSet,
      inputs::alpha_t alpha,
      outputs::points_t result,
      size_t numberOfPoints
   )
   {
      int ptIdx = blockIdx.x * blockDim.x + threadIdx.x;
      if (numberOfPoints <= ptIdx) return;
      (*result)[ptIdx].x = (*firstSet)[ptIdx].x * alpha + (*secondSet)[ptIdx].x * (1.0 - alpha);
      (*result)[ptIdx].y = (*firstSet)[ptIdx].y * alpha + (*secondSet)[ptIdx].y * (1.0 - alpha);
      (*result)[ptIdx].z = (*firstSet)[ptIdx].z * alpha + (*secondSet)[ptIdx].z * (1.0 - alpha);
   }

   extern "C" void applyBlendGPU(
      inputs::points_t firstSet,
      inputs::points_t secondSet,
      outputs::points_t result,
      inputs::alpha_t& alpha,
      size_t numberOfPoints
   )
   {
      const int numberOfThreads = 256;
      const int numberOfBlocks = (numberOfPoints + numberOfThreads - 1) / numberOfThreads;
      applyBlendCUDA<<<numberOfBlocks, numberOfThreads>>>(firstSet, secondSet, result, alpha, numberOfPoints);
   }

To see further examples of how CUDA compute can be used see the :ref:`CUDA tutorial node <ogn_tutorial_cudaData>`.



.. _ogn_example_node_warp:

Warp Version
------------
A Warp node is written in exactly the same way as a Python node, except for its `compute()` it will make use
of the Warp cross-compiler to convert the Python into highly performant CUDA code. You'll notice that there is more
boilerplate code to write but it is pretty straightforward, mostly wrapping arrays into common types, and the
performance is the result.

.. code-block:: python

   import numpy as np
   import warp as wp

   @wp.kernel
   def deform(first_set: wp.array(dtype=wp.vec3),
              second_set: wp.array(dtype=wp.vec3),
              points_out: wp.array(dtype=wp.vec3),
              alpha: float):
      tid = wp.tid()
      points_out[tid] = first_set[tid] * alpha + second_set[tid] * (1.0 - alpha)

   class OgnAddWeightedPoints:
      @staticmethod
      def compute(db) -> bool:
         if not db.inputs.points:
            return True

         with wp.ScopedDevice("cuda:0"):
            # Get the inputs
            first_set = wp.array(db.inputs.points, dtype=wp.vec3)
            second_set = wp.array(db.inputs.points, dtype=wp.vec3)
            points_out = wp.zeros_like(first_set)

            # Do the work
            wp.launch(kernel=deform, dim=len(first_set), inputs=[first_set, second_set, points_out, db.inputs.alpha])

            # Set the result
            db.outputs.points = points_out.numpy()

To see further examples of how Warp can be used see the
`Warp extension documentation. <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_warp.html>`_

.. _ogn_example_node_slang:

Slang Version
-------------

Unlike the previous types, Slang implementations are done directly within the application. Once you create a blank
Slang node you open up the property panel and use the **Add+** button to add the attributes. This is what the
attribute definition window will look like for one of the inputs.

.. image:: images/SlangAddInput.png

Add the three inputs and one input in this way. In the end the property panel should reflect the addition of the
attributes the node will use.

.. image:: images/SlangFinalProperties.png

.. note::

   For now the array index is passed through to the Slang compute function as the `instanceId` as the function
   will be called in parallel for each element in the array. The number of elements is reflected in the attribute
   `inputs:instanceCount` as seen in the property panel. In an OmniGraph you can connect either of the input point
   arrays through an `ArrayGetSize<GENERATED - Documentation _ognomni.graph.nodes.ArrayGetSize>` node to that
   attribute to correctly size the output array.

.. code-block:: cpp

   void compute(uint instanceId)
   {
      double alpha = inputs_alpha_get();
      double[3] v1 = inputs_firstSet_get(instanceId);
      double[3] v2 = inputs_secondSet_get(instanceId);
      double[3] result = {
         v1[0] * alpha + v2[0] * (1.0 - alpha),
         v1[1] * alpha + v2[1] * (1.0 - alpha),
         v1[2] * alpha + v2[2] * (1.0 - alpha)
      };
      outputs_result_set(instanceId, result);
   }

To see further examples of how Slang can be used see the
`Slang extension documentation <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_omnigraph/slang-nodes/nodes/slang.html>`_

.. _ogn_example_node_auto:

AutoNode Version
----------------
For this version, as with the Slang version, there is no *.ogn* file required as the type information is gleaned from
the node type definition. For this example only a single point will be used for inputs and outputs as the arrays are
not yet fully supported. You just execute this code in the script editor or through a file and it will define a node
type `my.local.extension.blend_point` for you to use as you would any other node.

.. code-block:: python

   @og.AutoFunc(pure=True, module_name="my.local.extension")
   def blend_point(firstSet: og.Double3, secondSet: og.Double3, alpha: og.Double) -> og.Double3:
      return (
         firstSet[0] * alpha + secondSet[0] * (1.0 - alpha),
         firstSet[1] * alpha + secondSet[1] * (1.0 - alpha),
         firstSet[2] * alpha + secondSet[2] * (1.0 - alpha),
      )

To see further examples of how AutoNode can be used see the :ref:`AutoNode documentation<autonode_ref>`.


Node Writing Tutorials
======================

We have a number of tutorials to help you write nodes in OmniGraph.
This set of tutorials will walk you through the node writing process
by using examples that build on each other, from the simplest to the most complex nodes.

.. toctree::
   :maxdepth: 2
   :glob:

   :ref:`Node Writing Tutorials <ogn_tutorial_nodes>`


OmniGraph Nodes In Kit
======================

This is an amalgamated collection of documentation for all OmniGraph nodes that have been defined in Kit proper.
Other nodes may exist in other extensions and will be in the documentation for those extensions.

.. toctree::
   :maxdepth: 1
   :glob:


   ../../../../_build/ogn/docs/*

