Chapter 7. Defining custom plugs

In a shader code, new plug may be defined by a magic comment:

/* PLUG: name (param1, param2, ...) */

This defines a point where calls to user functions declared as PLUG_name will be inserted. They will be called with given parameters.

Many effects may use the same PLUG_name. Even within a single effect, the same PLUG_name may be used many times. All the PLUG_name functions will be uniquely renamed to not collide with each other.

The calls will be added in the order they are specified on the effects list. More precisely, the most local effects (at light sources and textures) are called first, then the effects at shape appearance, and finally the effects inside the grouping nodes. Although, preferably, for most effects this order will not matter.

For the effects on lights and textures, we first try to find the plug specific to the given light or texture node. This means that using the PLUG_light_scale inside the X3DLightNode.effects changes only the given light node contribution. Contrast this with using the same PLUG_light_scale inside Appearance.effects, in which case the intensity of all the light sources on the given shape can be changed.

A plug is often defined to allow modifying some parameter repeatedly (like adding or modulating the fragment color), so one or more of the parameters are often allowed to be handled as inout values.

The same plug name may be defined many times in the source shader. That is, the magic comment /* PLUG: name ... */ may be repeated a couple of times, with the same name. This means that the final shader may call every matching PLUG_name function many times. This is useful when the algorithm is naturally expressed as a loop, but it had to be unrolled for shader source (for example, to slightly tweak some loop iterations).

Currently all the plugs must be procedures, that is their result type must be declared as void. We have been considering a possibility of functions, where part of the calculation may be replaced by a call to a plugged function. While not difficult to implement, this idea seems unnecessary after many tests. Procedural plugs are easier to declare, as the call to the plug may be simply inserted, while in case of function it will have to replace some previous code. This also means that using a procedural plug never replaces or removes some existing code, which is a very nice concept to keep. We want the effects to cooperate with each other, not to hijack from each other some parts of the functionality.

New plugs can be defined inside the Effect nodes, as well as inside the complete shaders (like standard ComposedShader nodes). In the first case, the plugs are only available for the following effects of the same shape.

The advantage of using magic comments to define plugs is that they can be ignored and a shader source remains valid. This means that ComposedShader nodes can define custom plugs and still work (although with no extra effects) even in X3D browsers that do not support our extensions.

7.1. Forward declarations

Suppose we have an effect X that defines a new plug, by including a magic /* PLUG: ... */ comment. When this plug is used by another effect Y, then an appropriate function call is automatically inserted into the generated shader. In the middle of the source code of effect X, a function defined in effect Y has to be called. This is the simplest implementation of our plugs.

Additionally, a forward or external declaration of the called function may need to be inserted into the effect X. That is because Y may be in a separate compilation unit (in case of GLSL), or just defined lower in the code. In simple cases, such forward or external declarations can be inserted right at the beginning of effect X code.

Some shading language directives are required to be placed before all normal declarations. For example, in case of the OpenGL shading language, the #version as well as some #extension directives must occur at the beginning of the shader code. To handle such cases, another magic comment /* PLUG-DECLARATIONS */ is available. If present, it signifies a place where forward or external declarations should be inserted.