1A. WIN32 OpenGL 3.2 Window Creation

Tutorial Summary:

Welcome to tutorial 1A. Our goal is to create a foundation that supports cross-platform game engine code. If you are not interested in cross-platform capability, you can safely ignore the #ifdef / #endif statements for each platform.

To start off, we will get a window up and running, initialized with an OpenGL context. I’m using OpenGL 3.2, but these fundamentals apply to any version. Unlike DirectX, OpenGL has kept most of the same function and variable names since version 1. The big change starting at OpenGL 3 and DirectX 10 is the deprecation of the “Fixed Function Pipeline”. If you are new to graphics programming, forget I ever mentioned this term. If you are transitioning from an earlier graphics API and did not use shaders, unlearn all you have learned! Seriously, now everything is handled in shaders. Don’t worry, it’s a little steep in the beginning, but it gets easier!

WIN32 OpenGL Lesson Code and Explanation:

For this lesson we are going to create 2 classes, “SubVideoGL” and “Video”. The first class will initialize all OpenGL specific API functions and variables. The class itself will not contain any functionality for this first lesson, but humor me by getting used to its existence for now. The 2nd class will be the meat of the Renderer code that gets a window up, creates the OpenGL context, and renders!

The only hurdle to OpenGL is that initializing it really sucks! It’s quite a bit more complicated than getting a normal SDK and linking libraries. Because OpenGL is an “open standard”, there is no official SDK. But there is support in just about every platform imaginable! This works by getting the memory address of the OpenGL functions at run-time by querying the graphics card. First off, make sure to head on over to the OpenGL home page and download the relevant header files (gl.h, glext.h). I like to put all my header files in a single “GL” folder to include in my projects.

Let’s make “SubVideoGL.h” and “SubVideoGL.cpp” files and get to the initialization code!

SubVideo.h

#pragma once

#include "GL/gl.h"    //gl.h and glext.h from OpenGL site.
#include "GL/glext.h"

#ifdef _WIN32
    #define WGL_WGLEXT_PROTOTYPES
    #include "GL/wglext.h" //comes with Windows
#endif

First off, we use #pragma once because this is supported by all modern IDEs. The header file will only be included once in the project. Then we link the OpenGL header files (hopefully you got the latest versions from the OpenGL site). The #ifdef _WIN32 checks to see if this is a Windows environment. If you are working on Windows, it will automagically be defined for you. Make sure to #define WGL_WGLEXT_PROTOTYPES. This enables function calls up to OpenGL version 1.3. Windows OS stopped supporting OpenGL at this version because of the switch to DirectX. But have no fear, messy manual function prototyping is here! If you are only working on Windows, you can probably go ahead and use an “Extension Wrangler” like GLEW to do this next part. But it isn’t really that hard, and I found GLEW very difficult to get working cross-platform. The below code for enabling extension functions is kind of a write once and forget, everything just works and it isn’t complicated.

// Program
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
extern PFNGLUSEPROGRAMPROC glUseProgram;
extern PFNGLATTACHSHADERPROC glAttachShader;
extern PFNGLDETACHSHADERPROC glDetachShader;
extern PFNGLLINKPROGRAMPROC glLinkProgram;
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
extern PFNGLUNIFORM1IPROC glUniform1i;
extern PFNGLUNIFORM1IVPROC glUniform1iv;
extern PFNGLUNIFORM2IVPROC glUniform2iv;
extern PFNGLUNIFORM3IVPROC glUniform3iv;
extern PFNGLUNIFORM4IVPROC glUniform4iv;
extern PFNGLUNIFORM1FPROC glUniform1f;
extern PFNGLUNIFORM1FVPROC glUniform1fv;
extern PFNGLUNIFORM2FVPROC glUniform2fv;
extern PFNGLUNIFORM3FVPROC glUniform3fv;
extern PFNGLUNIFORM4FVPROC glUniform4fv;
extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
extern PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation;
extern PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f;
extern PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv;
extern PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv;
extern PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv;
extern PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv;
extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform;

