[About] [News] [Documentation] [Screenshots]

Chapter 4. Extending bugle

4.1. Introduction

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.

4.2. API and ABI compatibility

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.

4.3. Guidelines

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.

4.3.1. Coding style

  • 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 named type_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 malloced instance should be named type_new, whereas a constructor that modifies existing memory is called type_init. The corresponding destructors are type_free and type_clear. For a module of code, initialisation and shutdown functions should be named module_initialise (note spelling) and module_shutdown. This in particular applies to filter-sets.

  • Functions 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 is bugle_ 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.

4.3.2. OpenGL contexts and threads

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.

4.3.3. OpenGL extensions

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).

4.4. Compiling an external filter-set library

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

4.5. Concepts

4.5.1. Callbacks, filter-sets, filters and libraries

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.

4.5.2. Loading and 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.

4.5.3. Budgie

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.

4.5.4. Functions and groups

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.

4.6. Basic filter-set API

4.6.1. Library initialisation

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.

4.6.2. Filter-set variables

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

EnumerantC typeChecks
FILTER_SET_VARIABLE_BOOLbugle_bool 
FILTER_SET_VARIABLE_INTlong 
FILTER_SET_VARIABLE_UINTlong≥ 0
FILTER_SET_VARIABLE_POSITIVE_INTlong> 0
FILTER_SET_VARIABLE_FLOATfloatfinite
FILTER_SET_VARIABLE_STRINGchar * 
FILTER_SET_VARIABLE_KEYbugle_input_keyvalid
FILTER_SET_VARIABLE_CUSTOMany 

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.

4.6.3. Filter-set initialisation and destruction

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.

4.6.4. Writing callbacks

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 glFunction, then *call->glFunction.argi is the ith argument, and *call->glFunction.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.

The data is currently still undergoing revision, so it will not be documented yet.

4.7. Object shadowing

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.

4.7.1. Concepts

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.

4.7.2. Class management functions

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.

4.7.3. Object management functions

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).

4.7.4. Thread safety

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.

4.8. Utility functions and filter-sets

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.

4.8.1. Begin/end checking

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("filtername") as part of the filter-set initialisation, for filters that run after invocation. Failing to do so may yield incorrect results during interception of 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.

4.8.2. Error checking

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("filterset") during library initialisation. To actually retrieve the error, call 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.

4.8.3. Making calls to OpenGL

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 glFunction, you should call it as

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("name", BUGLE_TRUE); 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.

4.8.4. Checking for OpenGL extensions

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.


Get BuGLe at SourceForge.net. Fast, secure and Free Open Source software downloads