SIGGRAPH '97

Course 24: OpenGL and Window System Integration

OpenGL and Win32



A Simple Example

In order to use OpenGL with Win32 to render images, there are some initialization steps that must be taken. These steps are outlined below.

Creating a Window
Setting the Pixel Format
Creating a Rendering Context

Example source code:
simple.c



Create a Window

Before creating a window, a window class must be registered. A window class is a basic template that is used to create a window in an application. Every window is associated with a window class. To register a window class, a WNDCLASS structure is filled out with the desired settings and then the Win32 function RegisterWindowClass() is called with a pointer to this structure as an argument. Multiple windows can be associated with a single class. When the application that registered a window class exits, the window class is destroyed. A window class can be identified by its class name (a character string).

The window class contains the window procedure. A window procedure is a callback function that is used by Win32 to notify the application of messages that should be processed by the window. A window procedure must have the form: LONG WINAPI WindowProc(HWND, UINT, WPARAM, LPARAM). See the next section on messages for more information about window procedures.

The following code fragment shows how to register a new window class.
code fragment from oglCreateWindow() function in simple.c

/* oglCreateWindow
 *  Create a window suitable for OpenGL rendering
 */
HWND oglCreateWindow(char* title, int x, int y, int width, int height)
{
    WNDCLASS  wc;
    HWND      hWnd;
    HINSTANCE hInstance;

    /* get this modules instance */
    hInstance = GetModuleHandle(NULL);

    /* fill in the window class structure */
    wc.style         = 0;                           /* no special styles */
    wc.lpfnWndProc   = (WNDPROC)WindowProc;         /* event handler */
    wc.cbClsExtra    = 0;                           /* no extra class data */
    wc.cbWndExtra    = 0;                           /* no extra window data */
    wc.hInstance     = hInstance;                   /* instance */
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO); /* load a default icon */
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW); /* load a default cursor */
    wc.hbrBackground = NULL;                        /* redraw our own bg */
    wc.lpszMenuName  = NULL;                        /* no menu */
    wc.lpszClassName = title;                       /* use a special class */

    /* register the window class */
    if (!RegisterClass(&wc)) {
      MessageBox(NULL, 
		   "RegisterClass() failed:  Cannot register window class,",
		   "Error", MB_OK);
	return NULL;
    }

    . . .

}
Although the settings above should be sufficient for many applications, there are many values each field of the WNDCLASS structure can assume. For more information on the WNDCLASS structure and its options, see the Microsoft Developer Studio InfoViewer topic WNDCLASS.

Once a window class has been successfully registered, a new window can be created. When creating a window suitable for OpenGL rendering, the window style must have the WS_CLIPSIBLINGS and WS_CLIPCHILDREN attribute bits set.

The following code shows how to create a window.
code fragment from oglCreateWindow() function in simple.c

/* oglCreateWindow
 *  Create a window suitable for OpenGL rendering
 */
HWND oglCreateWindow(char* title, int x, int y, int width, int height)
{
    WNDCLASS  wc;
    HWND      hWnd;
    HINSTANCE hInstance;

    . . .

    /* create a window */
    hWnd = CreateWindow(title,          /* class */
			title,          /* title (caption) */
			WS_CLIPSIBLINGS | WS_CLIPCHILDREN,  /* style */
			x, y, width, height, /* dimensions */
			NULL,		/* no parent */
			NULL,		/* no menu */
			hInstance,	/* instance */
			NULL);		/* don't pass anything to WM_CREATE */

    /* make sure we got a window */
    if (hWnd == NULL) {
	MessageBox(NULL,
		   "CreateWindow() failed:  Cannot create a window.",
		   "Error", MB_OK);
	return NULL;
    }

    /* show the window (map it) */
    ShowWindow(hWnd, SW_SHOW);

    /* send an initial WM_PAINT message (expose) */
    UpdateWindow(hWnd);

    return hWnd;
}
A common style attribute which is used quite often (and bears mentioning here) is the WS_OVERLAPPEDWINDOW style. This creates a window that has resize handles and a system menu as well as the three icons (minimize, maximize and close) common to most Win32 windows in the upper right hand corner of the title (caption) bar. In the next section on messages, there are some example programs that use this style. Another style that can be used allows for the window to take up the whole screen. See the fullscrn.c program for an example of this style.

While in the example we only use the minimum style options necessary for OpenGL (WS_CLIPCHILDREN and WS_CLIPSIBLINGS), there are many options that can be used when creating a window. See the Microsoft Developer Studio InfoViewer topic CreateWindow for a list of all the available options.

