[About] [News] [Documentation] [Screenshots]
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:
- BUGLE_BINFMT_SYMBOL_BY_DSO
The binary format lists the external library that is to be used for each external symbol.
- BUGLE_BINFMT_LDPRELOAD
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
signal and
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:
BUGLE_WINSYS_X11
BUGLE_WINSYS_WINDOWS
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
- BUGLE_FS_UNIX
Configuration files are stored in
$and system libraries are searched for inHOME/.bugle/usr/lib.- BUGLE_FS_CYGMING
Configuration files are stored in
$, but system libraries are searched for inHOME/.bugleGetSystemDir().
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:
- null
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.
- posix
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.
- msvcrt
Microsoft Visual C++ runtime. This is only tested with Visual C++ 9.0.
- mingw
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 src/platform.
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
memory from bugle_malloc).
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.
int
fsfuncbugle_snprintf( | char *str, | |
| size_t size, | ||
| const char *format, | ||
...); |
int
fsfuncbugle_vsnprintf( | char *str, |
| size_t size, | |
| const char *format, | |
va_list ap); |
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.
char *fsfuncbugle_strdup( | char *str); |
char *fsfuncbugle_strndup( | char *str, |
size_t n); |
Returns a copy of str in newly
allocated memory. bugle_strndup
truncates the string to at most n
characters.
char *fsfuncbugle_asprintf( | const char *format, | |
...); |
Performs a printf, but returns the
written data in newly allocated memory.
int fsfuncbugle_gettime( | bugle_timespec *ts); |
Returns a timestamp, measured in seconds since an
arbitrary point in time (bugle_timespec is
defined in 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.
int fsfuncbugle_isfinite( | double x); |
int fsfuncbugle_isnan( | double x); |
Returns a non-zero value if x is
respectively finite or not-a-number.
double fsfuncbugle_nan( | void); |
Returns a non-signalling not-a-number value.
double fsfuncbugle_round( | double x); |
Returns x rounded to the nearest
integral value. Half-integer values may be rounded in
either direction.
float fsfuncbugle_sinf( | float x); |
float fsfuncbugle_cosf( | float x); |
These correspond to the C99 functions
sinf and cosf.
bugle_dl_module fsfuncbugle_dl_open( | const char *filename, |
int flag); |
int fsfuncbugle_dl_close( | bugle_dl_module module); |
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
- BUGLE_DL_FLAG_GLOBAL
Place the symbols in the module in the global symbol table. This flag may be ignored.
- BUGLE_DL_FLAG_NOW
Resolve all symbols in the module immediately, rather than on first use. This flag may be ignored.
- BUGLE_DL_FLAG_SUFFIX
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_close.
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
failure.
void *fsfuncbugle_dl_sym_data( | bugle_dl_module module, |
const char *symbol); |
BUDGIEAPIPROC fsfuncbugle_dl_sym_function( | bugle_dl_module module, |
const char *symbol); |
These functions retrieve a symbol.
module must be a handle previously
returned by bugle_dl_open —
there are no magic values for retrieving global symbols.
These functions return NULL if the
function is not declared.
void fsfuncbugle_dl_foreach( | const char *path, |
void (*callback)
(const char *filename,
void *arg)); |
Search path (non-recursively) for
loadable modules (files with appropriate filenames), and
calls callback for each file found.
arg is passed on to
callback.
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
C99 va_copy function.
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.
- bugle_thread_once_t
A type used to allow an initialisation function to be run only once.
- BUGLE_THREAD_ONCE_INIT
A static initialiser for a bugle_thread_once_t. There is no guarantee that this can be used in an assignment expression.
- bugle_thread_lock_t
A mutual exclusion lock.
- bugle_thread_rwlock_t
A multiple-reader, single-writer lock.
- bugle_thread_key_t
A key used to identify thread-local storage.
- bugle_thread_id
A thread identifier, useful only for comparisons.
- bugle_thread_handle
A thread handle, used to join with a thread.
int
fsfuncbugle_thread_once( | bugle_thread_once *once, |
void (* once_function)
(void)); |
Call once_function if
once has not previously been used
with this function; otherwise waits until the previous
call has completed. once must have
been staticly initialised to
BUGLE_THREAD_ONCE_INIT.
int fsfuncbugle_thread_lock_init( | bugle_thread_lock_t *lock); |
int fsfuncbugle_thread_lock_destroy( | bugle_pthread_lock_t *lock); |
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.
int fsfuncbugle_thread_lock_lock( | bugle_thread_lock_t *lock); |
int fsfuncbugle_thread_lock_unlock( | bugle_thread_lock_t *lock); |
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.
int fsfuncbugle_thread_rwlock_init( | bugle_thread_rwlock_t *lock); |
int fsfuncbugle_thread_rwlock_destroy( | bugle_thread_rwlock_t *lock); |
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.
int fsfuncbugle_thread_rwlock_rdlock( | bugle_thread_rwlock_t *lock); |
int fsfuncbugle_thread_rwlock_wrlock( | bugle_thread_rwlock_t *lock); |
int fsfuncbugle_thread_rwlock_unlock( | bugle_thread_rwlock_t *lock); |
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.
int fsfuncbugle_thread_sem_init( | bugle_thread_sem_t *sem, |
unsigned int value); |
int fsfuncbugle_thread_sem_destroy( | bugle_thread_sem_t *sem); |
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.
int fsfuncbugle_thread_sem_post( | bugle_thread_sem_t *sem); |
int fsfuncbugle_thread_sem_wait( | bugle_thread_sem_t *sem); |
int fsfuncbugle_thread_sem_trywait( | bugle_thread_sem_t *sem); |
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
failure; bugle_thread_sem_trywait
will return a positive value if it failed because the
count was zero and a negative value if it failed for some
other reason.
The value of errno after calling any of these functions in undefined.
int fsfuncbugle_thread_key_create( | bugle_thread_key_t *key, |
void (*destructor)
(void *)); |
int fsfuncbugle_thread_key_delete( | bugle_thread_key_t key); |
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.
int fsfuncbugle_thread_setspecific( | bugle_thread_key_t key, |
const void *value); |
void *fsfuncbugle_thread_getspecific( | bugle_thread_key_t key); |
Respectively set or query a thread-local value.
Thread-local values are of type void *.
bugle_thread_setspecific returns
zero on success, non-zero on failure;
bugle_thread_getspecific must never
fail given a valid key (an invalid key leads to undefined
behaviour).
int fsfuncbugle_thread_raise( | int sig); |
Send sig to the current thread,
similar to the C raise function. This
function is not required to wait for signal handlers to
complete.
bugle_thread_id fsfuncbugle_thread_self( | void); |
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
bugle_bool fsfuncbugle_thread_equal( | bugle_thread_id thread1, |
bugle_thread_id thread2); |
which will return true if the thread IDs refer to the same thread.
void fsfuncbugle_flockfile( | FILE *filehandle); |
void fsfuncbugle_funlockfile( | FILE *filehandle); |
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_flockfile.
Calling 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.
BUGLE_CONSTRUCTOR(function); BUGLE_RUN_CONSTRUCTOR(function);
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
bugle_bool fsfuncbugle_thread_create( | bugle_thread_handle thread, |
unsigned int (*start)
(void *), | |
void *arg); |
bugle_bool fsfuncbugle_thread_join( | bugle_thread_handle thread, |
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.