// Shader
extern PFNGLCREATESHADERPROC glCreateShader;
extern PFNGLDELETESHADERPROC glDeleteShader;
extern PFNGLSHADERSOURCEPROC glShaderSource;
extern PFNGLCOMPILESHADERPROC glCompileShader;
extern PFNGLGETSHADERIVPROC glGetShaderiv;

// VBO
extern PFNGLGENBUFFERSPROC glGenBuffers;
extern PFNGLBINDBUFFERPROC glBindBuffer;
extern PFNGLBUFFERDATAPROC glBufferData;
extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
extern PFNGLMULTIDRAWELEMENTSPROC glMultiDrawElements;
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
extern PFNGLMAPBUFFERPROC glMapBuffer;
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;

// VAO
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;

#ifdef __WIN32__
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
#endif

void LoadGLExtensions();

Notice that the last 2 prototypes are Windows specific. That’s because when we get into Linux programming, these functions will already be supported out of the box. The extern command tells the compiler that we can start using these prototypes in code, but they will actually be defined at runtime in a .cpp file before the runtime code starts using them. The last line is a function definition that we will implement in the code below that queries the graphics card for the OpenGL function pointers. But first, as promised, let’s do the actual prototype definitions before using them in the same .cpp file.

SubVideoGL.cpp

#include "SubVideoGL.h"

// Program
PFNGLCREATEPROGRAMPROC glCreateProgram = NULL;
PFNGLDELETEPROGRAMPROC glDeleteProgram = NULL;
PFNGLUSEPROGRAMPROC glUseProgram = NULL;
PFNGLATTACHSHADERPROC glAttachShader = NULL;
PFNGLDETACHSHADERPROC glDetachShader = NULL;
PFNGLLINKPROGRAMPROC glLinkProgram = NULL;
PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = NULL;
PFNGLUNIFORM1IPROC glUniform1i = NULL;
PFNGLUNIFORM1IVPROC glUniform1iv = NULL;
PFNGLUNIFORM2IVPROC glUniform2iv = NULL;
PFNGLUNIFORM3IVPROC glUniform3iv = NULL;
PFNGLUNIFORM4IVPROC glUniform4iv = NULL;
PFNGLUNIFORM1FPROC glUniform1f = NULL;
PFNGLUNIFORM1FVPROC glUniform1fv = NULL;
PFNGLUNIFORM2FVPROC glUniform2fv = NULL;
PFNGLUNIFORM3FVPROC glUniform3fv = NULL;
PFNGLUNIFORM4FVPROC glUniform4fv = NULL;
PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv = NULL;
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = NULL;
PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f = NULL;
PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv = NULL;
PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv = NULL;
PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv = NULL;
PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv = NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL;
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = NULL;
PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation = NULL;
PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform = NULL;

// Shader
PFNGLCREATESHADERPROC glCreateShader = NULL;
PFNGLDELETESHADERPROC glDeleteShader = NULL;
PFNGLSHADERSOURCEPROC glShaderSource = NULL;
PFNGLCOMPILESHADERPROC glCompileShader = NULL;
PFNGLGETSHADERIVPROC glGetShaderiv = NULL;

// VBO
PFNGLGENBUFFERSPROC glGenBuffers = NULL;
PFNGLBINDBUFFERPROC glBindBuffer = NULL;
PFNGLBUFFERDATAPROC glBufferData = NULL;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL;
PFNGLDELETEBUFFERSPROC glDeleteBuffers = NULL;
PFNGLMULTIDRAWELEMENTSPROC glMultiDrawElements = NULL;
PFNGLBUFFERSUBDATAPROC glBufferSubData = NULL;
PFNGLMAPBUFFERPROC glMapBuffer = NULL;
PFNGLUNMAPBUFFERPROC glUnmapBuffer = NULL;

// VAO
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = NULL;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = NULL;

#ifdef __WIN32__
	PFNGLACTIVETEXTUREPROC glActiveTexture = NULL;
	PFNGLGENERATEMIPMAPPROC glGenerateMipmap = NULL;
#endif

And now the function implementation to query the graphics card:

