Writing plugins for BuGLe

Introduction

Eventually, this file will serve as a guide for third party plugin authors. At present, the API is still too unstable for this to be practical, and the ABI will vary between machines with different OpenGL headers. To repeat: you cannot just distribute binary filter-sets and expect them to work. If you decide to write a plugin anyway, consider submitting it to the program author for inclusion, so that he can maintain it across API changes.

Filters are arranged in a four-level hierarchy:

  1. Filter libraries, which are a loose collection of filter-sets. These allow filters to be grouped together to avoid having hundreds of files. It also makes shared access to data easier.
  2. Filter-sets, which are tightly coupled groups of filters. Each filter-set may have state, may depend upon other filter-sets and may only be loaded or unloaded as a unit.
  3. Filters, which are used to control sequencing. For example, some filter-sets need to have some code run before calls are executed and other code after. These pieces of code would be placed in separate filters. The plugin author may list ordering dependencies between filters.
  4. Callbacks, which are the actual functions that act on calls. A filter may register multiple callbacks, each of which acts on some subset of the possible calls.

Loading vs. activation

In order to allow modifiers to be turned on or off on the fly, there is a distinction between loading and activation. All filter-sets listed in the chain are loaded, but should alter the behaviour of the program only when active. Filter-sets are loaded only at startup and unloaded at program termination, but can be activated or deactivated arbitrarily. Most callbacks are only called when the filter-set is active, but it is possible to register callbacks that are called even when the filter-set is inactive. This allows the filter-set to monitor state so that it can do the right thing during activation, or to execute cleanup code after deactivation.

Filter-related structures and typedefs

filter_set
An opaque structure representing a filter-set.
filter
An opaque structure representing a filter.
filter_set_variable_info
A user-supplied structure that describes one filter-set variable that can be set from the configuration file. It has the following fields:
name
The name to match in the configuration file.
help
The help string to show when help is requested. This may be NULL to leave the variable undocumented.
type
The type of variable. It must be one of the following:
  • FILTER_SET_VARIABLE_STRING (a general string)
  • FILTER_SET_VARIABLE_INT (a general integer)
  • FILTER_SET_VARIABLE_UINT (a non-negative integer)
  • FILTER_SET_VARIABLE_POSITIVE_INT (a positive integer)
  • FILTER_SET_VARIABLE_BOOL (a boolean value)
  • FILTER_SET_VARIABLE_KEY (a xevent_key structure, which holds an X KeySym and a shift mask)
  • FILTER_SET_VARIABLE_CUSTOM (any type, with a callback to assign the value)
value
A void pointer to a location to update. For the integer types, the target must be of type long and for string variables it must be of type char *. In the latter case, the value written in is a copy of the string, and you are responsible for freeing it. If the old value was non-NULL, it will be freed first (so be sure to use a static initialisation to NULL; if you want a non-NULL default then you must allocate the memory). It is also legal for value to be NULL, in which case no assignment is done.
callback
A callback function that runs before the assignment. It has the signature
bool callback(const filter_set_variable_info *info, const char *text, void *value);
The text is the literal string value from the configuration file, while value is interpreted in the same way as the value field in the structure (note: the parameter will be meaningful even if the structure provides a value of NULL). The value may be overridden by the callback. The return value should be true unless the value is determined to be illegal, in which case it should print a message to the log and return false. The callback may also be NULL (and usually will be).
filter_set_variable_type
An enum of the possible values for the type field above.
filter_set_info
A structure used to register a filter-set. It has the following fields:
name
The name of the filter-set, to be used in the configuration file.
load
A callback function used to initialise the filter-set. It has the following signature:
bool filter_set_loader(filter_set *);
It should return true on success and false on failure. See below for more information on initialisation.
unload
A shutdown function for the filter-set, with signature
void filter_set_unload(filter_set *);
activate
An optional activation function, with signature
void filter_set_activator(filter_set *);
deactivate
An optional deactivator function, with signature
void filter_set_deactivator(filter_set *);
variables
A pointer to an array of filter_set_variable_info structures, terminated by a null structure {NULL, NULL, 0, NULL, NULL}. This describes the user variables that are accepted by the filter-set. If there are no variables, this field may be NULL.
call_state_space
An integer number of bytes of storage to piggy-back on to the call structure. This will probably go away soon and it is recommended that it is set to 0 for now.
help
The help string to show when help is requested. This may be left as NULL to leave the filter-set undocumented. This is recommended for internal or helper filter-sets.

Other structures and typedefs

Bugle makes a sharp distinction between functions and groups. A function is what you think it is. A group is a set of functions with identical parameters and semantics. In OpenGL, groups arise from the fact that functions are promoted between extensions or from extensions to the core; for example glActiveTexture and glActiveTextureARB are equivalent functions and belong to the same group.

