Version 1/2 - Next ยป - Current version
coder, 04/09/2014 22:02


Deriving a New Node Class in C++

This page explains how to create your own node class. The number of libavg internals you need to know to do this isn't large. There are several base-class methods which need to be overridden in a derived class. They manage setup and destruction of the node as well as performing the actual rendering. In addition, you probably need to define a python API that your node exposes.

Any node that shows the contents of a bitmap should be derived from RasterNode - this is probably the most common case. Alternatively, nodes that display geometric forms can be derived from VectorNode or FilledVectorNode.

If you derive from RasterNode, rendering proceeds using an OGLSurface. The main task of the derived class is to associate one or more textures with the surface and fill the textures with data. Setting vertex positions, rendering the surface and applying FXNodes is mostly handled by the base class. CameraNode provides a simple sample that you can build upon.

Deriving from VectorNode or FilledVectorNode is different. The derived class is responsible for setting vertex positions, while the base class manages basic vector attributes like stroke width and color/texture of the node. As sample, use e.g. LineNode.

Lifecycle

Node construction proceeds in several phases. In the constructor, neither a connection to the root of the node tree nor an OpenGL context are available. At this point, Python arguments are transferred to C++ member variables and everything that doesn't need an OpenGL context or a connection to the tree root is setup.

Second, connect() is called. This signifies a connection to the root of the node tree and might cause the mediadir to change (making a media reload necessary). Also, at this point, the on-canvas coordinates of the node are known. Finally, connectDisplay() is called when the window has been opened and the node can access OpenGL functionality. This allows, e.g., upload of texture data to the GPU.

Conversely, when the node is removed from the tree (or when playback ends), disconnect() is called. Any data on the GPU (e.g. textures) should be discarded in disconnect(). RasterNode::disconnect() deletes the OpenGL objects managed by the base class, including the OGLSurface and any FX objects. If lbKill == true@, the node will never be reconnected. All data can be discarded.

In the destructor, delete anything left - as usual :).

Rendering

libavg renders in two passes. Each pass does a depth-first traversal through the node tree. In the first pass, preRender() is called for each node. During this pass, no OpenGL functions should be called. In RasterNode-derived classes, preRender() needs to make sure all textures are updated by calling scheduleTexUpload() with correct bitmap data if necessary. It should also call scheduleFXRender() if the texture has changed - this causes a possible FXNode to be applied. In addition, it always needs to call calcVertexArray() to generate the vertexes that will be rendered.

The second pass is responsible for sending all data to the GPU, and if there are multiple windows open, it will actually traverse the node tree multiple times, each time with a different current OpenGL context. In this pass, render() is called for each visible node. Usually, the derived class only needs to call RasterNode::blt32() (or blta8()) appropriately.

One other function that some nodes need to override is getMediaSize(). This returns the native size of the node, e.g. the bitmap size for an ImageNode.

VectorNodes simply need to override calcVertexes and submit appropriate vertexes in that function. A number of auxilliary functions are available in the base classes and in VertexData.

Python Interface

The python interface exported by a node is defined using boost::python. The ColorNode plugin contains some boost::python code. For node classes in the core library, the python interface is defined in the src/wrapper directory.