void LoadGLExtensions()
{
	#ifdef _WIN32
		// Program
		glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
		glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
		glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
		glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
		glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader");
		glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
		glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
		glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
		glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
		glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
		glUniform1iv = (PFNGLUNIFORM1IVPROC)wglGetProcAddress("glUniform1iv");
		glUniform2iv = (PFNGLUNIFORM2IVPROC)wglGetProcAddress("glUniform2iv");
		glUniform3iv = (PFNGLUNIFORM3IVPROC)wglGetProcAddress("glUniform3iv");
		glUniform4iv = (PFNGLUNIFORM4IVPROC)wglGetProcAddress("glUniform4iv");
		glUniform1f = (PFNGLUNIFORM1FPROC)wglGetProcAddress("glUniform1f");
		glUniform1fv = (PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv");
		glUniform2fv = (PFNGLUNIFORM2FVPROC)wglGetProcAddress("glUniform2fv");
		glUniform3fv = (PFNGLUNIFORM3FVPROC)wglGetProcAddress("glUniform3fv");
		glUniform4fv = (PFNGLUNIFORM4FVPROC)wglGetProcAddress("glUniform4fv");
		glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
		glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)wglGetProcAddress("glGetAttribLocation");
		glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)wglGetProcAddress("glVertexAttrib1f");
		glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)wglGetProcAddress("glVertexAttrib1fv");
		glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)wglGetProcAddress("glVertexAttrib2fv");
		glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)wglGetProcAddress("glVertexAttrib3fv");
		glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)wglGetProcAddress("glVertexAttrib4fv");
		glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
		glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glDisableVertexAttribArray");
		glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)wglGetProcAddress("glBindAttribLocation");
		glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)wglGetProcAddress("glGetActiveUniform");

		// Shader
		glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
		glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
		glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
		glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
		glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");

		// VBO
		glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
		glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
		glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
		glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
		glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers");
		glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC)wglGetProcAddress("glMultiDrawElements");
		glBufferSubData = (PFNGLBUFFERSUBDATAPROC)wglGetProcAddress("glBufferSubData");
		glMapBuffer = (PFNGLMAPBUFFERPROC)wglGetProcAddress("glMapBuffer");
		glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)wglGetProcAddress("glUnmapBuffer");

		// VAO
		glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
		glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
		glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glDeleteVertexArrays");

		// Multitexturing GL Does Not Load 1.3+ Functionality in Windows
		glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
		glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)wglGetProcAddress("glGenerateMipmap");
	#endif
}

If you are like me, the first time you saw this you said “What the hell is this jibberish!?”. First off, open up those gl.h and glext.h files and look this stuff up so you can see what’s going on in there. The PFNGL variables are actually function pointer types, and we are implementing the standard function names and setting these function pointers to NULL. Then in the loading function, we are using the Windows specific command wglGetProcAddress to query the graphics card for that function pointer address. Awesome! Now we can start using these post OpenGL 1.3 functions! Heads up though, I have not defined every OpenGL function that is supposed to be supported in 3.2, but all of the common functions used in 3D game development for these tutorials are here.

 

Ok, now that the craptastic part is out of the way, let’s open a window.

Video.h

#pragma once

#include "SubVideoGL.h"

#ifdef _WIN32
	#include 
#endif

class Video
{
public:
	Video();
	~Video();

	bool Create(const char* title, bool fullscreen, unsigned int width, unsigned int height);
	void RenderBegin();
	void RenderEnd();

private:
	void setTitleFPS();
	void resizeWindow(unsigned int width, unsigned int height);

	#ifdef _WIN32
		static LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
			WPARAM wParam, LPARAM lParam);
	#endif

	char	m_title[255];
	unsigned int	m_width;
	unsigned int	m_height;
	bool			m_fullscreen;

	#ifdef _WIN32
		HDC		m_hDC;
		HWND	m_hWnd;

		//OpenGL:
		HGLRC	m_hGLRC;
	#endif
};

Ok, we included SubVideoGL.h, which means we will be able to work with OpenGL. Also, the “Video” class is created. The idea here is that the renderer is fully encapsulated within the “Video” class, making it as easy to use as possible. It is always better in my opinion to work with game engines that are easy to use, rather than have a million options and not know how to do anything! It is easy to load custom options with a config text file if you wish, without cluttering up all the public function parameters!

