Background
I will try to keep things as much simple as possible but I must suppose you have at least a basic knowledge on how to write a Win32 application and a minimal knowledge of OpenGL programming in Win32 environment.
Introduction
In my opinion, one of the most complicated (and/or boring) things to do when writing a OpenGL application is to prepare the needed environment. Even if the setup process of a plain Win32 application which draws a 3D scene on a window using OpenGL is quite easy when compared to the same process done using Direct3D, it involves a lot steps that can create some trouble to beginners. This problem can be brilliantly solved using the OpenGL Utility Toolkit library (best known as GLUT) which allows to setup and run an OpenGL application, starting from a plain Win32 console application, writing some functions, and doing some library routine calls. However, even if GLUT is really cool, it does not fit all possible situations: it does not have all the flexibility of a "clean" Win32 application, and it cannot be used for commercial applications. For this reason, I've decided to write my own framework/library which allows to simplify the application setup process of my OpenGL applications and provide them with some other useful stuff.
Introducing the GLFW library
The OpenGL (simple) Framework or GLFW is a static library which can be linked to your Win32 application to simplify the process needed to create and setup a window suitable to render a 3D or 2D scene using OpenGL. The library also provides you other useful features such as:
- Support for multiple windows rendering on up to 10 different windows.
- Automatic import of entry points of all OpenGL core functions, from version 1.2 up to the current implementation version of your system.
- Utility functions to handle OpenGL extensions.
GLFW has been designed to be easy to use, and it is provided with a detailed documentation written in Compiled HTML format (CHM). This documentation provides the reference to all the \c glfwXXX()
routines, and a programming guide which explains how to use the library. I've also added some sample application code that can be found into the \Examples directory.
The library is still in BETA phase so, the development of some of the above features is still in progress (or they need a deep test/optimization session); however, I think it is already usable, at least as a demonstration of its features. To get the list of current limitations and problems of the library, see the "Known Issues" pages in the documentation file.
About import of OpenGL v1.2+ routines
As you probably know, the Microsoft support for OpenGL has been stopped on version 1.1. However, you still can access the OpenGL version 1.2 and above routines from your graphic cards drivers, by using a special function called wglGetProcAddress()
which is similar to the GetProcAddress()
routine used to get entry points of functions contained in DLL libraries. Due to the high number of routines, it is a common practice to retrieve only the entry points of the OpenGL routines that we know will be used (or that have a higher probability to be used) in our application. In my opinion, this is another boring issue, so I've decided to let the GLFW library retrieve all the routine entry points for you: when the first window is created, the library detects which OpenGL version is supported by your system and automatically retrieve all the routine pointers up to that version, and they will have the same name as the related routine (i.e., the name of pointer to the glWindowPos2i()
function will be glWindowPos2i
). These pointers are available to be accessed by the user simply by including the glfw.h header file. (They are declared as extern
inside the glfw_ext.h header which is included in glfw.h. Please do not include the glfw_ext.h header directly in your code!). Unsupported routine pointers and pointers to core OpenGL functions which belong to an OpenGL version that is not supported on your system are set to NULL
so, always check for NULL
-pointing conditions before use them to call the related function, or don't be surprised if your system crashes really often ;-)
GLFW tutorial
In this section, I will show an example usage of the GLFW: I will show you how to setup and run an OpenGL application using my library. The output generated will be a simple RGB-filled triangle in a black window; however, even if it is not really sophisticated, it will perfectly fit our requirement. The operation consists of various steps that we will analyze individually. Let's begin.
Step 1 - Create a Win32 project and include all the needed stuff
The first thing to do is to install the library. To do this, follow the instructions you will find in the "GLFW Installation" section of the library documentation. Once you have done that, create a new Win32 project (the procedure depends on the used compiler). The compiler will automatically generate a .cpp source file for you. Now, it is time to add all the needed stuff (libraries and header files). This can be done directly form the code:
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include "glfw.h"
#ifdef _DEBUG
#pragma comment ( lib, "glfwVC6d.lib" )
#else
#pragma comment ( lib, "glfwVC6r.lib" )
#endif
In the above code, we are using the Visual C++ 6.0 compiler and so we are linking the glfwVC6x.lib library variant. Notice that there is no link directive for the opengl32.lib and glu32.lib libraries, and there is no #include
directive for standard OpenGL include files (gl.h and glu.h). This is because the GLFW library automatically links the OpenGL library for you, and all the needed header files are included inside the glfw.h header.
Step 2 - Create the application skeleton
As you probably know, all Win32 applications which display windows need at least two routines: the WinMain()
function, which is the program entry point, and the Windows messages handler procedure. These two functions should be automatically created for you by the compiler when you have created the project. In all the cases, be sure that they look like the code below:
static LRESULT WINAPI MsgProc ( HWND hWnd, UINT iMsg,
WPARAM wParam, LPARAM lParam )
{
switch( iMsg )
{
case WM_DESTROY:
break;
case WM_SIZE:
break;
case WM_PAINT:
ValidateRect( hWnd, NULL );
break;
}
return DefWindowProc ( hWnd, iMsg, wParam, lParam );
}
INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, INT nCmdShow )
{
MSG sMsg;
while ( GetMessage( &sMsg, NULL, 0, 0 ) )
{
TranslateMessage( &sMsg );
DispatchMessage( &sMsg );
}
return 0;
}
If you take a look at the MsgProc()
function, you will notice that there are three messages handler cases in the switch statement: WM_DESTROY
, WM_SIZE
, and WM_PAINT
. These three messages are the ones we must necessarily take care of.
Step 3 - Create a window with GLFW
Now, we must add the code to create the window we will use to draw. This will be done using the routines provided by the GLFW library: the following is the new version of the WinMain()
routine, modified to add the window creation code:
INT APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, INT nCmdShow )
{
MSG sMsg;
glfwInit();
GLWF_WINPAR sWinPar;
GLFW_WCTXT *pWinCntx;
ZeroMemory( (PVOID)&sWinPar, sizeof( GLWF_WINPAR ) );
sWinPar.m_iPosX = 100;
sWinPar.m_iPosY = 100;
sWinPar.m_iWidth = 800;
sWinPar.m_iHeight = 600;
sWinPar.m_iBpp = GLFW_32BPP;
sWinPar.m_iZDepth = GLFW_16BPP;
sWinPar.m_szTitle = _T( "My 1st GLFW application." );
sWinPar.n_bFullScreen = FALSE;
sWinPar.m_fpWindProc = MsgProc;
glfwCreateWindow( &sWinPar, &pWinCntx );
glfwSetCurrWinContext( pWinCntx );
while ( GetMessage( &sMsg, NULL, 0, 0 ) )
{
TranslateMessage( &sMsg );
DispatchMessage( &sMsg );
}
return 0;
}
The above code will create a 800 x 600 x32 bpp blank window located on the screen, at position (100, 100). The first interesting thing to notice is the call to the glfwInit()
routine. This function will initialize the GLFW library before starting to work with it. Now comes the code to create the window: the function declares a GLWF_WINPAR
data structure (sWinPar
) and a pointer to a GLFW_WCTXT
data structure (pWinCntx
). The sWinPar
data structure is used to define the parameters of the window to be created: size, position, bit depths, title etc... Notice also that the pointer to the window message handler procedure MsgProc()
that we created before is used to set the value of the m_fpWindProc
field of the structure. Once the fields of this structure have been set with the desired values, it is time to call the glfwCreateWindow()
function. This routine takes as parameters the pointer to the sWinPar
structure and the pointer to the pWinCntx
data structure pointer (yes, a pointer to a pointer to a data structure). The pWinCntx
parameter will be used to return the pointer to the allocated Window Context to the caller. Once glfwCreateWindow()
is called, the window is created. The last thing we need to do is to set the returned Window Context as the currently active one. This is done by the glfwSetCurrWinContext()
routine call. Notice that in the above code sample, we have intentionally left out error checking. Remember to always check the values returned by the called glwfXXX()
functions in your applications, or they can crash without any apparent reason. The current result of our efforts can be seen in the following picture:
Step 4 - Handle window messages
Now is the time to modify the MsgProc()
routine, adding some code to handle the messages sent to our window. As told before, we must take care of at least the following three messages: WM_SIZE
, WM_PAINT
, and WM_DESTROY
. The first two messages are sent to our window when it is resized by the user and when it is time to redraw the scene, respectively. We will define the two functions to be called in these cases, but we will delay their implementation until the next step (as they involve OpenGL code). We will instead focus on the last message: WM_DESTROY
. This message is sent to our window when it is destroyed as the user has closed the application or because the glfwDestroyWindow()
routine has been called. In this case, we must release the allocated Window Context, and to do this, we must use the glfwDestroyContext()
routine. This function requires as argument the pointer to the Window Context to be released. This can be obtained from the window handler using the glfwGetWinContext()
routine or, if we know that the Window Context to be released is the currently active one, using the glfwGetCurrWinContext()
function. We will use the first way, and now we can update the code of our application:
static void ChangeWindowSize ( GLsizei iWidth, GLsizei iHeight )
{
}
static void RenderTheScene ( void )
{
}
static LRESULT WINAPI MsgProc ( HWND hWnd, UINT iMsg,
WPARAM wParam, LPARAM lParam )
{
GLFW_WCTXT *pContext;
pContext = glfwGetWinContext( hWnd );
switch( iMsg )
{
case WM_DESTROY:
glfwDestroyContext( pContext );
PostQuitMessage( 0 );
break;
case WM_SIZE:
ChangeWindowSize( LOWORD(lParam), HIWORD(lParam) );
break;
case WM_PAINT:
RenderTheScene();
SwapBuffers( pContext->m_hDC );
ValidateRect( pContext->m_hWin, NULL );
break;
}
return DefWindowProc( hWnd, iMsg, wParam, lParam );
}
Step 5 - Add OpenGL code
OK, now we have arrived at the fun part! We must write the code for the two routines we left blank before: ChangeWindowSize()
and RenderTheScene()
. Let's begin from ChangeWindowSize()
: this function will be called when the user resizes the window. Here it is necessary to reset the projection matrix and the view port:
#define NEAR_PLANE 1.0
#define FAR_PLANE 200.0
#define FIELD_OF_VIEW 60.0
static void ChangeWindowSize ( GLsizei iWidth, GLsizei iHeight )
{
GLfloat fAspectRatio;
if ( iHeight == 0 )
iHeight = 1;
glViewport( 0, 0, iWidth, iHeight );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
fAspectRatio = ( (GLfloat)iWidth ) / ( (GLfloat)iHeight );
gluPerspective( FIELD_OF_VIEW, fAspectRatio,
NEAR_PLANE, FAR_PLANE );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
gluLookAt( 0.0f, 0.0f, 80.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 );
}
Now, it's time to write the rendering code. To give a simple example, our RenderTheScene()
function will draw a colored triangle on the screen. Here is the code:
static void RenderTheScene ( void )
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glBegin( GL_TRIANGLES );
glColor3f( 1.0f, 0.0f, 0.0f );
glVertex3f( -25.0f, -25.0f, 0.0f );
glColor3f( 0.0f, 1.0f, 0.0f );
glVertex3f( 0.0f, 25.0f, 0.0f );
glColor3f( 0.0f, 0.0f, 1.0f );
glVertex3f( 25.0f, -25.0f, 0.0f );
glEnd();
glFlush();
}
The final thing to do is to add some code to setup the OpenGL environment in the WinMain()
routine, immediately after the call to the glfwSetCurrWinContext()
function:
...
glfwSetCurrWinContext( pWinCntx );
glShadeModel( GL_SMOOTH );
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
...
Step 6 - Have Fun!
Now our application is complete (and this tutorial is complete too). You can see the result in the picture below. You can now modify the code to handle some more Windows messages, and maybe add a timer to animate the scene by rotating the triangle. Notice that if you wish to see the application run in full screen mode instead of in windowed mode, just change the value used in the m_fpWindProc
field of the sWinPar
structure from FALSE
to TRUE
. More details on this last issue can be found in the documentation in the "Write a Full Screen Application" section.
The demo application
The demo application will show you a really simple example of multiple window rendering using the GLFW library. It will create two different windows, both showing a spinning cube. Due to its simplicity, I think that examining the code is better than seeing it run on your PC (in future, I will write a more complex and interesting demo bit; for now, I think this one is enough).
Future development
As I've already mentioned, the development of the library is not yet complete as there are still a lot of things I need/wish to do. Even if I do not have a definitive plan on the improvements to add in version 1.01.00R (the first to be considered as official "release"), it will surely contain the fixes to all the points listed in the "Known issues" section of the documentation. I think I will also add a minimal support for vector math (at least, a way to calculate surface normals), maybe using the SSE2 technology...
Conclusions
I wish to point out that my intent is not to write something better than the GLUT library: I just whish to provide to all newbie OpenGL programmers an alternative way to speed up the setting up of OpenGL applications, keeping in tact all the power and flexibility of a pure Win32 application. If you take a look at the code of all the core routines of the GLFW library, you will notice that they make use of glXXX()
, gluXXX()
, and (obviously) wglXXX()
functions, so you can consider GLFW as a sort of "High Level" wrapper for classic OS-related OpenGL routines. However, it also provides some other useful features (the list will increase in the next release of the library).
As I usually do in my job, I'm waiting for your feedback: you can contact me through the CodeProject comments system. Please send me all your doubts, questions, comments, suggestions, and bug reports.
Acknowledgements
A really, really big thank you to DoxyGen development group! You guys saved my life ;-)
Bibliography and web references
Here is the list of books I've used as reference during the development of the library:
- Shreiner, Woo, Neider, Davis: "OpenGL Programming Guide 5th Edition", Addison- Wesley.
- Shreider: "OpenGL Reference Manual, 4th Edition", Addison-Wesley.
- Wright, Lipchak: "OpenGL SuperBible, 3rd Edition", SAMS.
- Fosner: "OpenGL Programming for Windows 95 and Windows NT", Addison-Wesley.
- LaMothe: "Tricks of Windows Game Programming Gurus, 2nd Edition", SAMS.
Web Resources: