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:
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_set
filter
filter_set_variable_info
name
help
NULL
to leave the variable undocumented.type
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
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
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
enum
of the possible values for the type
field above.filter_set_info
name
load
bool filter_set_loader(filter_set *);It should return
true
on success and false
on
failure. See below for more information on initialisation.
unload
void filter_set_unload(filter_set *);
activate
void filter_set_activator(filter_set *);
deactivate
void filter_set_deactivator(filter_set *);
variables
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
0
for now.
help
NULL
to leave the filter-set undocumented. This is
recommended for internal or helper filter-sets.
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
.
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.
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)
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)
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.
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.
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.
bugle_register_filter_set_renders("foo")
in
the library initialiser. This causes the filter-set to depend on the
necessary utility filter-sets. In addition, for each filter
bar that makes GL/GLX calls after the captured call is
executed, one must call
bugle_register_filter_post_renders("bar")
. This inserts
ordering dependencies to ensure that errors from the original call are
not confused with errors generated by the inserted calls.bugle_begin_internal_render()
. If it returns false, then
it is not safe to render at this time. Otherwise, make your calls, then
call bugle_end_internal_render("name",
warn)
. If warn is true and your calls
generated any GL errors, then a warning will be written to standard
error including name (which should be the name of the callback
function).bugle_gl_has_extension(BUGLE_GL_EXT_some_extension)
to determine whether the extension is present (the defines for
available extensions are in the generated file glexts.h
).
This is a relatively cheap operation, since the list of available
extensions is parsed at context creation time rather than call
time.
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")
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.
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
.
filter_callback
. If
it has one for each of several functions, then call each
filter_glSomeFunction
.