Table of Contents
While other projects may have simply a “Windows” port and a “GNU” port, for example, it is not so simple for bugle. For example, one might run on Windows but using Cygwin with an X server, or on Linux but using EGL rather than GLX. Instead, a target system is based on a number of categories:
- Binary format
The binary format determines the details of how dynamic linking is done and how it may be subverted. For example, the Windows PE format explicitly indicates which symbols come from which library, and hence require an entire library to be impersonated, while ELF files just list dependent libraries and dependent symbols, making it easy to replace certain symbols.
- Operating system API
This determines how things like segfault handling and other low-level operations are performed. Examples are POSIX and Win32.
- Windowing system
The main effect of the windowing system is to determine how keyboard and mouse events may be intercepted.
- GL-Window system integration
This determines how GL interacts with the windowing system. Usually there is a one-to-one mapping (e.g., AGL, WGL, GLX), but EGL applies on any windowing system.
- Filesystem layout
This determines where to look for files, namely bugle configuration files and system libraries, and potentially temporary files.
Rather than conditionally compiling code based on the particular system in use, each system is characterised by a set of defines that describe its essential properties. This makes it easier to port bugle to similar but not identical systems in future: new code is needed only for those parts either specific to the system in question, or where the new system behaves in an entirely novel way.
The following properties are defined as either 0 or 1, meaning false or true; the descriptions indicate the meaning if true:
The binary format lists the external library that is to be used for each external symbol.
Bugle is injected by
LD_PRELOADor an equivalent mechanism. If false, the bugle library is given the same name as the usual OpenGL library (e.g.,
opengl32.dllon Windows), and loads the original library with a full path.
At present the supported binary formats are PE (BUGLE_BINFMT_PE) and ELF (BUGLE_BINFMT_ELF), although other formats could well be similar enough to be put in the same category.
For the most part, code that needs to be OS API aware needs to
have code implemented for each target OS. The only use of this
in the API at present is the use of
siglongjmp to return useful information
in the face of an access violation, and these functions may be
detected via autoconf.
For this reason, no preprocessor symbols are currently defined to indicate the OS API. This is subject to change should a need arise.
As for OS porting, code that is windowing system specific needs to be written for each windowing system. The following window systems are defined:
The primary difference between GL-Windowing integration APIs is whether extension function pointers are context-dependent or constant. This is determined by BUGLE_GLWIN_CONTEXT_DEPENDENT. The only currently supported instance of this is WGL (BUGLE_GLWIN_WGL). EGL (BUGLE_GLWIN_EGL) and GLX (BUGLE_GLWIN_GLX) both require function pointers to be the same in any context.
Filesystem layout determines the conventional placement of files. Three variants are defined
Configuration files are stored in
$and system libraries are searched for in
Configuration files are stored in
$, but system libraries are searched for in
The term “platform” has a specific meaning in bugle porting: it is the set of standard runtime libraries available, together with the associated header files. The currently available platforms are:
This is a dummy platform which provides all the interfaces but no implementation. It will compile and link, and is intended as a stepping stone for porting to new platforms.
This is IEEE 1003.1-2001 with the _POSIX_THREADS option. Other optional features will be used if present (including GNU extensions), but are (in theory) not required.
Microsoft Visual C++ runtime. This is only tested with Visual C++ 9.0.
MinGW runtime, based on msvcrt. It is treated separately because MinGW provides its own header files.
Adding a new interface requires implementing a number of
functions and header files, described below. Because some
implementations can be shared between platforms (particularly
implementations that bypass system libraries), each function
(or closely-related group of functions) is implemented in a
separate file in
Some of these functions return newly allocated memory.
These functions must terminate the program if memory
could not be allocated, by calling
bugle_alloc_die (or by allocating the
Failure behaviour is loosely defined, since the methods
of reporting failure vary from one platform to another.
Functions may modify
errno but are
not required to.
|const char *format,|
|const char *format,|
These functions must implement the C99 semantics of the corresponding functions, in particular, returning the number of characters that would have been written if truncation had not occurred.
Returns a copy of
str in newly
truncates the string to at most
|const char *format,|
printf, but returns the
written data in newly allocated memory.
Returns a timestamp, measured in seconds since an
arbitrary point in time (bugle_timespec is
bugle/time.h). There are
no specific requirements on precision, but since this
function is used for statistics it should have at least
millisecond precision. If possible, it should also use a
monotonic clock that will not be affected by changes to
the system time, daylight savings, leap seconds etc.
Returns a non-zero value if
respectively finite or not-a-number.
Returns a non-signalling not-a-number value.
x rounded to the nearest
integral value. Half-integer values may be rounded in
These correspond to the C99 functions
|const char *filename,|
bugle_dl_open loads a dynamical module.
filename is the name of the file to
open and will not be NULL. If it is not an
absolute pathname, it is platform-dependent how the file is
located. The possible value for flags are
Place the symbols in the module in the global symbol table. This flag may be ignored.
Resolve all symbols in the module immediately, rather than on first use. This flag may be ignored.
Append a platform-specific suffix to the filename before trying to locate it. If this flag is absent, it is still permitted (but not required) for the implementation to append a suffix.
The type bugle_dl_module is an opaque pointer, which the implementation may interpret as it wishes. This function is not required to be reentrant nor to support multiple loading of the same library. However, it must be possible to open a library that is already linked into the application by other means.
A module is closed with
bugle_dl_open supports opening the
same library multiple times, then the library must not
actually be released until it has been closed as many times
as it has been opened. Returns zero on success, non-zero on
|const char *symbol|
|const char *symbol|
These functions retrieve a symbol.
module must be a handle previously
there are no magic values for retrieving global symbols.
These functions return NULL if the
function is not declared.
|const char *path,|
path (non-recursively) for
loadable modules (files with appropriate filenames), and
callback for each file found.
arg is passed on to
If this function fails, it is responsible for logging an appropriate error and terminating the process.
This file provides miscellaneous macros. At present, the
only macro that is defined in
BUGLE_VA_COPY, which must implement the
This file provides thread primitives. While this section lists a number of functions, they may be implemented as function-like macros or inline functions, and probably should where they are thin wrappers around platform functions.
The header file needs to define the following types and defines. With the exception of bugle_thread_once_t, there is no requirement that variables of these types are useable before being initialised by an appropriate function call.
A type used to allow an initialisation function to be run only once.
A static initialiser for a bugle_thread_once_t. There is no guarantee that this can be used in an assignment expression.
A mutual exclusion lock.
A multiple-reader, single-writer lock.
A key used to identify thread-local storage.
A thread identifier, useful only for comparisons.
A thread handle, used to join with a thread.
|void (* once_function)
once has not previously been used
with this function; otherwise waits until the previous
call has completed.
once must have
been staticly initialised to
These functions respectively create and destroy a lock. Locks may only be used when initialised. Initialising an already initialised lock, destroying an uninitialised lock, or destroying a lock that is held, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.
These functions respectively obtain or release a lock. Attempting to obtain a lock that is already held, release a lock that is not held by this thread, or perform an operation that results in deadlock all have undefined behaviour. These functions return zero on success, non-zero on failure.
These functions respectively create and destroy a read-write lock. Locks may only be used when initialised. Initialising an already initialised lock, destroying an uninitialised lock, or destroying a lock that is held, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.
These functions obtain a read-write lock for reading or writing, or release a held lock. Attempting to obtain a lock that is already held, release a lock that is not held by this thread, or perform an operation that results in deadlock all have undefined behaviour.
Bugle does not itself depend on multiple concurrent readers, but some drivers make synchronous callbacks from different threads that will deadlock if this is not implemented. It is also recommended that implementations are designed to avoid writer starvation.
|unsigned int value|
These functions respectively create and destroy a counting semaphore. Semaphores may only be used when initialised. Initialising an already initialised semaphore, destroying an uninitialised semaphore, or destroying a semaphore on which any thread is blocked, all lead to undefined behaviour. These functions return zero on success, non-zero on failure.
The underlying platform may impose an implementation limit on the maximum value of a semaphore. You should not expect to be able to use enormous semaphore values. Values up to 65536 should be safe.
These functions increment or decrement a semaphore count.
If the count is currently zero,
bugle_thread_sem_wait will block
until it can decrement it, while
bugle_thread_sem_trywait will fail.
These functions return zero on success or non-zero on
will return a positive value if it failed because the
count was zero and a negative value if it failed for some
The value of errno after calling any of these functions in undefined.
Respectively create or destroy a key for thread-local storage. On key creation, the value NULL is associated with the key in all threads. On thread creation, the value NULL is associated with all keys in that thread. These functions return zero on success, non-zero on failure.
When a thread terminates, if the destructor and the associated value are both non-NULL then the destructor should be called with the value. The order of destructors is unspecified, and the destructors will have undefined behaviour if they try to set or query any thread-local storage. If a key is destroyed, the destructor will not be called on any of its associated data.
At present, not all platforms implement destructors. These platforms will leak resources and should be fixed.
|const void *value|
Respectively set or query a thread-local value.
Thread-local values are of type void *.
zero on success, non-zero on failure;
bugle_thread_getspecific must never
fail given a valid key (an invalid key leads to undefined
sig to the current thread,
similar to the C
raise function. This
function is not required to wait for signal handlers to
Returns an OS identifier for the current thread. Two concurrently executing threads must have different identifiers. Ideally, identifiers should not be recycled during a process, but this may be impossible to guarantee.
Thread IDs must not be compared directly, but with
which will return true if the thread IDs refer to the same thread.
These functions lock or unlock a file handle for
concurrent access. While locked, other threads attempting
to use the filehandle will be blocked, even if they do not
explicitly lock. Locking is recursive, so the handle
becomes unlocked when
bugle_funlockfile has been called as
many times as
bugle_funlockfile in a thread
which does not hold a lock yields undefined behaviour.
Since not all platforms provide this mechanism, and since bugle only relies on it to avoid interleaving output from different threads, platforms are permitted to implement these functions as no-ops.
These macros implement an initialise-once mechanism at a higher level than bugle_thread_once_t. The function must be static and have no parameters or return value. BUGLE_CONSTRUCTOR is used at file scope prior to the declaration of the function, while BUGLE_RUN_CONSTRUCTOR is used inside code to ensure that the function has been run.
Implementations are free to execute the function at any point up to BUGLE_RUN_CONSTRUCTOR returning. In particular, they may choose to implement these macros using constructor semantics. However, the function must be run in an environment where it may safely execute all the functions in this section, particularly the dynamic library functions.
Threads can be created and destroyed with
|unsigned int (*start)
|unsigned int *retval|
These functions return zero on success. POSIX detached threads are not supported; all threads must be joined to avoid leaking resources. Joining a thread also invalidates the handle.