Each GL function is assigned a number in the range [0, NUMBER_OF_FUNCTIONS). These numbers are conventionally of type budgie_function, which is some signed integral type. There is also a special value NULL_FUNCTION, which is simply -1. For each function glSomeFunction, there is a typedef FUNC_glSomeFunction which is the number assigned to the function.

Similar, groups are numbered from 0 to NUMBER_OF_GROUPS and given defines GROUP_glSomeFunction. Groups do not have explicit names, but are referenced by any of the functions in the group.

BuGLe tries to support systems that only have header files and runtime support for OpenGL 1.1 (so that a Win32 port will be possible one day). Thus for any functions introduces after 1.1, you should favour the GROUP_ definition with the oldest name, which is generally an extension function. You should also protect any such code with a test for the GL extension define e.g. GL_ARB_multitexture.

Library initialisation

Each filter library must define an initialisation function called bugle_initialise_filter_library which takes no parameters and no return. This function registers all the filter-sets in the library, as well as their dependencies.

Filter-sets are registered by passing a pointer to a filter_set_info structure (see above) to bugle_register_filter_set. Some of the fields in the structure are used later in-place rather than copied, so the structure (including the variables array) must have global lifetime.

Dependencies are registered by calling

bugle_register_filter_set_depends("set1", "set2")

to indicate that set1 requires set2 to be present for operation.

Filter-set loading

Apart from initialising internal structures, a filter-set loader registers filters and callbacks. A filter is created by calling bugle_register_filter(handle, "filtername") This function returns a filter * which should be saved for later use.

There are two ways to register a callback:

bugle_register_filter_catches(filter *filter, budgie_function function, bool inactive, filter_callback callback)
Registers a callback function that will apply to a single GL function (or several if called several times). The function should be one of the CFUNC_* defines; other names for the same function are also trapped. If inactive is true, the callback will be called even if the filter-set is inactive (see below for an explanation of active and inactive filter-sets).
bugle_register_filter_catches_all(filter *filter, bool inactive, filter_callback callback)
Registers a callback function that will apply to all GL functions.

In addition, you should list sequencing requirements between filters. To specify that filter1 must run after filter2 if both are present, call bugle_register_filter_depends("filter1", "filter2"). An important filter for setting dependencies is the built-in invoke filter, which actually executes GL functions.

Writing callbacks

A callback has the following signature:

bool callback(function_call *call, const callback_data *data)

The callback should generally return true. Returning false aborts any further processing of the call (including execution if the invoke filter has not yet run). This can be useful if you want to suppress a call, but if possible it is better to allow execution and undo the effects afterwards so that later filters get a chance to run.

The data element will likely change or go away in the near future, so it will not be documented here.

Callbacks that handle multiple GL functions will need to know which GL function was called; the function is found in call->generic.id, but more useful is the group found in call->generic.group. One can also get access to the arguments: if the call is known to be glSomeFunction, then *call->typed.glSomeFunction.argi is the ith argument, and *call->typed.glSomeFunction.retn is the return value. If the function is not known at compile time, then the arguments can be accessed via the void pointers call->generic.args[i] and call->generic.retn. These values can also be modified to change the arguments used or the value returned to the application, respectively.

Making GL calls

There are some issues to be aware of if you want your callbacks to themselves make calls to GL/GLX. Firstly, if you make the call in the obvious way, it will be intercepted in the same way as all other GL/GLX calls. There is protection against this, so everything will continue to work, but there will be a loss of performance. Instead, you should prefix the name of the function with CALL_. e.g.

CALL_glClear(GL_COLOR_BUFFER_BIT);

If there is no current context, then you should not make any calls to OpenGL (GLX is okay though). In addition, almost all OpenGL calls, and context-switching GLX calls, are illegal between glBegin and glEnd. It is also illegal to switch contexts while in feedback or selection mode, although this is not well handled at the moment. Finally, remember that BuGLe aims to support OpenGL 1.1 and thus anything not in OpenGL 1.1 must be treated as an extension.

To simplify matters, some utility functions are provided.

Sharing symbols between libraries

Sometimes it is necessary to access variables or functions that reside in a different filter library. To get a filter_set * handle to the filter-set foo, call

bugle_get_filter_set_handle("foo")

With a filter_set * handle, symbols (global variables and functions) are obtained with

bugle_get_filter_set_symbol(handle, "symbol")

State management

BuGLe is designed from the ground up to work with multiple contexts, and a good filter should as well. Part of this is recognising that a lot of things that you might want to store in global variables in fact needs a copy per context. BuGLe has a fairly generic method for associating state with various objects such as contexts, which is described in objects.html.

Logging

BuGLe has a logging system that acts as a built-in filter-set. If you intend to do any logging, see the comments at the top of src/log.h.

Conventions

  1. If a filter named filter has only one callback, call it filter_callback. If it has one for each of several functions, then call each filter_glSomeFunction.