The core function here will be the Create function. This will create the window and OpenGL context, do all the error checking and pass control down to the main loop to get this show on the road! The render functions will initialize OpenGL rendering by clearing the screen and depth buffer, and end it by swapping the back buffer. More on that vocab in a bit. First, let’s get the window opening code in:

Video.cpp

#include "Video.h"
#include 

Video::Video(){}
Video::~Video(){}

bool Video::Create(const char* title, bool fullscreen, unsigned int width, unsigned int height)
{
	sprintf(m_title, title);
	m_fullscreen = fullscreen;
	m_width = width;
	m_height = height;

	#ifdef _WIN32
		WNDCLASS windowClass;
		windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
		windowClass.lpfnWndProc = Video::WndProc;
		windowClass.cbClsExtra = 0;
		windowClass.cbWndExtra = 0;
		windowClass.hInstance = NULL; //Don't need hInstance
		windowClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
		windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
		windowClass.hbrBackground = NULL;
		windowClass.lpszMenuName = NULL;
		windowClass.lpszClassName = (LPCSTR)title;
		if (!RegisterClass(&windowClass))
		{
			int error = (int)GetLastError();
			char strbuf[256];
			sprintf(strbuf, "Couldn't Register Window Class, ErrorCode: %i", error);
			MessageBoxA(NULL, strbuf, "ERROR", MB_OK);
			printf("Failed to register window class\n");
			return false;
		}

		DEVMODE devScrSettings;
		memset(&devScrSettings, 0, sizeof(devScrSettings));
		devScrSettings.dmSize = sizeof(devScrSettings);
		devScrSettings.dmPelsWidth = width;
		devScrSettings.dmPelsHeight = height;
		devScrSettings.dmBitsPerPel = 32;
		devScrSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
		DWORD dwExStyle;
		DWORD dwStyle;
		if(m_fullscreen)
		{
			dwExStyle = WS_EX_APPWINDOW;
			dwStyle = WS_POPUP;

			ChangeDisplaySettings(&devScrSettings, CDS_FULLSCREEN);
		}
		else
		{
			dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
			dwStyle = WS_OVERLAPPEDWINDOW;
		}

		m_hWnd = CreateWindowEx(dwExStyle, (LPCSTR)title, (LPCSTR)title,
		  WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dwStyle,
		  CW_USEDEFAULT, 0,
		  width, height,
		  NULL, NULL,
		  NULL, //Look Ma, no hInstance
		  this);

		if(!m_hWnd)
		{
			int error = (int)GetLastError();
			char strbuf[256];
			sprintf(strbuf, "Failed to create base window before Video Acceleration\nErrorCode: %i", error);
			MessageBoxA(NULL, strbuf, "ERROR", MB_OK);
			return false;
		}

First, we store the parameters as the class data members. It’s good to keep track of these in case we need them later. Microsoft complains (warns during compiling) about wanting to deprecate the ‘sprintf‘ function. I personally think this is a crappy scare tactic, as this function is part of standard C++. I doubt it will get deprecated, nice try Microsoft. Next, define the WNDCLASS options as stated. Notice Video::WndProc is our function that was defined in Video.h. Contrary to most other tutorials, you do not need hInstance, which I set to NULL. This is because we only need once instance of the game engine. Next, we register the window class, and report any errors. Next are the DEVMODE settings which are configured to be able to support full-screen mode. It isn’t very interesting, you can read up on this standard stuff elsewhere on the net, so if you don’t care, copy-paste is your friend. Also note, ChangeDisplaySettings is a Windows function if you were wondering. Next we create the window, and we are on our way! Note that the last parameter to CreateWindowEx is the this pointer. That’s important because we will need to access the class object pointer later in our WndProc function.

Now to the OpenGL context. Yes, we are still implementing the same function Create, this part comes after the Window Initialization code.

		m_hDC = GetDC(m_hWnd); // Get the device context for our window

		PIXELFORMATDESCRIPTOR pfd; // Create a new PIXELFORMATDESCRIPTOR (PFD)
		memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); // Clear our  PFD
		pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // Set the size of the PFD to the size of the class
		pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; // Enable double buffering, opengl support and drawing to a window
		pfd.iPixelType = PFD_TYPE_RGBA; // Set our application to use RGBA pixels
		pfd.cColorBits = 32; // Give us 32 bits of color information (the higher, the more colors)
		pfd.cDepthBits = 32; // Give us 32 bits of depth information (the higher, the more depth levels)
		pfd.iLayerType = PFD_MAIN_PLANE; // Set the layer of the PFD

		int nPixelFormat = ChoosePixelFormat(m_hDC, &pfd); // Check if our PFD is valid and get a pixel format back
		if (nPixelFormat == 0) // If it fails
		{
			MessageBoxA(NULL, "Failed to choose pixel format", "ERROR", MB_OK);
			return false;
		}

		bool bResult = SetPixelFormat(m_hDC, nPixelFormat, &pfd); // Try and set the pixel format based on our PFD
		if (!bResult) // If it fails
		{
			MessageBoxA(NULL, "Failed to set pixel format", "ERROR", MB_OK);
			return false;
		}