After creating a new window it must be shown if the rendering is to be seen. It is also a good idea (though not strictly necessary) to force an initial paint by making a call to the window procedure in order to "prime the message pump". This is accomplished by calling the ShowWindow() and UpdateWindow() functions as shown in the example above.


Set the Pixel Format

After a window class has been registered and a new window has been successfully created, the pixel format must be set. The simplest way to set the pixel format is to use the ChoosePixelFormat() function. More sophisticated methods for choosing the pixel format will be discussed in a later section.

The pixel format specifies several properties of an OpenGL context. Common properties are depth of the Z buffer, whether a stencil buffer exists or not, whether the framebuffer is double buffered and many others.

In order to specify the many properties available, a PIXELFORMATDESCRIPTOR structure is employed. The members of this structure correspond to different properties. In order to set these properties, the corresponding field is set in the PIXELFORMATDESCRIPTOR structure and a format that best fits the criteria defined by the PIXELFORMATDESCRIPTOR structure is selected by the ChoosePixelFormat() function. The "best fit" is somewhat ambiguous and methods for finding exactly the pixel format desired are covered, as mentioned above, in a later section.

The following code fragment illustrates how to set the pixel format.
code defining the oglSetPixelFormat() function in simple.c

/* oglPixelFormat()
 *  Sets the pixel format for the context
 */
int oglSetPixelFormat(HDC hDC, BYTE type, DWORD flags)
{
    int pf;
    PIXELFORMATDESCRIPTOR pfd;

    /* fill in the pixel format descriptor */
    pfd.nSize        = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion     = 1;		    /* version (should be 1) */
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | /* draw to window (not bitmap) */
                       PFD_SUPPORT_OPENGL | /* draw using opengl */
                       flags;               /* user supplied flags */
    pfd.iPixelType   = type;                /* PFD_TYPE_RGBA or COLORINDEX */
    pfd.cColorBits   = 24;
    /* other criteria here */

    /* get the appropriate pixel format */
    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {
       MessageBox(NULL,
		  "ChoosePixelFormat() failed:  Cannot find format specified.",
		  "Error", MB_OK); 
       return 0;
    } 
 
    /* set the pixel format */
    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
	MessageBox(NULL,
		   "SetPixelFormat() failed:  Cannot set format specified.",
		   "Error", MB_OK);
        return 0;
    } 

    return pf;
}    
Note that type is one of PFD_TYPE_RGBA for non-paletted or PFD_COLORINDEX for paletted (indexed) display mode. flags is a bitwise OR (|) of several options. We'll use only PFD_DOUBLEBUFFER which selects a doublebuffered framebuffer for these simple examples. For more information on what other values it can assume, see the next section on pixel formats or the Microsoft Developer Studio InfoViewer topic PIXELFORMATDESCRIPTOR.


Create a Rendering Context

The final step in setting up for OpenGL rendering is to create the OpenGL context. An OpenGL rendering context in Win32 has the type HGLRC. All OpenGL rendering must go through an HGLRC. A context must be current for OpenGL calls to affect to it.

The procedure for creating and making a context current is shown below.
code from main() function in simple.c

/* main()
 *  Entry point
 */
int main(int argc, char** argv)
{
    HDC       hDC;			/* device context */
    HGLRC     hRC;			/* opengl context */
    HWND      hWnd;			/* window */

    . . .

    /* create an OpenGL context */
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);

    /* now we can start changing state & rendering */
    while (1) {
        /* rotate a triangle around */
        glClear(GL_COLOR_BUFFER_BIT);
	glRotatef(1.0, 0.0, 0.0, 1.0);
	glBegin(GL_TRIANGLES);
	glColor3f(1.0, 0.0, 0.0);
	glVertex2i( 0,  1);
	glColor3f(0.0, 1.0, 0.0);
	glVertex2i(-1, -1);
	glColor3f(0.0, 0.0, 1.0);
	glVertex2i( 1, -1);
	glEnd();
	glFlush();
	SwapBuffers(hDC);		/* nop if singlebuffered */
    }

    /* clean up */
    wglMakeCurrent(NULL, NULL);		/* make our context 'un-'current */
    ReleaseDC(hDC, hWnd);		/* release handle to DC */
    wglDeleteContext(hRC);		/* delete the rendering context */
    DestroyWindow(hWnd);		/* destroy the window */

    return 0;
}
After this is done, OpenGL calls can be made to change state and render to the context as shown in the example above. In order to clean up the resources allocated for OpenGL rendering, first make the HGLRC 'un'-current, release the HDC and delete the context. Lastly, destroy the window.