Core Architecture
Layering
Natron is built as a stack of layers with a strict dependency direction. The table lists them from the highest level (the executables) down to the bundled libraries; each layer may depend only on the layers below it.
Layer |
Directories |
Responsibility |
|---|---|---|
Applications |
|
The three |
GUI |
|
The Qt desktop application: node graph, OpenGL viewer, parameter panels, curve editor / dope sheet, roto & tracker tools. |
Engine |
|
The GUI-free core: nodes, knobs (parameters), the render pipeline, the cache, the built-in effects, color, images, the project model, and the Python API. |
OpenFX host |
|
The implementation of the OpenFX host contract that loads and drives plug-ins (most nodes are OFX plug-ins). |
Bundled libraries |
|
Third-party code built from source: |
The golden rule is that dependencies only point downward. Gui knows
about Engine; Engine must never include a Gui header. This is what
lets NatronRenderer link Engine without Gui and run on a headless
render farm. The mechanism that makes it possible while still letting the engine
“talk to” the GUI is the abstract-interface pattern in Design Techniques and Idioms.
The runtime object model
At run time Natron is a tree of a few long-lived objects. Understanding this ownership graph is the key to understanding the whole program:
AppManager(singleton, reached through theappPTRmacro)The one global object. It owns the process-wide state: the plug-in registry (all discovered OpenFX and built-in plug-ins), the OpenFX host (
OfxHost), the globalCache, the userSettings, theKnobFactory, and the list of open documents.GuiApplicationManageris the GUI subclass that additionally owns window/shortcut/icon state. You will seeappPTR->…calls throughout the code — that is this singleton.AppInstance(one per open project;QObject)A single “document”/session. It owns exactly one
Project. The GUI subclassGuiAppInstanceadditionally owns the main window (Gui). A singleAppManagercan hold severalAppInstanceobjects at once (for example when rendering multiple projects headless).Project(KnobHolder+NodeCollection)The root of the document. It is a
NodeCollection(a container of nodes) and it is aKnobHolder(it has parameters — format, frame range, color settings, …). Saving a project serializes theProjectand everything it contains.NodeCollectionA container of
Nodeobjects with the wiring between them.Projectis the top-level collection; eachNodeGroup(aGroupnode, and also each PyPlug) is a nested collection, which is how Natron supports sub-graphs. This composite structure means the graph is recursive: a node can itself contain a graph.Node(QObject)A vertex in the graph. A
Nodeholds graph-level state: its position and appearance, its input connections, its label and script name, its knobs-container, a pointer to its GUI (throughNodeGuiI), and — most importantly — a pointer to its effect instance (getEffectInstance()/setEffect()). TheNodeis the stable identity; the effect is the behavior.EffectInstance(NamedKnobHolder)The actual image-processing behavior of a node. This is where rendering happens. There are two families of effects:
OfxEffectInstance— wraps a loaded OpenFX plug-in. Most user- visible nodes are of this kind.Native
EffectInstancesubclasses — the built-in nodes implemented directly in C++:ViewerInstance,RotoPaint,TrackerNode,ReadNode/WriteNode,NodeGroup,Backdrop,Dot,PrecompNode,GroupInput/GroupOutput,OneViewNode,JoinViewsNode,DiskCacheNodeand a few no-ops.
OutputEffectInstanceis the base for effects that drive rendering (the Viewer and Writers);ViewerInstanceandNodeGroupboth derive from it.
So the chain from application to pixels is:
appPTR → AppInstance → Project (a NodeCollection) →
Node → EffectInstance → (OpenFX plug-in or built-in C++).
Why Node and EffectInstance are separate
This split is deliberate and worth internalizing. The Node is a durable
graph object with a stable identity, connections and undo history. The
EffectInstance is the possibly-replaceable behavior. Keeping them apart lets
Natron do things like reset or reload a plug-in, swap a Read node’s decoder when
the file type changes, or run several render clones of the same effect
concurrently, all without disturbing the graph topology the user built.
The OpenFX host at the center
Because most nodes are OpenFX plug-ins, a large part of the Engine exists to
be a good OpenFX host: to load plug-ins and read their descriptors, to present
Natron’s images to them as OFX clips (OfxClipInstance), to present Natron’s
knobs to them as OFX parameters (OfxParamInstance), to translate the OFX
render actions into Natron’s rendering pipeline (OfxImageEffectInstance,
OfxEffectInstance), and to route overlay drawing and interaction
(OfxOverlayInteract). This host layer is split between the reusable OpenFX
support code in HostSupport/libs/OpenFX and the Natron-specific glue in
Engine/Ofx*. See Natron as an OpenFX Host for detail.
The rendering pipeline in one paragraph
Rendering is pull-based. An output effect (the Viewer or a Writer) asks its
input for a region of interest of an image at a given time, view and
resolution (mip-map level / proxy). That call — EffectInstance::renderRoI()
— recurses upstream: each effect computes which region of its inputs it needs,
asks for them (again through renderRoI), then renders its own output tile by
tile, possibly on many threads. Results are stored in the Cache keyed by a
hash of everything that can affect them, so unchanged sub-results are reused on
the next frame or the next parameter tweak. The whole recursive render is
coordinated by scheduler threads and carries per-render state through
thread-local storage (ParallelRenderArgs). This is the single most complex
part of the code and has its own chapter, Rendering, Threading and Caching.