First we get the current handle to the window and store it in m_hDC. Then we create the pixel format description. The dwFlags say that it will support Double Buffering, OpenGL and drawing to the window. Double Buffering means that the graphics card will display one texture to the screen while rendering on another texture, then swap them when ready. This method ensures you don’t actually see the rendering happening on screen while watching animation or playing a game.

		HGLRC tempOpenGLContext = wglCreateContext(m_hDC); // Create an OpenGL 2.1 context for our device context
		wglMakeCurrent(m_hDC, tempOpenGLContext); // Make the OpenGL 2.1 context current and active

		int attributes[] = {
			WGL_CONTEXT_MAJOR_VERSION_ARB, 3, // Set the MAJOR version of OpenGL to 3
			WGL_CONTEXT_MINOR_VERSION_ARB, 2, // Set the MINOR version of OpenGL to 2
			WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Set our OpenGL context to be forward compatible
			0
		};

		PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
		if(wglCreateContextAttribsARB){
			m_hGLRC = wglCreateContextAttribsARB(m_hDC, NULL, attributes); // Create and OpenGL 3.x context based on the given attributes
			wglMakeCurrent(NULL, NULL); // Remove the temporary context from being active
			wglDeleteContext(tempOpenGLContext); // Delete the temporary OpenGL 2.1 context
			wglMakeCurrent(m_hDC, m_hGLRC); // Make our OpenGL 3.2 context current

			PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
			wglSwapIntervalEXT(0); //VSYNC NOT LOCKED TO 60
		}
		else {
			m_hGLRC = tempOpenGLContext; // If we didn't have support for OpenGL 3.x and up, use the OpenGL 2.1 context
			printf("No OpenGL 3.2 Support, fallback Version 2.1");
		}

Next, we create an OpenGL context. In order to query the graphics card for the extension functions for OpenGL 3, first we need to open the old OpenGL context so that we can use the wgl (Windows OpenGL) functions. Then set the attributes array. You can replace the Major 3 and Minor 2 (3.2) with whatever version you are implementing. Next we create the OpenGL 3.2 context and delete the old context. If this fails, we fall back to the old OpenGL and write a warning to the console. And the last part of the Create function:

		LoadGLExtensions(); //Load all the extensions!

		ShowWindow(m_hWnd, SW_SHOW);
		UpdateWindow(m_hWnd);

		glClearColor(0.0f, 0.15f, 0.0f, 1.0f);
		glFrontFace(GL_CCW);
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glEnable(GL_DEPTH_TEST);
		return true;
	#endif

	return false;
}

We load all the OpenGL extensions defined earlier. Then call the Windows functions to show and update the window, so the user can see it on their screen. Next we call some OpenGL functions that define our renderer defaults. glClearColor defines what the background color of the window should be (R,G,B,A), the values are between 0 and 1 (the example above renders a dark green background). glFrontFace defines which winding order vertices should be. glCullFace defines whether to cull back faces or not. glEnable(GL_DEPTH_TEST) enables depth testing, which ensures closer polygons render after ones behind them. Don’t forget to set this! We aren’t drawing any geometry yet, so we’ll come back to these in a future lesson. Lastly, after the Windows specific code we return false just in case the Windows code did not run.

