Simple OpenGL program

In this tutorial we will build an OpenGL program to use as a basis for the rest of our projects. This initially small program will evolve into a complete basis for game development.

So let's start with the basics.

Our project basis is currently Windows and Visual C++ 2008 Express. You can download the whole Visual Studio 2008 Express from Microsoft for free. I prefer it because it assists in the management of the project. You do not have to learn how to build makefiles and so you can focus in the real job and has no problems operating under Windows. However you can use any platform you feel comfortable.

Initial application

To begin with we create a Win32 project and a Windows application with Visual C++. Click Ok and then compile and run the result application.

Now that we have a running program we start modifying it to make it simpler. After we get rid of the unwanted stuff we add the OpenGL stuff. Actually in order to make an OpenGL program the steps we have to take are a few and simple.

First we have to create the main window which will have a special client area capable of accepting OpenGL rendering. This is done in the 'create_GL_window' function. It replaces the traditional CreateWindow function of the Windows API in our InitInstance function and hides all the details. We have to take a look at it to see what is happening.

The first thing we do is create the window as we would in any windows application

HWND create_GL_window (int wid, int hei, int bitsPerPixel, const char* title, HINSTANCE hInstance, const char* classname, int stencilBuffer)
{
	DWORD windowStyle = WS_OVERLAPPEDWINDOW; // define our window style
	DWORD windowExtendedStyle = WS_EX_APPWINDOW; // define the window's extended style
				
	int width = wid;
	int height = hei;
				
	RECT windowRect = {0, 0, width, height}; // define our window coordinates
				
	// adjust window, account for window borders
	AdjustWindowRectEx (&windowRect, windowStyle, 0, windowExtendedStyle);
				
	// create the opengl window
	hWnd = CreateWindowEx (windowExtendedStyle, // extended style
				classname, // class name
				title, // window title
				windowStyle, // window style
				0, 0, // window x, y position
				windowRect.right - windowRect.left, // window width
				windowRect.bottom - windowRect.top, // window height
				HWND_DESKTOP, // desktop is window's parent
				0, // no menu
				hInstance, // pass the window instance
				0);
				
	if (hWnd == 0) // was window creation a success?
	{
		return hWnd; // if not return false
	}

If everything is OK we proceed with the creation of the drawing surface within the window.

	hDC = GetDC(hWnd); // grab a device context for this window
	if (hDC == 0) // did we get a device context?
	{
		// Failed
		DestroyWindow (hWnd); // destroy the window
		hWnd = 0; // zero the window handle
		return hWnd; // return false
	}
				
	PIXELFORMATDESCRIPTOR pfd = // pfd tells windows how we want things to be
	{
		sizeof (PIXELFORMATDESCRIPTOR), // size Of this pixel format descriptor
		1, // version number
		PFD_DRAW_TO_WINDOW | // format must support window
		PFD_SUPPORT_OPENGL | // format must support opengl
		PFD_DOUBLEBUFFER, // must support double buffering
		PFD_TYPE_RGBA, // request an rgba format
		bitsPerPixel, // select our color depth
		0, 0, 0, 0, 0, 0, // color bits ignored
		0, // no alpha buffer
		0, // shift bit ignored
		0, // no accumulation buffer
		0, 0, 0, 0, // accumulation bits ignored
		16, // 16bit z-buffer (depth buffer)
		stencilBuffer, // stencil buffer
		0, // no auxiliary buffer
		PFD_MAIN_PLANE, // main drawing layer
		0, // reserved
		0, 0, 0 // layer masks ignored
	};
				
	GLuint PixelFormat = ChoosePixelFormat (hDC, &pfd); // find a compatible pixel format
	if (PixelFormat == 0) // did we find a compatible format?
	{
		// Failed
		ReleaseDC (hWnd, hDC); // release our device context
		hDC = 0; // zero the device context
		DestroyWindow (hWnd); // destroy the window
		hWnd = 0; // zero the window handle
		return hWnd; // return false
	}
	
	if (SetPixelFormat (hDC, PixelFormat, &pfd) == false)// try to set the pixel format
	{
		// Failed
		ReleaseDC (hWnd, hDC); // release our device context
		hDC = 0; // zero the device context
		DestroyWindow (hWnd); // destroy the window
		hWnd = 0; // zero the window handle
		return hWnd; // return false
	}
	
	hRC = wglCreateContext (hDC); // try to get a rendering context
	if (hRC == 0) // did we get a rendering context?
	{
		// Failed
		ReleaseDC (hWnd, hDC); // release our device context
		hDC = 0; // zero the device context
		DestroyWindow (hWnd); // destroy the window
		hWnd = 0; // zero the window handle
		return hWnd; // return false
	}
	
	// make the rendering context our current rendering context
	if (wglMakeCurrent (hDC, hRC) == false) // failed
	{
		wglDeleteContext (hRC); // delete the rendering context
		hRC = 0; // zero the rendering context
		ReleaseDC (hWnd, hDC); // release our device context
		hDC = 0; // zero the device context
		DestroyWindow (hWnd); // destroy the Window
		hWnd = 0; // zero the window handle
		return hWnd; // return false
	}

Things are looking good, show the window

	ShowWindow (hWnd, SW_NORMAL); // make the window visible
	isMinimized = false; // set isMinimized to false
	
	resize_window (width, height); // reshape our gl window
	UpdateWindow(hWnd);
	
	return hWnd; // window creating was a success
}

Now we are ready to enter the main loop of our game. In our case the loop has to cooperate with the OS. When the OS is not sending us any messages to process we simply loop with every frame.

	// Main message loop:
	bool bLooping = true;
	while (bLooping)
	{
		// check for windows message and precess them
		if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) != 0)
		{
			if (msg.message == WM_QUIT)
				bLooping = false;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else // no messages, just loop for next frame
		{
			if (isMinimized) // if window is minimized
			{
				WaitMessage (); // yeld back to system, do not waste processing power
			}
			else
			{
				frame_render(); // render scene
				SwapBuffers (hDC); // Swap Buffers (Double Buffering)
			}
		}
	}

The only thing we have to do upon exit is to clean up the OpenGL mess we created

destroy_GL_window();

In the code you will see two functions. First is the 'resize_window'. This is called every time the window size changes to update the scaling in OpenGL system. The other one is 'frame_render' which is called in every loop in order to draw our scene. You should notice that after the call to 'frame_render' in the main loop we call 'SwapBuffers'. This is the key to seamless animation. We actually have two drawing surfaces. One is displayed and we draw on the other. When we are done drawing we swap them and start drawing the next frame.