Note: This is an archived version of the Blender Developer Wiki (archived 2024). The current developer documentation is available on developer.blender.org/docs.

User:KevinDietrich/Cycles Node Definition Language

Cycles Node Definition Language

This is a sketch of a language I designed to define Cycles Nodes.

Rationale

The goals were:

  • to cleanup usage of macros, and liberate ourselves from their shortcomings
  • to have a system to properly document each socket, with categories and tooltips/documentation for user interfaces
  • to more easily add data to SocketType (e.g. min/max information, doing so would require modifying a lot of macros currently)
  • to automate data validation e.g. the values of some socket may depend on the values of another one, although we could do this manually, automating this could lead to better code, fewer bugs due to typos
  • to support "socket groups", similar to the previous points, we could associate multiple sockets into groups to ensure that they are updated at the same with sensible values
  • to properly separate public and private APIs, where the public part is the one define from the metadata, and the private part is whatever Cycles needs internally to work on Nodes during Scene updates
  • to improve static initialization of reflection data, and allow for easy access to every NodeType's reflection data without having to include every header files, or creating a bogus Session (which is useful for generating UIs in external software using this metadata)

The idea was to define all of the metadata (sockets, documentation, etc.) of the various Nodes in files and generate C++ code from them at compile time. The generated C++ code would be a single base class for each Node containing just the socket types, from this base class Cycles would derive its own final class with any other members that it desires.

For example, the code for the Mesh Node would have been:

// This class is generated from the language
class MeshNode : public GeometryNode {
    // sockets go here
    
public:
    // sockets accessors go here
};

// Then defined by Cycles
class Mesh : public MeshNode {
    // members only used internally by Cycles go here

public:
    // internal methods go here
};

Only the MeshNode class would be exposed to the Cycles public API. Cycles would be allocating a Mesh, but returning a MeshNode whenever a new Mesh Node is requested, and similarly for any other Node type.

The main issue with this approach, is that base classes, like Geometry, are lost, or rather, it cat get tricky to treat Meshes, Hairs, and Volumes, as a simple pointer to some Geometry. Multiple inheritance could be considered though.

Example Code

// Possible data types for a Cycles socket
// The | is used to indicate that is also possible for a type to be used in an array
// so, int|array means we can have a socket of type int, and one of type array<int>.
SocketDataType {
    int|array
    uint
    bool|array
    Node|array
    float|array
    float2|array
    float3|array
    Transform|array
}

// Main structure to hold static data representing a socket.
SocketType {
    type SocketDataType
    name ustring
    struct_offset int
    default_value *void
    enum_values *NodeEnum
    node_type *NodeType
    flags int
    ui_name ustring
    modified_flag_bit SocketModifiedFlags
}

// Define the interface of the SocketType class
interface SocketType {
    size() -> size_t
    is_array() -> bool

    size(SocketType) -> size_t @static
    max_size() -> size_t @static
    type_name(SocketDataType) -> ustring @static
    zero_default_value() -> *void @static
    is_float3(SocketDataType) -> bool @static
}

// Base class for Nodes
Node {
    name ustring
    owner *NodeOwner
    modified_flags SocketModifiedFlags
}

// Define the interface of the Node class
interface Node {
    get(SocketType) -> SocketType.type

    set(SocketType, Socket.type)

    :doc:
        Return whether any of the socket on the Node was modified.
    :enddoc:
    is_modified() -> bool

    is_socket_modified(SocketType) -> bool
}

// Defines the Mesh Node
Mesh : Node {
    :socket triangles:
        type = float3*array
        min = 0
        max = 1
        ui = "Triangles"
    :endsocket:

    :socket num_ngons:
        min = 0
        max = subd_corners.size
        ui = "Number of N-Gons"
    :endsocket:

    :socket used_shaders:
        type = Shader*array
        ui = "Used Shaders"
    :endsocket:

    ...
}

// Defines the Particle Node (note that this is currently not a Node).
Particle : Node {
    :socket position:
        type = float3
        ui = "Position"
    :endsocket:

    ...
}

// Defines the ParticleSystem Node.
ParticleSystem : Node {
    :socket particles:
        type = Particle*array
        ui = "Particles"
    :endsocket:

    ...
}