Menu
OpenGL Tutorials

OpenGL

OpenGL Window



Tutorials > OpenGL > OpenGL Window

View Full Source

Introduction

OpenGL Window Welcome to one of the largest tutorials on this website.

If you are following the OpenGL tutorials using GLUT, please skip this tutorial and simply download the GLUT source files at the bottom of the page.

If you are following these tutorials using Win32, get yourself a cup of coffee and get ready for the long haul. This tutorial will assume that you have read through the first 8 Win32 tutorials.

In this tutorial, we will create the code that will form the basecode of every future tutorial. If you feel that a particular part of the basecode should be changed, please let me know.

The basecode has been created in an object-oriented fashion. A number of people have said that this could be slow and it is better to take a C style approach. I have tested this basecode thoroughly against its C style (not object-oriented) counterpart and have found that there is no performance penalty.

To simplify the code for every tutorial, the basecode will be placed in separate files, glutil.h and glutil.cpp.

We will first have a look at our header file.

Contents of glutil.h :


#ifndef GLUTIL_H

#define GLUTIL_H

The first important step is to include the libraries that your application will use. This was seen in the previous tutorial. Both the opengl32.lib and glu32.lib library files are needed.

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

The next step is to include the appropriate header files. The windows.h file is included to allow a majority of the Win32 functions to be called. The gl/gl.h and gl/glu.h files are included to allow OpenGL and OpenGL utility functions to be called. Note that the windows.h file should be included before the OpenGL header files. The stdio.h file is used further in the tutorial to display error messages.

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <stdio.h>

The window for our tutorials will be housed within an OpenGL singleton.

class OpenGL
{

public :

A static create method needs to be created to return a pointer to the singleton. This is done as we only ever want one OpenGL window. Most variables and methods will be declared as static because of this.

The create method simply allocates memory for the OpenGL class. Note that this memory needs to be reclaimed. The destructor therefore reclaims this memory when the OpenGL object is removed from scope, generally when the program terminates.

	static OpenGL *create()
	{ return instance = new OpenGL; }
	~OpenGL() { if (instance) delete instance; }

Once we have instantiated an instance of our OpenGL class, an initGL method needs to be called to setup the window. This function will accept the title that will be displayed at the top of the window. The other parameters include the width, the height, the bits per pixel and whether the window must be displayed in fullscreen mode.

	bool initGL(char *t, int w = 640, int h = 480, 
	bool full = false, int bpp = 32);

There needs to be some way of swapping between windowed and fullscreen mode. A changeDisplaySettings method will allow us to change the settings passed into initGL above.

	bool changeDisplaySettings(int w, int h, bool full, int bpp);

As was seen in the Win32 tutorials, an event loop needs to be entered. A method is therefore required to enter the loop and a method is required to end the loop. A finished variable is used to determine when to exit the loop.

	void runMessageLoop();
	void endMessageLoop() const { finished = true; }

Instead of continually calling the MessageBox function everytime an error occurs, we create our own displayError method.

	void displayError(char *title);

A number of function pointers will be placed in this class. This is to determine the OpenGL initialization, display, idle and resize function of the window. Their use will become more evident below.

	void setInitFunc(bool (*i)())
	{ init = i; }
	void setDisplayFunc(void (*d)())
	{ display = d; }
	void setIdleFunc(void (*i)())
	{ idle = i; }
	void setResizeFunc(void (*r)(int w, int h))
	{ resize = r; }

Other methods need to be created to retrieve data in private variables. The private variables will be discussed below.

	HWND getHWND() const
	{ return hwnd; }

	bool isKeyDown(int key) const
	{ return keys[key]; }
	void keyUp(int key)
	{ keys[key] = false; }
	
	bool isMouseDownL() const
	{ return mouseDownL; }

	int getMouseX() const
	{ return mouseX; }
	int getMouseY() const
	{ return mouseY; }
	
	int getWidth() const { return width; }
	int getHeight() const { return height; }

Below are our private variables and methods. Note that our OpenGL constructor is private as we only want it to be called within our object.

private :

	OpenGL() {}

A killGL method is created to allow our window to be destroyed.

