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:
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)value
void
pointer to a location to update. For the integer
types, the target must be of type long
. For boolean variables,
it must be of type bool
, and for string variables it must
be of type char *
. In the last 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 stderr
and return
false
. The callback may also be NULL
.
filter_set_variable_type
enum
of the possible values for the type
field above.filter_set_info
name
init
bool filter_set_initialiser(filter_set *);It should return
true
on success and false
on
failure. See below for more information on initialisation.
done
void filter_set_destructor(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 initialiser
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.
Activation is slightly different from initialisation. Initialisation happens only once at startup time, and should be used to open files, allocate memory and so on. Destruction is the complement of initialisation, and occurs at program termination. Activation and deactivation are not yet fully supported, but the eventual goal is that the debugger can activate and deactivate filter-sets on the fly. An example of this would be the wireframe filterset: eventually it will be possible to control wireframe mode on a frame-by-frame basis. Some calls need to be trapped even when wireframe is not being used, so that the contexts can be identified, so initialisation must still be done at program startup.
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
.