[About] [News] [Documentation] [Screenshots]
Table of Contents
This chapter is aimed at developers who wish to implement new filter-sets for bugle. Bugle uses a module system to make this relatively easy to do without making intrusive changes to the base system.
It is also worth reading for anyone interested in hacking on the bugle core.
The API has been designed so that filter-sets can be maintained separately from bugle, even if the filter-set depends on OpenGL extensions that were not available at the time bugle was compiled. It has also been designed so that installing a newer (or older) version of bugle will not necessarily break separately installed filter-sets purely because the set of OpenGL headers has changed.
Warning
The API is still changing regularly and there is room for optimisation, so upgrades to bugle may break the API or ABI.
This section contains stylistic guidelines and best practices. You should follow them if you hope to get your filter-sets included in the bugle source code, or want them to be generally useful to other users.
Indenting is four spaces.
Use spaces not tabs.
Both opening and closing braces go on their own line.
Function return types go on the same line as the rest of the prototype.
Filter-sets, and any other code that will be preloaded into a user program, should link against as few libraries as possible, since if the user program uses a different version there will be conflicts. As a result, C++ code cannot be used if it links against
libstdc++
.Functions that act on
type
should be namedtype
_action
.Types and functions that are intended to be used from outside the file/submodule where they are defined should be prefixed with
bugle_
. This is an area where consistency could be improved.A constructor for a type that returns a
malloc
ed instance should be named
, whereas a constructor that modifies existing memory is calledtype
_new
. The corresponding destructors aretype
_init
andtype
_free
. For a module of code, initialisation and shutdown functions should be namedtype
_clear
(note spelling) andmodule
_initialise
. This in particular applies to filter-sets.module
_shutdownFunctions in filter-sets should be declared
static
, unless they are to be exported to other filter-sets. Exported functions must have some vendor prefix. This isbugle_
for the filter-sets distributed with bugle, but should be something else for other filter-sets to avoid conflicts.C files should contain conforming C89 syntax as far as possible. That means no variable initialisers, no variable-length arrays, no C++-style (
//
) comments, and no code after declarations.Non-C89 library functions should not be called directly unless they are protected by a suitable test of BUGLE_PLATFORM_
PLATFORM
. If such a function is used in multiple places with alternatives for multiple operating systems, it really should be abstracted through the platform layer.
Most of the state in OpenGL is associated with a particular context. Each thread may have a different current context, or no context. Each context can be current in at most one thread. You should keep this in mind when you attempt to track any particular OpenGL state. If you are using a global variable then your code is almost certainly not correct for multi-threaded or multi-context applications.
The object sub-system in bugle makes it easy to associate data with a particular context, and is the recommended method.
BuGLe tries to support systems that only have
runtime support for OpenGL 1.1, to
support the Windows XP software rasteriser. However, as of
December 2011, an up-to-date glext.h
file
is a requirement, so you may assume that the reflection system knows
about all functions and enums in the latest version of this file.
There is a still a lot of code that pre-dates this policy and which
is protected by #ifdefs, but this was found to make the code more
difficult to understand and maintain.
On the other hand, code that depends on recent OpenGL extensions is unlikely to work on OpenGL ES 1.x or 2.0, and so should be protected by a test for BUGLE_GLTYPE_GL.
When making run-time extension checks, one should generally use
BUGLE_GL_HAS_EXTENSION_GROUP
applied to the
older versions of the extension. This will also return true when
newer versions are present (provided that the extension promotion
table in gengl.py
is up to date).
At present there are no aclocal macros
to set up bugle, but there is a
pkg-config configuration file. Note
that with the default install paths for bugle, not all
pkg-config installations will look in
the right place. You might need to set
PKG_CONFIG_PATH
to something like /usr/local/lib/pkgconfig
.
Apart from the basic CFLAGS
and
LDFLAGS
extracted from
pkg-config, you should also ensure that
your module is compiled with suitable flags to make it
thread-safe.
The installation directory can also be extracted from pkg-config:
pkg-config --variable=filterdir bugle
The mechanisms for intercepting and modifying OpenGL function calls are arranged in a four-level hierarchy. From largest to smallest, these are:
Filter libraries, which are a loose collection of filter-sets. These allow filter-sets to be grouped together to avoid having hundreds of files. It also makes shared access to data easier.
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.
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.
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.
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.
Budgie is the module within bugle that generates huge
amounts
of code automatically. Apart from the code to override each
OpenGL function, it also generates a lot of reflection
information about functions and types. The API for the
generated code isn't currently documented. Refer to existing
filter-sets or to the header files in include/budgie
.
In bugle terminology, a function is
simply an OpenGL function. Both
glActiveTexture
and
glActiveTextureARB
are functions, and
they are referred to internally by the symbols
FUNC_glActiveTexture and
FUNC_glActiveTextureARB respectively.
At this point, you're probably thinking “but they're the same function!” Bugle deals with this using groups, which are sets of functions with different names but the same signatures and semantics. The group that contains these two functions may be referred to as either GROUP_glActiveTexture or GROUP_glActiveTextureARB. The symbols expand to the same value.
The values of these symbols can change every time the OpenGL
headers are modified, so the symbols should not be used in
filter-sets (in fact, the corresponding headers are not even
installed by bugle). Instead, you should use
budgie_function_name
,
budgie_function_id
and
budgie_function_group
to map between
these numbers and symbolic names on the fly. If you know the
name of the function as a literal piece of text at compile
time, you can write, for example, BUDGIE_FUNCTION_ID(glGetString)
to
get the symbol for glGetString
. At
present this simply wraps budgie_function_id
with
a cache variable, but in future there may be further
optimisations. In core code that includes budgielib/defines.h
, this
macro reduces to FUNC_glGetString. There are
similar macros BUDGIE_GROUP_ID and
BUDGIE_TYPE_ID.
Each library must contain an initialisation function with the following signature:
void bugle_initialise_filter_library( | void) ; |
This is the literal name of the function. It will be called by bugle's loader code, and you should not mark it as a constructor or anything similar. Note that it will always be called, even if the corresponding filter-sets are not requested by the user.
This function does any global initialisation required, registers filter-sets, and registers ordering requirements between filter-sets:
#include <bugle/filters.h>
filter_set *bugle_filter_set_new( | filter_set_info *info) ; |
The filter_set_info must be constant and have global lifetime, since bugle will continue to refer to it after the call returns. It has the following fields:
typedef struct { const char * name; filter_set_loader load; filter_set_unloader unload; filter_set_activator activate; filter_set_deactivator deactivate; const filter_set_variable_info * variables; const char * help; } filter_set_info;
The fields have the following meanings:
name
The name of the filter-set, as it will appear in
~/.bugle/filters
.load
,unload
,activate
,deactivate
Callback functions that are used to load, unload, activate and deactivate the filter-set. They are described in more detail in Section 4.6.3, “Filter-set initialisation and destruction”.
variables
An array describing the variables that may be configured by the user. This is described in Section 4.6.2, “Filter-set variables”.
help
A one-line description of the filter-set, which is displayed if an unknown chain is requested by the user.
Many filter-sets are configurable via variables that are set
in the configuration file. The
variables
field of a
filter_set_info points to an array of
filter_set_variable_info structures,
which have the following format:
typedef struct { const char * name; const char * help; filter_set_variable_type type; void * value; bugle_bool (*callback)(const filter_set_variable_info *var, const char *text, const void *value); } filter_set_variable_info;
The name
and
help
fields have the same use as
they do in filter_set_info. The
type
is one of the enumerants in
Table 4.1, “Filter-set variable types”.
Table 4.1. Filter-set variable types
Enumerant | C type | Checks |
---|---|---|
FILTER_SET_VARIABLE_BOOL | bugle_bool | |
FILTER_SET_VARIABLE_INT | long | |
FILTER_SET_VARIABLE_UINT | long | ≥ 0 |
FILTER_SET_VARIABLE_POSITIVE_INT | long | > 0 |
FILTER_SET_VARIABLE_FLOAT | float | finite |
FILTER_SET_VARIABLE_STRING | char * | |
FILTER_SET_VARIABLE_KEY | bugle_input_key | valid |
FILTER_SET_VARIABLE_CUSTOM | any |
With the exception of the
FILTER_SET_VARIABLE_CUSTOM type, each
enumerant corresponds to a C type. Set the
value
field to a pointer to a
C variable of this type, and it will be overwritten with the
value specified by the user. The C variable is not modified if
the user does not specify a value, so it is best to initialise
it with an appropriate default value.
Sometimes it is necessary to perform some extra validation or
initialisation when a variable is passed. For example,
bugle-showstats(7) accepts the show
variable
multiple times to indicate which statistics should be
displayed. The callback
field may be
set to a callback function, which is passed a pointer to the
corresponding
filter_set_variable_info object, the
raw string present in the configuration file, and a pointer to
the interpreted value. Note that at the time of the callback,
the variable passed will not have been
modified. The callback should return
BUGLE_FALSE if the value was invalid and
BUGLE_TRUE otherwise.
Apart from initialising internal structures, a filter-set loader registers filters and callbacks. The function to register a filter within a filter-set is
filter *bugle_filter_new( | const char *name) ; |
A filter * is an opaque handle to a filter. It is passed to the following functions to register callbacks:
void bugle_filter_catches( | filter *f, |
const char *group, | |
bugle_bool inactive, | |
filter_callback callback) ; |
void bugle_filter_catches_function( | filter *f, |
const char *function, | |
bugle_bool inactive, | |
filter_callback callback) ; |
void bugle_filter_all( | filter *f, |
bugle_bool inactive, | |
filter_callback callback) ; |
These respectively register callbacks for a single group, a
single function, or all functions.
inactive
says whether the callback
should apply when the filter-set is inactive. Usually you will
want to set this to BUGLE_FALSE, but in some cases
it is necessary to track some state even when the filter-set
is inactive. When using a group, you can use the name of any
function in that group, although it is best to use the one
with the oldest name as bugle is most likely to know about
it. If bugle was compiled without OpenGL headers exposing
that function, it is ignored.
A filter-set should also indicate ordering dependencies between filters. This is done by calling
void bugle_filter_order( | const char *first, |
const char *second) ; |
This does not require that either filter is actually loaded.
It simply indicates that if both are present, then they are to
be run in the specified order. The most common use is to
specify an ordering relative to
invoke
, the filter that is
responsible for passing a function call on to the real OpenGL
library.
In the functions above, callback
is a
function with the signature
bugle_bool callback( | function_call *call, |
const callback_data *data) ; |
The callback should generally return BUGLE_TRUE.
Returning BUGLE_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.
Callbacks that handle multiple OpenGL functions will need
to know which 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
gl
,
then Function
*call->
is the glFunction
.argi
i
th argument, and *call->
is the return value. If the function is not known at compile
time, then the arguments can be accessed via the
void pointers glFunction
.retncall->generic.args
and [i
]call->generic.retn
. These
values can also be modified to change the arguments used or
the value returned to the application, respectively.
The data
is currently still undergoing
revision, so it will not be documented yet.
Bugle has support for associating data with OpenGL objects such as contexts, textures, calls and so on. It is implemented using a generic interface that is not OpenGL-specific, and utility code to deal with specific types of objects is implemented on top of this.
- class (object_class)
A class is like an OOP class, in that objects are instantiations of classes with data.
- registrant
Several units of code may wish to attach storage to an object, and they need to be kept separate from each other. Each such unit is known as a registrant. There is no explicit structure to represent registrants: it's just a piece of terminology for the documentation.
- object (object *)
An object holds all the data that all registrants have attached to a single OpenGL object. It should be treated as being opaque.
- view (object_view)
This term is somewhat ambiguous. The object_view type is a key used to fetch a particular registrant's data from objects of a particular class. In some places, however, it also means the actual data that is stored in the object.
Each registrant is free to define these data in any way it likes. The memory will be suitably aligned for all built-in data types (at present the memory is allocated with
malloc
, but this may well change for efficiency reasons).- scope
This has nothing to do with inheritence. It refers to the scope at which an object can be considered to be current. For example, each thread has a current context, and each context may have a current display list, so display lists have context scope. Bugle allows you to specify the current object for each class (which may be NULL), and retrieve it later.
Class structs are generally declared as global pointers.
However, exported variables from a shared library is not very
portable, so access to the class struct should be via a
function call. And example of such a function call is
bugle_get_namespace_class
.
#include <bugle/objects.h>
object_class *bugle_object_class_new( | object_class *parent) ; |
void bugle_object_class_free( | object_class *klass) ; |
These are the constructor and destructor for classes. The
parent
is either the class defining the scope of
the new class, or NULL to indicate that the
new class has thread scope (like an OpenGL context).
Warning
The bugle_object_class_free
function
does not delete instances of the class. They must be
manually deleted first. Attempting to manipulate objects
once their class (or any ancestor class in the scope tree)
has been deleted will lead to undefined behaviour.
object_view bugle_object_view_new( | void (*constructor)
( const void *, void *)
, |
void (*destructor)
( void *)
, | |
size_t size) ; |
This function creates a new view on an existing class. The
constructor is described later. The destructor is simply
called when the object goes away, and receives a pointer to
the view data. It is responsible for cleaning up, but it must
not attempt to free
the pointer.
The size
is the number of
bytes that the registrant wishes to associate with each
instance of this class. It is legal for the size to be zero,
in which case no data is associated but the constructor and
destructor will still be called to provide notification of
object creation and destruction.
Since the object system has no knowledge of OpenGL, each type of object requires some management code to shadow creation and destruction of OpenGL objects, and to keep track of which object is current.
object *bugle_object_new( | object_class *klass, |
const void *key, | |
bugle_bool make_current) ; |
This instantiates a new instance of the given class.
Firstly, if
make_current
is BUGLE_TRUE
then the new object is immediately made the current object of
its class. Then, for each registrant the constructor is called
to initialise the data in its view. The constructor is passed
key
and the view data (the
key
parameter exists
only to pass extra information to the constructor, usually
about the associated GL object). Before calling the
constructor, the view data is filled with zero bytes. The
constructor is optional and may be NULL.
void bugle_object_free( | object *obj) ; |
For each registrant of the class, calls the destructor (if any), passing a pointer to the view data. It then frees the memory associated with the object. Don't forget that some OpenGL objects don't actually die when you delete them, only when they are no longer referenced.
object *bugle_object_get_current( | const object_class *klass) ; |
void bugle_object_set_current( | object_class *klass, |
object *obj) ; |
These functions get and set the current object, within the context of that class. For example, when setting the current display list, it is made current within the current OpenGL context, and switching OpenGL contexts will yield a new current display list. It is legal for the current object to be NULL, and in fact this is the initial state.
If the current object is destroyed, then NULL becomes the new current object.
void *bugle_object_get_data( | object *obj, |
object_view view) ; |
void *bugle_object_get_current_data( | object_klass *klass, |
object_view view) ; |
This retrieves the view data stored in
obj
. The view
is
the handle returned by
bugle_object_view_register
. One can get
the data from the current object with the convenience function
bugle_object_get_current_data
(if it is
certain that the current object is not NULL).
Manipulations of a particular class must be serialised, as there is no internal protection against concurrent access. This is seldom a problem since modification of a class is usually done during startup and shutdown, which are serialised.
The object manipulation functions are safe to use
concurrently, since the object structures are not modified.
Setting the current object is thread-safe unless the class has
a parent in the scope tree and an instance of that parent is
current in more than one thread. Even then, it is safe on most
reasonable machines, since setting the current object amounts
to overwriting a single pointer. Note that
bugle_object_new
and
bugle_object_free
may both set the
current object implicitly.
Manipulation of the view data is of course up to each individual registrant, and it is responsible for managing any concurrency issues that arise.
The functions listed above are the minimal set needed to write a
filter-set. This section describes a number of utilities which
simplify the job of writing a more powerful filter-set. Most of
the functions described here are defined in bugle/gl/glutils.h
.
Many OpenGL functions may not be called between
glBegin
and glEnd
.
To facilitate checking for this, the
glbeginend
filter-set provides the
boolean function bugle_gl_in_begin_end
which
indicates whether the current context is in this state. This
function is defined in the header bugle/gl/glbeginend.h
. You must also
also call bugle_gl_filter_post_queries_begin_end("
as part of the filter-set initialisation, for filters that run
after invocation. Failing to do so may yield incorrect results
during interception of filtername
")glBegin
or
glEnd
.
Warning
Do not call
bugle_gl_filter_post_queries_begin_end
for filters that run before invocation, or you will
generate a cyclic dependency.
If there is no current OpenGL context, this call will return BUGLE_TRUE. This means that whenever the call returns false, it should be safe to issue OpenGL commands. Refer to Section 4.8.3, “Making calls to OpenGL” for more information about making OpenGL calls from within a filter-set.
It is often useful to know whether an OpenGL call generated an
OpenGL error. If your filter-set
filterset
wishes to do this, it
must call bugle_gl_filter_set_queries_error("
during library initialisation. To actually retrieve the error,
call filterset
")bugle_gl_call_get_error(data->call_object)
.
This mechanism is not totally reliable. It is illegal to call
glGetError
when there is no OpenGL
context or when between glBegin
and
glEnd
. In these cases,
GL_NO_ERROR may be returned even though the
call did not succeed.
In the current implementation, enabling this mechanism also
drasticly reduces performance, as every function call is
intercepted to call glGetError
.
If you attempt to call an OpenGL function directly, the
interception system will intercept the call and bugle will
be entered recursively. There is protection against this (the
call will be forwarded to the real OpenGL library without
further action), but it is nevertheless a performance hit. To
call an OpenGL function
,
you should call it as
glFunction
CALL(glFunction
)(args
);
This should only be done if you put the actual name of the
function as literal text. If you have a string containing the
name, it can be passed to
budgie_function_address_real
instead.
If glFunction
was not known when
bugle was compiled, this will generate a call to
glFunction
. If the OpenGL library
does not have this symbol (which it need not, since OpenGL
entry points beyond OpenGL 1.2 should be queried with
glXGetProcAddressARB
), then you will get
an unresolved symbol error at run-time. On Windows, you will
get a link failure.
If the function is not defined by OpenGL 1.1, you must first check that the extension is supported at run time, and if it is not defined by OpenGL 2.0, you must also check that it is defined at compile time. This is described in Section 4.8.4, “Checking for OpenGL extensions”. Note that bugle will first try the function whose name exactly matches what you pass, but if that does not exist, it will then try the other functions in the same group.
For OpenGL (as opposed to GLX) functions, it is also important to have a valid context which is not inside begin/end. Some utilities are provided to simplify the interaction with begin/end checking. A filter-set that wishes to make OpenGL calls must be registered at library initialisation time with
bugle_gl_filter_set_renders("filter-set
");
and any filters that make such calls and which run after invocation must be registered during filter-set initialisation with
bugle_gl_filter_post_renders("filter
");
To begin a section of code that wishes to make OpenGL function
calls, call bugle_gl_begin_internal_render()
. It
will return a boolean, indicating whether it is safe to
proceed. If it is, you must terminate that block of code by
calling
bugle_gl_end_internal_render("
The first parameter is
a descriptive name for the section of code (typically the
name of the containing function), which is logged if the code
generates any OpenGL errors. Setting the second parameter to
BUGLE_FALSE, however, suppresses the warning, which
should only be done if you have intentionally written code
that may generate OpenGL errors.
name
", BUGLE_TRUE);
Like functions and groups, bugle enumerates the extensions
it knows about, but the enumeration may change frequently and
so is not part of the API. Mapping between names and the
enumeration can be done with
bugle_gl_extension_name
and
bugle_gl_extension_id
, and the macro
BUGLE_GL_EXTENSION_ID can be used with the
literal unquoted name of the extension.
The following macros simplify checking for the presence of an OpenGL extension in the current context:
#include <bugle/gl/glextensions.h> bugle_bool BUGLE_GL_HAS_EXTENSION(GL_EXT_some_extension
); bugle_bool BUGLE_GL_HAS_EXTENSION_GROUP(GL_EXT_some_extension
); bugle_filter_set_depends("myfilterset"
, "glextensions");
An extension group is a set of extensions with similar
functionality. For example, when passing
GL_EXT_pixel_buffer_object to
BUGLE_GL_HAS_EXTENSION_GROUP
, it will return
BUGLE_TRUE if any one of
GL_EXT_pixel_buffer_object,
GL_ARB_pixel_buffer_object, or OpenGL 2.1 is
available.
Note
Unlike with function groups, there associations are not usually symmetric, because extensions supersede others rather than having identical functionality.
There are also some special extension groups, defined in
src/gengl/gengl.py
, that are
convenient ways to test for certain functionality that is
provided by several extensions but is not the sole function of
any of them.
Apart from extensions, you can also check for OpenGL versions using symbols such as GL_VERSION_2_0. The list of extensions is parsed once per context, so these checks are reasonably cheap. If there is no current context, the function returns BUGLE_FALSE.
Remember that you also need to do a compile-time check before using any of these symbols; see Section 4.3.3, “OpenGL extensions” for details.