	void killGL();

As with all Win32 programs, a WndProc event handling function needs to be created.

	static LRESULT WndProc(HWND hwnd, UINT msg, 
						WPARAM wParam, LPARAM lParam);

Our private variables are shown below. Most are self explanatory. A keys boolean array of size 256 has been created. This will hold a single boolean value for each key on the keyboard to determine if it has been pressed.

	static OpenGL *instance;

	static char *title;
	static int width;
	static int height;
	static int bits;
	static bool fullscreen;

	static bool active;
	static bool finished;
	static bool keys[256];
	static int mouseX;
	static int mouseY;
	static bool mouseDownL;

Below are our handles. The HDC is a handle to a device context. This allows the graphical contents of a window to be modified. An HGLRC is a handle to an OpenGL Rendering Context. This connects OpenGL to the device context, therefore allowing OpenGL to draw to the window.

	static HDC hdc;
	static HWND hwnd;
	static HGLRC hrc;

Below are our 4 function pointers that were explained above. The init function will deal with any OpenGL initialization code that we want to incorporate into our application. This will execute when the application starts and when the display settings are changed. The display function will contain all code used to render a single frame of your OpenGL application. The idle function will contain extra processing that needs to be done every frame. The resize function will be called each time our window is resized.

	static bool (*init)();
	static void (*display)();
	static void (*idle)();
	static void (*resize)(int w, int h);
};

#endif

Now that we have defined what our class will consist of, we need to implement these methods.

Contents of glutil.cpp :


#include "glutil.h"

The first step is to initialize all variables.

OpenGL *OpenGL::instance = NULL;

char *OpenGL::title = "";
int OpenGL::width = 640;
int OpenGL::height = 480;
int OpenGL::bits = 16;
bool OpenGL::fullscreen = false;

bool OpenGL::active = false;
bool OpenGL::finished = false;
bool OpenGL::keys[256];
int OpenGL::mouseX = 0;
int OpenGL::mouseY = 0;
bool OpenGL::mouseDownL = false;

HDC OpenGL::hdc = NULL;
HWND OpenGL::hwnd = NULL;
HGLRC OpenGL::hrc = NULL;

bool (*OpenGL::init)() = NULL;
void (*OpenGL::display)() = NULL;
void (*OpenGL::idle)() = NULL;
void (*OpenGL::resize)(int w, int h) = NULL;

Our initGL method follows. Each parameter passed is assigned to the classes' member variables.

