HowTo Extend GLAF mini-guide
NOTE: This discussion is for OpenGL extensions *only*.
It doesn't apply to extensions to GLX/WGL/AGL/etc... At the moment of writing
this, GLAF is not yet ready to access GLX/WGL/AGL extensions. When GLAF
be ready to support such extensions, another "how to extend GLAF" document
will be published.
I-INTRODUCTION
Extending GLAF is easy if you know the details behind the extensions
you want to add support to. You're encouraged to do so. Some care and considerations
are needed before you begin to work:
GLAF must offer an API equivalent to the one of the OpenGL extensions.
So, when you want to support an extension, you need to add support for
all
the extension functions and enumerants.
Check whether your extension has suffered API modifications over the
time, because GLAF must offer the same API no matter the OpenGL
version or the extension version: For example, the extension could initially
be developed as a vendor-specific extension, and later become an EXT/ARB
extension, or even be included in the OpenGL core:
-
In the very best case (not very common), the extension didn't suffer any
modifications over the time, and was never merged into the core. This is
straightforward to code into GLAF. An example is the ABGR extension.
-
In the second best case (fortunately a very common case), the modifications
affected just the names of functions and enumerants. The complete
functionality and the enumerants numeric values were left unmodified. This
case is also easy to support. Examples: Texture Objects, Blending Equations,
Imaging Extensions, SubTexture, and Copy Texture.
-
A third case, not too bad, is when the functionality has been slightly
extended or reduced over the time. For the moment, since these functionality
modifications have been very little, it has been decided that GLAF
supports just the functionality subset that is available in all versions
of the feature. Example: A few enumerants of GL_EXT_texture were dropped,
or their numeric values modified when it was merged into the OpenGL 1.1
core, so GLAF doesn't support all the GL_EXT_texture enumerants.
-
A fourth case, a bit worse than previous ones, is when the meaning of API
arguments has been modified. An example is the Polygon Offset feature:
The meaning of the arguments for glPolygonOffsetEXT() and glPolygonOffset()
is different, and the later supports more offset modes than the first.
The best choice found for GLAF was to support the GL_FILL offset mode only,
and implement the glPolygonOffset() arguments meaning. So, when running
in a (older) glPolygonOffsetEXT() implementation, GLAF needs to do a transparent
conversion of arguments on the fly.
-
More complicated cases would need additional considerations. In the worst
case, if it's not possible to support a consistent API, the best choice
could be to provide "utility functions" that would offer the functionality
with an API different to the original one. This is very discouraged,
and should be done just as a last resource. An example is the RGBA LogicOp,
which is available as a blending equation in old versions, and as plain
LogicOp in new ones. The first requires to set the proper blend equation
and to glEnable(GL_BLEND), while the later just needs a call to glEnable(GL_COLOR_LOGIC_OP).
For this feature, GLAF offers a couple of "utility functions" (one for
enabling RGBA logicop and another for disabling it). A small side
effect in the blending state is introduced, but it's fully detailed in
the GLAF documentation, and it's guaranteed to happen in all implementations,
so the application behaviour will be the same no matter the implementation
version. Also, fortunately, the logicop mode is controlled with glLogicOp()
in both cases. Another lucky point is that OpenGL doesn't allow to enable
both blending and logicop at the same time, so the GLAF implementation
is valid because it can safely use blending since the user is not allowed
to do so.
Your new GLAF public API functions should call the proper OpenGL
extensions functions and return. No more than that. If your functions need
to do some extra work, you must study it very carefully, in order
to be sure that you're fully compliant with the OpenGL state machine
concept and that you don't introduce side effects. For example, the Polygon
Offset feature may need to
convert arguments depending on the system's OpenGL implementation,
but that conversion is a few maths only, so it doesn't affect the OpenGL
state machine. Another example, previously described, is RGBA LogicOp.
In this case, a side effect is introduced in the blending state, but it's
guaranteed to happen in all implementations. If you really need to introduce
side effects, you must be careful and be sure that they will happen exactly
in the same way in all implementations. Otherwise, if application developers
are not careful enough, applications could behave different in different
machines. Also, you're urged to explain the side effects details in the
documentation.
You should also consider whether the feature can be emulated if the
extension is not supported or if the OpenGL version is too old. In most
cases, you won't be able to emulate it (because you need to support the
whole functionality, which is often impossible to do from outside the OpenGL
implementation), but in some cases it's possible. For example, GLAF emulates
the Copy Texture feature if GL_EXT_copy_texture is not available and the
OpenGL version is older than 1.1. For doing the emulation, a glReadPixels
and a glTexImage* are performed, but the pixel transfer state (including
imaging extensions) needs
to be "frozen" in one of those calls, so that it doesn't be applied
twice. And care is taken for avoiding introducing side effects. Look at
the emulation source code of glaf_CopyTexImage2D().
II-STEP BY STEP GUIDE: HOW TO EXTEND GLAF
-
First, you need some official documentation for OpenGL extensions. Some
must-have documents are the "Extensions Registry", the latest OpenGL spec,
and a few OpenGL "gl.h" and "glext.h" include files from different versions
and vendors. These include files are useful for being confident that the
enumerants numeric values were left unchanged when an extension was modified,
or in different extension versions from different vendors/platforms.
-
Using both the "Extensions Registry" and the latest OpenGL spec, locate
how your wished feature has been implemented over the time. If at some
point the feature was merged into the core, the C, D, E,... appendices
of the OpenGL spec will tell you in which version the merge took place.
You'll need to remember that version number later (when you write glaf_FillContextxxxxx).
-
At the "Extensions Registry", locate which enumerants (if any) need to
be supported, and look for their numeric value at gl.h or glext.h. Hopefully,
the same numeric value should appear even when extensions are moved to
core features. If the numeric values change, it may be necessary to study
the case more deeply, and perhaps develop an alternative approach, as described
in "I-INTRODUCTION", above.
-
Create the new GLAF enumerants (if any), putting them in "glaf.h". The
naming convention is to substitute the GL_ preffix by GLAF_ , and to cut
any extension suffix. The enumerants numeric values *must* match those
of gl.h and glext.h.
-
At the "Extensions Registry", locate which functions (if any) need to be
supported, and make sure that their functionality and arguments meaning
was never modified. Otherwise, you may need to follow one of the strategies
described in "I-INTRODUCTION", above.
-
Create typedefs for the new 'pointers to functions' (if any) in "contexts.h".
-
Create the new pointers (if any) in the GLAFST_context structure in "contexts.h".
-
If you didn't create new pointers to functions, you'll need to add a GLboolean
variable in the GLAFST_context structure in order to cache the feature
availability (if you created pointers to functions, you can set them to
a NULL value for caching 'unavailability' of the feature, so you don't
need a boolean variable in that case). Look at the existing GLAFST_context
structure in order to see how other features were introduced.
-
Create three new files, named "xxxxx.c", "xxxxxP.c", and "xxxxxP.h", where
'xxxxx' is the name of the new feature.
-
Time for hot chocolate and a donut. Alternatively, a capuccino and a croissant
may suffice.
-
Let's write into "xxxxxP.c". Look at "abgrP.c", since it's a simple example.
Your "xxxxxP.c" file should include both "private.h" and "xxxxxP.h". You
need to write a function: void _glaf_FillContextxxxxx(struct GLAFST_context
*ctx), where xxxxx is the name of the feature. This will be perhaps the
most important function for the support of the new feature. This function
is responsible for deciding whether a feature is available or unavailable,
and for choosing which extension or core functionality to use. For making
these decisions, you can call glaf_IsOpenGLExtensionSupported() and glaf_OpenGLVersion().
In "abgrP.c" you'll see a simple choosing mechanism. For a more complicated
one, see the "convolutionP.c" file. For a mega-complicated one see "blending_extensionsP.c".
Your _glaf_FillContextxxxxx() function must initialize *all* pointers and
booleans that you created in previous steps. For initializing the pointers
to functions, call _glaf_LookupFunction(). If the feature is not available,
set *all* the pointers to NULL.
-
While you're writing _glaf_FillContextxxxxx(), call _glaf_Verbose() with
the proper message strings depending on the availability of the feature
(see the mentioned "xxxxxP.c" example files and follow them).
-
You can create any convenience functions you wish in "xxxxxP.c". For example,
open "convolutionP.c" and you'll see functions for filling several pointers
to functions in a single call. An interesting function in that file is
_glaf_TestContextConvolution, because it tests all pointers of the feature
in order to make sure that all of them are non-NULL (this function is used
during initialization only, in order to be confident that all the functions
are *really* supported by the implementation).
-
No matter if you create your convenience functions or not, you should always
check for the validity of the returned pointers. If pointers are not valid,
this has priority over what both the extensions string and the OpenGL version
say about what the implementation theoretically supports.
-
Update your "xxxxxP.h" file accordingly. See "abgrP.h" and "convolutionP.h"
as examples. Use the "#ifndef __xxxxxP_h__" trick for avoiding multiple
inclusion.
-
Now open "contexts.c", and add a '#include "xxxxxP.h"'.
-
In "contexts.c", scroll down and go to the _glaf_FillContextAllData() function.
Just before the 'return', insert a call your new _glaf_FillContextxxxxx()
function.
-
Now it's time to write "xxxxx.c". This is the public API of your new feature.
Add a #include "public.h" at the beginning of the file. You also need to
create a "GLboolean glaf_IsxxxxxSupported(void)" function that will return
GL_TRUE or GL_FALSE depending on whether the feature is available or not.
For checking the availability, the function should read the cached pointers
or the boolean initialized by your _glaf_FillContextxxxxx() function. Those
pointers/booleans must be accessed through "GLB_current->" (which always
points to the current context). In the case of pointers, just one needs
to be checked,because your _glaf_FillContextxxxxx() should set all of them
to NULL if the feature is not available. So you don't need to check all
pointers, and that's a fast test.
-
In "xxxxx.c", add the rest of your public API (the functions that will
call to the real OpenGL extension functions -if any-). The ideal coding
technique is to simply do a "GLB_current->glWhatEverPtr(blah, blah,...);".
See "convolution.c" as an example. If your feature is enumerants-based
only, the "xxxxx.c" file will contain just the glaf_IsxxxxxSupported()
function (the public API will be made of that function only, as in "abgr.c").
-
In "glaf.h", add the prototypes for all functions in "xxxxx.c", including
the glaf_IsxxxxxSupported() function.
-
Add the "xxxxx.c" and "xxxxxP.c" files to the SRCS variable of the Makefile,
and the "xxxxxP.h" file to the HDRS variable.
-
Do a 'make depend'. If you followed all previous steps and there're no
errors in your code, the dependencies file ('dep') will be updated.
-
That's all! Now you can build your extended GLAF just doing a 'make'.
-
If you followed all the steps, your new feature will be automatically initialized
when you relink your applications with your new version. If you do a verbose
initialization of a GLAF context, you'll see your new verbose messages
in the terminal!
|