Next up, the Render functions:

void Video::RenderBegin()
{
	setTitleFPS();

	//OPENGL:
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void Video::RenderEnd()
{
	//OPENGL:
	SwapBuffers(m_hDC);
}

The first function call setTitleFPS will be a function we define later that writes the current frames per second to the Window Titlebar (handy!). Then the glClear command is doing three things. The screen is being cleared completely to the clear color we defined earlier. The Depth and Stencil buffer bits are being cleared. The Stencil buffer offers even higher precision to the Depth buffer because it is using a texture to enable higher precision. Next the setTitleFPS:

void Video::setTitleFPS()
{
	#ifdef _WIN32
		static unsigned int fps = 0;
		static DWORD fpsLast = GetTickCount();
		static char windowText[255];
		static DWORD curTick = 0;

		++fps;
		curTick = GetTickCount();

		if( curTick - fpsLast > 1000) //Once per Second
		{
			sprintf(windowText, "%s %s %i", m_title, "FPS:", fps);
			SetWindowTextA(m_hWnd, windowText);			
			fps = 0;
			fpsLast = curTick;
		}
	#endif
}

Next the resizeWindow function. This is called whenever the window changes size.

void Video::resizeWindow(unsigned int width, unsigned int height)
{
	m_width = width;
	m_height = height;

	//OPENGL:
	glViewport(0, 0, width, height);
}

Lastly, the WndProc function, which is the required Windows message pump. All messages being received by your program go through this function. This is where you can poll user input and when window events happen. Right now, we are interested in whether the window is created, if its resized, and if it is destroyed.

#ifdef _WIN32
	LRESULT CALLBACK Video::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		static Video* video = 0;
		switch (message) {
			case WM_CREATE:
			{
				video = (Video*)((LPCREATESTRUCT)lParam)->lpCreateParams;
				SetWindowLongPtr(hWnd,GWL_USERDATA,(LONG_PTR)video);
			}
			case WM_SIZE: // If our window is resizing
			{
				video = (Video*)GetWindowLongPtr(hWnd,GWL_USERDATA);
				if(!video)
					MessageBoxA(NULL, "Bad Window Pointer on Resize", "Error", MB_OK);
				video->resizeWindow(LOWORD(lParam), HIWORD(lParam));
				break;
			}
			case WM_DESTROY:
			{
				PostQuitMessage(0);
				break;
			}
		}
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
#endif

Notice the static Video*, which is assigned a value during WM_CREATE (window creation). This is the address of the single Video class object. We are only supporting 1 renderer, remember! Having this pointer in the WndProc function is very convenient because next we can call the video->resizeWindow function! That’s it for the Video class!

Finally the heart of the program so we can test this code out!

Main.cpp

#include 

#include "Video.h"

int main(int argc, char **argv)
{
	printf("hello world\n");
	Video video;
	if(!video.Create("Tutorial 1A Win32 OpenGL Window", false, 640, 480))
	{
		#ifdef _WIN32
			MessageBoxA(NULL, "Failed to create window!", "Error", MB_OK);
		#endif

		return 1;
	}

	#ifdef _WIN32
		MSG msg = {};
		while( WM_QUIT != msg.message )
		{
			if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
			{
				TranslateMessage( &msg );
				DispatchMessage( &msg );
			}
			else
			{
				video.RenderBegin();
				video.RenderEnd();
			}
		}
		return ( int )msg.wParam;
	#endif

	return 0;
}

I’ve decided not to use WINAPI WinMain, because I want this to be a console application. That means when we call printf, we can print messages to the commandline console, which will run in a separate window for debugging purposes. Next, we create the Video class object, and create the actual window. Then we enter the main loop. While the program doesn’t get the quit message, see if there is a message in the queue, then send it to the WndProc. This is where the message is passed to see if we created a window or are resizing it. When there are no system messages to handle, then continue on to render a frame. That’s it! You should have a window up and rendering a solid color with the assurance that OpenGL 3 is enabled!

Leave a Reply