bool OpenGL::initGL(char *t, int w, int h, bool full, int bpp)
{
	title = t;
	width = w;
	height = h;
	fullscreen = full;
	bits = bpp;

The instance of the application is retrieved. This function was covered in the Creating Windows tutorial.

	HINSTANCE hInstance = GetModuleHandle(NULL);

A variable is created for our window class. An unsigned int is also created to store our pixel format which will be shown at a later stage.

	WNDCLASSEX wc;
	unsigned int pixelFormat;

The next variable will be used to store our window style.

	DWORD style;

The next piece of code can be understood by visiting the Creating Windows tutorial. Note that a unique device context is created for every window. This is specified by the CS_OWNDC flag. The name of the window class we will use is "ZeusCMD".

	wc.cbSize = sizeof(wc);
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc = (WNDPROC)WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
	wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName = NULL;
	wc.lpszClassName = "ZeusCMD";

The window class is registered as per normal. Note the first use of our displayError method.

	if (!RegisterClassEx(&wc))
	{
		displayError("Registering Window Class");
		return false;
	}

A new topic we will now discuss is how to change display settings of the window. If the window is to be run in fullscreen mode, a change in display settings is needed.

	if (fullscreen)
	{

The functions we will use to change display settings require the use of a DEVMODE structure. We will only cover the properties of the DEVMODE that are necessary for our OpenGL window.

		DEVMODE displaySettings;

It is important to clear the contents of the structure. The only properties we will set are the width, height and bits per pixel properties.

		memset(&displaySettings, 0, sizeof(displaySettings));
		displaySettings.dmSize = sizeof(displaySettings);
		displaySettings.dmPelsWidth = width;
		displaySettings.dmPelsHeight = height;
		displaySettings.dmBitsPerPel = bits;

As there are a number of properties that we have not used, we need to specify which ones we have used. This is achieved by assigning particular flags to the dmFields property. These flags have a prefix of DM_*. The DM_PELSWIDTH, DM_PELSHEIGHT and DM_BITSPERPEL flags are used.

		displaySettings.dmFields = 
			DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

Now that we have populated our structure with the necessary settings, we can change the display settings. This is achieved by using the ChangeDisplaySettings function. The function accepts a DEVMODE structure to be passed such as the one created above. The second parameter accepts different flags depending on the operation that is wanting to be accomplished. Some of the commonly used flags are shown below :

Flag Description
NULL The graphics mode will be changed dynamically.
CDS_FULLSCREEN Switch to fullscreen mode.
CDS_RESET The display settings should be changed even if the required settings are the same as the current ones.
CDS_TEST The system tests if the display mode can be set.

The function returns a single flag indicating the result of the operation. A DISP_CHANGE_SUCCESSFUL flag is returned if the operation was completed successfully. Any other flag indicates an error such as DISP_CHANGE_FAILED, DISP_CHANGE_BADFLAGS or DISP_CHANGE_BADMODE.

		if (ChangeDisplaySettings(&displaySettings, 
			CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		{

If any error occurs, we display an error and assign false to our fullscreen variable. The window being created must be cancelled. This can be done by calling killGL as this function reallocates all previously allocated memory.

			displayError("Cannot change to requested display mode");
			fullscreen = false;
			killGL();
			return false;
		}
	}

If we are showing the window in fullscreen mode, we use the WS_POPUP window style. This is ideal as the window does not have any title bar or borders. The mouse cursor is also removed. This is done as usually you would want to create your own cursor for your applications. The cursor can be hidden by calling ShowCursor. This function accepts one boolean variable indicating whether the cursor should be hidden or visible.

	if (fullscreen)
	{
		style = WS_POPUP;
		ShowCursor(false);
	}

If we are displaying our program within a window, the WS_OVERLAPPEDWINDOW flag is used. This style provides many features along with a title bar, minimize box, maximize box and system menu.

	else
	{
		style = WS_OVERLAPPEDWINDOW;
	}

One problem when creating an OpenGL window is that the window size includes the title bar and border. If we want a 500 x 500 window, we want to have a rendering area of 500 x 500. It is therefore necessary to make a call to AdjustWindowRectEx. This calculates the required size of the window rectangle, based on the desired size of the client rectangle.

The first step is to fill out a RECT structure with the width and height of the desired rendering area.

	RECT rect;
	rect.left = 0; rect.top = 0;
	rect.right = width; rect.bottom = height;

A call is then made to AdjustWindowRectEx. This function accepts 4 parameters. The first specifies which rectangle will be modified. The second parameter indicates what style the window used. The third parameter specifies whether a menu is included in the window and can have a value of TRUE or FALSE. The final parameter specifies any extended style that the window has. As we are not using an extended style, we simply pass 0 for this parameter.

	AdjustWindowRectEx(&rect, style, FALSE, 0);

The creation of the window is shown in the Creating Windows tutorial. We use a WS_EX_APPWINDOW flag to force a top-level window to be created. This prevents other windows from appearing in front of it. The WS_CLIPSIBLINGS and WS_CLIPCHILDREN styles are also used to prevent drawing (clipping) in places occupied by child windows. Children will also clip each other properly. Note how the width and height of the window is determined. This simply finds the differences between the various sides of the rectangle defined above.

	if (!(hwnd = CreateWindowEx(
		WS_EX_APPWINDOW,
		"ZeusCMD",
		title,
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN | style,
		0, 0,
		rect.right - rect.left,
		rect.bottom - rect.top,
		NULL,
		NULL,
		hInstance,
		NULL)))
	{
		killGL();
		displayError("Creating Window");
		return false;
	}

Another new topic is that of a PIXELFORMATDESCRIPTOR structure. This structure specifies the data stored for every pixel within your window. A number of buffers are made available by OpenGL. Each buffer requires additional memory to be stored per pixel. The contents of this structure will change slightly throughout the tutorials. This will be made clear at the time.

Each property of the structure is described below :

	PIXELFORMATDESCRIPTOR pfd = 
	{

WORD nSize - This specifies the size of the data structure and should always be set to the size of the structure using the sizeof function.

		sizeof(pfd),

WORD nVersion - This specifies the version of this structure. The current version is 1.

			1,

DWORD dwFlags - This specifies properties of the pixel buffer. A number of commonly used flags are shown below :

Flag Description
PFD_DRAW_TO_WINDOW The buffer can draw to a window or device surface.
PFD_DRAW_TO_BITMAP The buffer can draw to a memory bitmap.
PFD_SUPPORT_OPENGL The buffer supports OpenGL drawing.
PFD_DOUBLEBUFFER The buffer is double-buffered. There are essentially 2 buffers.

We want the buffer to be able to draw to a window and we want it to support OpenGL drawing. The PFD_DRAW_TO_WINDOW and PFD_SUPPORT_OPENGL are therefore used. We also want to have a double buffer and we therefore include the PFD_DOUBLEBUFFER flag.

Whenever you display a scene on the screen, a number of frames are rendered. Many frames are rendered every second. The buffers are used to generate these frames. The problem is that if you write to a buffer while displaying it, you will receive an image that appears to flash rapidly. The process of double buffering helps here. There are 2 buffers which are written to. While you write to back buffer, the front buffer is being displayed. Once you have finished writing to the back buffer, the buffers are swapped therefore displaying the buffer you have just written to. This prevents the flashing effect, therefore making it necessary to utilize a double buffer.

			PFD_DRAW_TO_WINDOW | 
				PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,

BYTE iPixelType - This specifies the type of pixel data. This can either have the value of PFD_TYPE_RGBA or PFD_TYPE_COLORINDEX depending on whether you are wanting to specify color values as red, green, blue, alpha values or if you are wanting to use a color palette. We will be using the RGBA color mode.

			PFD_TYPE_RGBA,

BYTE cColorBits - This specifies the number of color bitplanes in each color buffer. This represents our bits per pixel which we already have stored.

			bits,

BYTE c Red/Green/Blue/Alpha Bits and BYTE c Red/Green/Blue/Alpha Shift - This the number and shift count for each separate color plane. These are not use and a 0 should therefore be passed.

			0, 0, 0, 0, 0, 0, // Color bits
			0, 0,             // Alpha bits

BYTE cAccumBits and BYTE cAccum Red/Green/Blue/Alpha Bits - The cAccumBits property specifies the number of bitplanes in the accumulation buffer where the other properties specify the number of bitplanes for each color. The accumulation buffer will be explained in a future tutorial. These will not be used for now.

			0, 0, 0, 0, 0,    // Accumulation bits

BYTE cDepthBits - This specifies the number of bits (depth) of the depth buffer. The depth buffer will not be used in the first few tutorials but it will be used in early tutorials. We will therefore assign 16 bits to the depth buffer. The depth buffer is essentially used to determine which objects appear in front or behind one another.

			16,               // Depth buffer

BYTE cStencilBits - This specifies the depth of the stencil buffer. This buffer will be discussed in a future tutorial and will therefore not be used for now.

			0,                // Stencil buffer

BYTE cAuxillaryBuffers - This specifies the number of auxillary buffers. These buffers are not supported and therefore 0 is passed.

			0,                // Auxillary buffer

BYTE iLayerType - The previous flag that was generally passed here was PFD_MAIN_PLANE. Earlier implementations of OpenGL used this but it is now ignored.

			PFD_MAIN_PLANE,

DWORD dw Layer/Visible/Damage Mask - The layer mask is ignored in current implementations of OpenGL. The visible mask specifies the transparent color or index of an underlay plane. The damage mask is also ignored as it is no longer used.

			0,                // reserved
			0, 0, 0           // Layer masks
	};

Now that we have set up our pixel format, we need to setup our device and rendering contexts.

We first need to retrieve the device context for our window. This is achieved by calling GetDC. This function accepts the handle to our window and returns a handle to the device context. All functions shown below are checked for errors.

	if (!(hdc = GetDC(hwnd)))
	{
		killGL();
		displayError("Retrieving DC");
		return false;
	}

The next step is to set our pixel format. Before we can set our pixel format, we need to allow windows to choose a format that matches our descriptor the most. Some values we supplied may not be supported. This is done by calling the ChoosePixelFormat function. This function accepts the handle to our device context as the first parameter and a pointer to our PIXELFORMATDESCRIPTOR structure as the second parameter. An integer is returned which specifies the selected pixel format.

	if(!(pixelFormat = ChoosePixelFormat(hdc, &pfd)))
	{
		killGL();
		displayError("Choosing Pixel Format");
		return false;
	}

Now that we have a supported pixel format, we need to set the format. This is done with a call to SetPixelFormat. This function accepts the handle to the device context as the first parameter, the integer generated above as the second parameter, and a pointer to our descriptor as the third parameter.

	if (!SetPixelFormat(hdc, pixelFormat, &pfd))
	{
		killGL();
		displayError("Setting Pixel Format");
		return false;
	}

Now that we have set our pixel format, we need to create an OpenGL rendering context to connect to the device context. This can be achieved by using the wglCreateContext function. This function accepts a handle to our device context and returns a handle to the rendering context.

	if (!(hrc = wglCreateContext(hdc)))
	{
		killGL();
		displayError("Creating Rendering Context");
		return false;
	}

Now that we have set up both our device and rendering contexts, we need to assign them to the current thread. This is done by calling wglMakeCurrent. This function accepts 2 parameters. The first parameter needs to be a handle to the device context and the second parameter needs to be a handle to the rendering context.

	if (!wglMakeCurrent(hdc, hrc))
	{
		killGL();
		displayError("Activating Rendering Context");
		return false;
	}

Once the window has been set up, it needs to be shown. This is done as we have seen in the Win32 tutorials.

	ShowWindow(hwnd, SW_SHOW);

Once the window is displayed, we want it to be given focus and we want the window to appear on top of all others. The window is brought forward by calling SetForegroundWindow and passing the handle to the window. The SetFocus function is called to set keyboard focus to the window. This function also accepts a handle to the window.

	SetForegroundWindow(hwnd);
	SetFocus(hwnd);

If a resize function has been assigned, now is a good time to resize the window.

	if (resize)
		resize(width, height);

Lastly, our initialization code is run. If any error occurs in the init function, false will be returned.

	if (init)
		return init();

	return true;
}

Our changeDisplaySettings method is given below.

bool OpenGL::changeDisplaySettings(int w, int h, bool full, int bpp)
{

Microsoft have indicated that calling the ChangeDisplaySettings on an old thread could cause problems such as icons being squashed. To avoid this, we first destroy our current window. Once this has been done, a new window will be created.

	killGL();

We store the current settings in case the new display mode is not supported.

	int oldWidth = width;
	int oldHeight = height;
	bool oldFull = fullscreen;
	int oldBpp = bits;

The next step is to initialize a new window with the new properties. If this fails, the old properties are used again.

	if (!initGL(title, w, h, full, bpp))
	{
		initGL(title, oldWidth, oldHeight, oldFull, oldBpp);
		return false;
	}

As we destroyed the OpenGL window above, we need to call the init function again.

	if (init)
		return init();

	return true;
}

The killGL method is provided below.

void OpenGL::killGL()
{

If we are in fullscreen mode, we want to return to normal mode. The default display mode is entered by passing NULL as the first parameter and 0 as the second. We also make the cursor visible again.

	if (fullscreen)
	{
		ChangeDisplaySettings(NULL, 0);
		ShowCursor(true);
	}

To deactivate the current rendering context, a call to wglMakeCurrent needs to be made. If NULL is passed as both parameters, this is achieved.

	if (hrc)
	{
		if (!wglMakeCurrent(NULL, NULL))
			displayError("Deactivating Rendering Context");

Now that the rendering context is deactivated, we can delete it by passing our handle onto the wglDeleteContext function.

		if (!wglDeleteContext(hrc))
			displayError("Deleting Rendering Context");

		hrc = NULL;
	}

We no longer require I device context and it should therefore be released to reclaim memory. A call to ReleaseDC is made. This function accepts the handle to the window as the first parameter and the handle to the device context as the second parameter.

	if (hdc && !ReleaseDC(hwnd, hdc))
		displayError("Releasing Device Context");

	hdc = NULL;

The window can now be safely destroyed by calling DestroyWindow.

	if (hwnd && !DestroyWindow(hwnd))
		displayError("Destroying Window");

	hwnd = NULL;

The last step is to unregister our window class so that a new class can be registered when calling initGL.

	if (!UnregisterClass("ZeusCMD", GetModuleHandle(NULL)))
		displayError("Unregistering Class");
}

We can now move onto our windows messaging method where all windows messages are processed. This is explained in the Message Loop tutorial.

LRESULT OpenGL::WndProc(HWND hwnd, UINT msg, 
						WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{

The hi-order word of the wParam variable when dealing with the WM_ACTIVATE message returns the current minimize state. A non-zero value indicates that the window is minimized. We do not want our window rendering when it is minimized.

	case WM_ACTIVATE :
		{
			active = HIWORD(wParam) ? false : true;

			return 0;
		}

Our close message closes the window as per normal.

	case WM_CLOSE :
		{
			PostQuitMessage(0);
			return 0;
		}

When dealing with the WM_SIZE message, the wParam variable specifies how the window is being resized eg. SIZE_MAXIMIZED, SIZE_MINIMIZED, SIZE_RESTORED, etc.

The lo-order word of lParam returns the new width of the window while the hi-order word of the lParam indicates the new height of the window. We call our resize function if one has been assigned.

	case WM_SIZE :
		{
			width = LOWORD(lParam);
			height = HIWORD(lParam);
			
			if (resize)
				resize(LOWORD(lParam), HIWORD(lParam));

			return 0;
		}

Our WM_KEYDOWN and WM_KEYUP messages are shown below. They simply modify the value of the key within the keys array. This is explained further in the Keyboard Input tutorial.

	case WM_KEYDOWN :
		{
			keys[wParam] = true;
			return 0;
		}

	case WM_KEYUP :
		{
			keys[wParam] = false;
			return 0;
		}

We want to keep track of our mouse position and whether the mouse button is down. This is shown in the Mouse Input tutorial. The y position of the mouse is subtracted from the height as OpenGL specifies y coordinates as increasing when moving to the top of the screen, unlike the standard mouse position returned.

	case WM_MOUSEMOVE :
		{
			mouseX = LOWORD(lParam);
			mouseY = height - (HIWORD(lParam) + 1);

			return 0;
		}

	case WM_LBUTTONDOWN : mouseDownL = true; return 0;
	case WM_LBUTTONUP : mouseDownL = false; return 0;

	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

Below is our displayError function. This function is useful as it displays any system errors that are given. This is sometimes more useful than your own messages. Sometimes, strange messages will be displayed but you will still be able to see where this message occurred from the title character string that has been passed.

void OpenGL::displayError(char *title)
{

We first need a buffer to store our message.

	LPVOID lpMsgBuf;

The FormatMessage function below is used to format a message obviously.

	FormatMessage( 

The FORMAT_MESSAGE_ALLOCATE_BUFFER flag indicates that our buffer does not already have allocated memory and therefore memory should be allocated.

		FORMAT_MESSAGE_ALLOCATE_BUFFER | 

The FORMAT_MESSAGE_FROM_SYSTEM specifies that the message should be found in the system message table resources.

		FORMAT_MESSAGE_FROM_SYSTEM | 

The FORMAT_MESSAGE_IGNORE_INSERTS flag ignores insert sequences and passes them directly onto the output buffer.

		FORMAT_MESSAGE_IGNORE_INSERTS,

The next flag is not used.

		NULL,

The next parameter uses the GetLastError function to retrieve the last received error code.

		GetLastError(),

The 0 below indicates that the default language should be used.

		0,

Our buffer is passed next.

		(LPTSTR) &lpMsgBuf,

The next parameter specifies the maximum number of characters to use. As we have selected the allocate buffer flag, we can set this to 0.

		0,

the last parameter is for additional arguments that are not used.

		NULL 
		);

The actual message is now displayed. We must also make sure to free up memory allocated for the buffer.

	MessageBox(NULL, (LPCTSTR)lpMsgBuf, 
		title, MB_OK | MB_ICONERROR);

	LocalFree(lpMsgBuf);
}

Our last method to implement is our runMessageLoop method. This is explained in the Message Loop tutorial.

void OpenGL::runMessageLoop()
{
	MSG msg;

	while(!finished)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

If we are not minimized, we first execute our idle function for additional processing.

		if (active)
		{
			if (idle)
			{
				idle();

If the F1 key has been pressed, we first reset its entry in the array. This is done to prevent the key from being processed twice. The display settings are then changed depending on whether we are in fullscreen mode or not.

				if (keys[VK_F1])
				{
					keys[VK_F1] = false;

					fullscreen = !fullscreen;

					if (fullscreen)
						fullscreen = this->changeDisplaySettings(
							1024, 768, true, 32
						);
					else
						this->changeDisplaySettings(
							500, 500, false, 32
						);
				}

If the escape key is pressed, we simply end the message loop. We could have placed this code within our idle function but as these 2 keys will be used in every tutorial, it has been decided to leave them here to simplify the main file.

				else if (keys[VK_ESCAPE])
					this->endMessageLoop();
			}

Once the processing has been done, the back buffer is written to using our display function.

			display();

As was explained earlier, we need to swap the buffers after writing to the back buffer. This is achieved by calling the SwapBuffers function. This function accepts the handle to our device context.

			SwapBuffers(hdc);
		}
	}

Once we have finished our loop, we must destroy our OpenGL window.

	killGL();
}

Well done. The source files for ZeusCMD's OpenGL base code has now been created. All future tutorials will use the above base code. If you feel that anything should change, please let me know.

The main.cpp file will create a simple OpenGL window using the basecode.

Contents of main.cpp :


The first step in all of our programs is to include the glutil.h header file.

#include "glutil.h"

A pointer to the OpenGL class is created.

OpenGL *opengl;

All tutorials will contain an init function. This function will initialize any data that is required for the program and will set up any OpenGL parameters. Do not worry too much about the code displayed below. It simply clears the window with black. We will discuss this again in the next tutorial.

bool init()
{
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

	return true;
}

Every tutorial will also contain a display function. This function will contain all information that is rendered to the buffer. Once again, do not worry about the code in the function as this will be explained in the next tutorial.

void display()
{
	glClear(GL_COLOR_BUFFER_BIT);

	glFlush();
}

Our idle function has been placed here. This will be used for any additional processing per frame.

void idle()
{

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
		LPSTR lpCmdLine, int nShowCmd)
{

In our WinMain function, the first step is to instantiate an object of the OpenGL class using the create method.

	opengl = OpenGL::create();

We assign the init, display and idle to our object.

	opengl->setInitFunc(init);
	opengl->setDisplayFunc(display);
	opengl->setIdleFunc(idle);

A window can now be created with a simple call to initGL. The code below creates a 500x500 32-bit windowed window.

	if (!opengl->initGL("03 - OpenGL Window", 500, 500, false, 32))
		return 1;

We can now run the message loop and continue to run the program.

	opengl->runMessageLoop();

	return 0;
}

Congratulations. That was an extremely long tutorial. Hopefully you will now be able to create an OpenGL window using Win32. If you find that you have struggled greatly with the tutorial and you are battling to understand, consider using GLUT. If you understand most of the code, you should not need to worry too much as the rest of the tutorials will generally only deal with OpenGL code.

Please let me know of any comments you may have : Contact Me

Win32 Source Files : Visual Studio Dev-C++
GLUT Source Files : Visual Studio Dev-C++ Unix / Linux

Last Updated : 9 December 2005


< Tutorial 02 - Setting Up Your Environment Tutorial 04 - Rendering >

Back to Top


All Rights Reserved, © Zeus Communication, Multimedia & Development 2004-2005

Read the Disclaimer

Links