From BlenderWiki

Jump to: navigation, search

Writing new compositor node

In this article I will describe how to write a new node for tile-based compositor.

This process consists of couple of steps:

  • Create DNA structures
  • Register new node
  • Define RNA accessors
  • Write tile-based operation

Create DNA structure

This step didn't change since old compositor system. All the structures are declared in file DNA_node_types.h. There's nothing really specific here, new structure should just meet general for all DNA alignment rules.

Register new node

Couple of files need to be changed to register new node.

First of all in the file BKE_node.h new node type need to be added. All types for compositor nodes should have CMP_NODE_ prefix and values for types should be contiguous with existing compositor node types.

Next step would be to go to node.c and find out function called registerCompositNodes. In this function all the compositor nodes register functions are called. So register function call of new node should be placed here (call is adding at this step, register function itself is defined in next step).

Register function calls better be grouped by node category for easier search in the future.

Then in the file NOD_composite.h declaration of new node register function is to be added. Name of register function should follow existing convention and have register_node_type prefix.

Next step would be to add an implementation of register function. All the register functions are implemented in separate files in nodes folder. There are quite enough of small nodes which could be used as a template here, writing register function is really straight forward.

Define RNA accessors

First file NOD_static_types.h is to be modified. Each line in this file defines type of node (shader/texture/compositor), defines type code, function which will register node's RNA properties, enum code for Python RNA, suffix of RNA structure and interface name of the structure.

New node is to be added at the end of CompositorNode block. If there's no properties in the node then instead of function name 0 is to be used (in this case adding DNA also not needed).

Next in the file NOD_static_types.h RNA properties register function is to be added. There're lots of examples in this file already which are easy to follow.

Write tile-based operation

Actually, both tile-based operation and tile-based node is to be written. Let's start with writing new tile-based node.

Writing tile-based-node

Tile-based nodes are defined in folder nodes and basically tile-based node defines which operations in which order need to be processed to calculate node output.

Two files are to be added in this folder -- on is for node header, another one is for node implementation. Tile-based node is a C++ sub-class of class Node and should override at least two methods:

  • Constructor, which initialized node (which is actually optional if nothing special need to be initialized)
  • convertToOperations(), which will convert blender's node to a graph of tile-based operations.

Method convertToOperations need to create all the operations, which need to compute node output and defines connections between node input sockets -> operation's input sockets and connects oprtsion's output sockets to node output sockets. Additionally, operations could be connected to each other, if more than one operation is needed to compute output value.

When writing convertToOperations method, usually getting node inputs/outputs goes first.

To get node's input node method this->getInputSocket(<socket index>) is to be used. Example:

InputSocket *color1Socket = this->getInputSocket(0);
InputSocket *color2Socket = this->getInputSocket(1);

To get node's output node method this->getOutputSocket(<socket index>) is to be used. Example:

OutputSocket *outputSocket = this->getOutputSocket(0);

It is likely that access to blender's node DNA structure would be needed. To get this node such call exists:

bNode *editorNode = this->getbNode();

After this accessing editorNode->custom1, editorNode->custom2, editorNode->storage could be used.

Next step would be to create operations which will compute node output. For simple nodes it could be a single operation, for more complex nodes more operations could be needed.

Node with a single operation

Just to show how connections between sockets defines, let's compute a sum of two input color sockets. MathAddOperation would be needed:

MathAddOperation *operation = new MathAddOperation();

This is a simple operation which doesn't require any settings and need to be just connected to sockets. To connect node input socket to operation input socket, input socket's relinkConnections method is to be used. First argument defines which operation's input socket connection need to be relinked to, second argument defines index of socket in blender's node (used for autoconnection) and last argument is an execution system (graph). This code

color1Socket->relinkConnections(operation->getInputSocket(0), 0, graph);
color2Socket->relinkConnections(operation->getInputSocket(1), 1, graph);

will connect operation's input sockets to node's inputs.

The same method relinkConnections is to be used to connect operation output socket to node output socket, but in this case it's just enough to say which operation's output socket need to be connected to particular node output:


The last step here would be to add new operation to the graph:


Node with multiple operations

If multiple operations are needed to compute node output, there would be need to connect operations to each other.

Say, operation1's output socket need to be connected to operation2's input socket:

addLink(graph, operation1->getOutputSocket(), operation2->getInputSocket(0 /* index of input socket to add connection to */));

Connections between node's sockets and operation sockets is done in the same way as it was showed in preview section.

Writing tile-based operation

All the operations are placed in operations folder. Both header and implementation files would be needed here. Operation is a sub-class of NodeOperation class and the following methods need to be overwritten:

  • Constructor
  • initExecution
  • deinitExecution
  • executePixel

Main purpose of constructor here is to define input/output sockets, also initialize some possible needed parameters of the operation.

To add input socket, call this->addInputSocket(type) is to be used, where type could be one of COM_DT_VALUE, COM_DO_VECTOR or COM_DT_COLOR. To add output socket, call this->addOutputSocket(type). With current design it could be multiple input sockets but only a single output socket.

initExecution is mainly used to get socket readers, which will then be used in executePixel method. To get input socket reader use this->getInputSocketReader(socket_index), it'll return a pointer to SocketReader structure. Usually readers are stored in private properties of the operation class.

For complex nodes, more operations could be needed in execution initialization.

deinitExecution resets/frees all the data used during execution.

Operations depending on single pixel

If operation output depends on a single input pixel, executePixel would be really simple. In this case 4 parameters are passed to the executePixel method:

  • float output[4] where output color/value is to be stored
  • float x,y are coordinates of pixel to compute value for
  • PixelSampler sampler is sampler to be used to sample input colors

To read value from input socket, read() method of SocketReader (which was stored in initExecution) is to be used. This method expects the following arguments:

  • float output[4] where read value would be stored
  • float x, y are coordinates to read value from
  • PixelSampler sampler, which defines which sample algorithm will be used. In most cases it's being transferred from executePixel's argument, but could be forced to COM_PS_NEAREST, COM_PS_BILINEAR or COM_PS_BICUBIC.

After all input data needed for given pixel is read, executePixel need to set it's output value/vector.

This is basically it.

Operations depending on multiple pixel

This is unfinished section and I would like tile-based maintainers help finishing it.

In this case operation need to be marked as complex in it's constructor by calling this->setComplex(true).

it should also override determineDependingAreaOfInterest method. This method get's an input rectangle area which is being expected to be computed by this operation and this method is expected to put to output area which is needed by this operation to compute input area.

There're couple of examples in operations folder of this function.

For complex operations executePixel function is also changes a bit, there should be enough of examples in operations.

Gluing tile-node to compositor system

There should be added a simple conversion code to COM_Converter.cpp, namely to function called Convertor::convert. This function creates tile-based node for given blender's node type code. It's pretty much straightforward logic here and there should be no extra information needed here to write the conversion.