Tutorials on Keith Granthttp://keithgrant.co.uk/tutorials/
Recent content in Tutorials on Keith GrantHugo -- gohugo.ioen-usCopyright 2016 Keith GrantTutorial 1 (Creating an OpenGL Context)http://keithgrant.co.uk/tutorials/Tutorial1/
Mon, 01 Jan 0001 00:00:00 +0000http://keithgrant.co.uk/tutorials/Tutorial1/
<h4 id="outline:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Outline</h4>
<p>Many OpenGL tutorials start with something like &ldquo;First install GLFW&rdquo;, or &ldquo;To start you will need a windowing library such as SDL&rdquo;, or if you’re really unlucky, &ldquo;Start by downloading Freeglut&rdquo;. None of these are necessary (although if you insist on using one I recommend SFML which provides lots of nice features such as image/audio loading and has a very shallow learning curve). The one advantage these libraries offer you is that they tend to be extremely portable and will run across most platforms.
I suspect that if you are reading a tutorial on getting started with OpenGL then you’re probably some way from worrying about cross-platform compatibility, and the benefits of doing it all yourself can arguably outweigh the costs.</p>
<h4 id="benefits:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Benefits</h4>
<ul>
<li>No external dependencies
<ul>
<li>This makes it easier to share your code with others and involves less faffing about setting up the project.</li>
</ul></li>
<li>You have a better understanding of how everything in your project works
<ul>
<li>This is generally a ‘Good Thing’.</li>
</ul></li>
<li>Most off these libraries do more than just provide an OpenGL context, they provide all kinds of things like image loading, input handling, font loading, vector/matrix classes and much more.
<ul>
<li>This is all useful stuff, but the more of it you choose to use the more it will influence the style and architecture of your own applications. This in itself isn&rsquo;t a bad thing, however being aware of how this stuff works helps you make better decisions if you do decide to go for a third party solution.</li>
</ul></li>
<li>These libraries themselves have API’s that you will need to learn. For most of them it is possible to create a window with an OpenGL context with just a couple of lines of code, and if that’s all you want then that would be OK, but the other features (particularly input handling which is generally closed tied to the window), are always tempting to use and it can be quicker to just role your own and only implement what you actually need.</li>
</ul>
<h4 id="costs:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Costs</h4>
<ul>
<li>It takes time to write your own windowing code and attach an OpenGL context.
<ul>
<li>This is especially true if you want it to be robust.</li>
</ul></li>
<li>Win32 is awful.
<ul>
<li>Really, it’s just awful.</li>
</ul></li>
<li>If you want to target another problem, you will have to do this all again.
<ul>
<li>In order to create cross-platform libraries, you do at some point need to write platform specific code. Some people will say you don’t need to know Win32 as you should only write cross platform code. If you want to do anything graphical then at some point platform specific code will be required. It may be hidden from you inside a 3rd party library such as SDL or SFML, but they do have various sections of code that are just pre-processor conditionals checking for platform type. It is nice to have at least a basic understanding of how this stuff works.</li>
</ul></li>
</ul>
<h4 id="method:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Method</h4>
<p>To start, create a new Visual Studio project (if you are using a different tool chain then do the equivalent), call it whatever you like, but for this tutorial I&rsquo;m going to call mine <strong>Tutorial1</strong>. (Personally I go for a console application with pre-compiled header, but you can go without the pre-compiled header if you like, I mainly just use them out of habit as most projects I work on actually benefit from them).
In your new project you will see a number of files have been generated:</p>
<ul>
<li>stdafx.h</li>
<li>targetver.h</li>
<li><em>YourProjectName</em>.cpp (For my project that will be Tutorial1.cpp)</li>
<li>stdafx.cpp</li>
</ul>
<p>stdafx.h and stdafx.cpp make up your pre-compiled header, targetver.h can be safely ignored as well, the block comment it contains provides all the explanation that’s really needed.</p>
<p>You may see both Win32 and x64 configurations have been created,
these tutorials assume that they run under the x64 configuration,
I generally just delete the Win32 one as soon as I remember.</p>
<p>Tutorial1.cpp defines a main function in the Microsoft way:</p>
<pre><code class="language-c++">int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
</code></pre>
<p>Delete this and replace it with the more traditional:</p>
<pre><code class="language-c++">int main( int argc, const char* argv[] )
{
return 0;
}
</code></pre>
<blockquote>
<p>NOTE: This tutorial was originally created using VS 2013. I now use VS 2015 and the generated <code>main</code> looks like:</p>
<pre><code class="language-c++">int main()
{
return 0;
}
&gt;```
as we don’t actually use the input parameters for the main method this is fine as it is.
We will also need a couple of windows header files as well as maing sure we link against the opengl library, add the following after #include &quot;stdafx.h&quot;:
```c++
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include &lt;windows.h&gt;
#include &lt;windowsx.h&gt;
#pragma comment(lib,&quot;opengl32.lib&quot;)
</code></pre>
</blockquote>
<p>I like to have my <code>main</code> function in a file called main.cpp so I&rsquo;m going to rename that as well.</p>
<p>As much as possible we will try and lessen the awfulness of Win32 by avoiding its endless macros and defines (macros are always a ‘Bad Thing’).
You don’t even really need the parameters unless you plan on passing in command line parameters to your application (and this tutorial won’t cover any off that) but it is nice to at least see the “Standard” entry point definition.</p>
<p>The first thing that has to be done is we need to create a window. I&rsquo;m not going to go into too much detail about this, there’s loads of stuff about windows and how to create them on the internet already,
however there are a couple of pitfalls to be careful off.</p>
<p>To start with we will just do everything inside <code>main()</code>.</p>
<p>The first thing you need is a Window Handle (<code>HWND</code>):</p>
<pre><code class="language-c++">HWND window_handle;
</code></pre>
<p>An <code>HWND</code> is initialised by calling:
<code>CreateWindow(...);</code>
Unfortunately, as with all things windows, it’s not that straightforward. In order to create the window you will need a <code>HINSTANCE</code> and a <code>WNDCLASSEX</code>.
The <code>HINSTANCE</code> can be created by calling:</p>
<pre><code class="language-c++">HINSTANCE instance = GetModuleHandle(nullptr);
</code></pre>
<p>The <code>HINSTANCE</code> is a handle to an instance. That is to say the base address of the module in memory. For more info on <code>HINSTANCE</code> click here:
The <code>WNDCLASSEX</code> is slightly more complex. It’s a struct contains information about your window and contains the following fields:</p>
<pre><code class="language-c++">struct WNDCLASSEX {
UINT cbSize; // This is always this sizeof(WNDCLASSEX)
UINT style; // For an OpenGL window this must be CS_OWNDC
WNDPROC lpfnWndProc; // Function pointer to a function that deals with windows messages
int cbClsExtra; // We don’t need this.
int cbWndExtra; // We don’t need this.
HINSTANCE hInstance; // The HINSTANCE of this module
HICON hIcon; // The icon that identifies the app. Set it to NULL for default
HCURSOR hCursor; // The default cursor to be used when your window has focus
HBRUSH hbrBackground; // The default background of the window
LPCTSTR lpszMenuName; // The main application menu for the window
LPCTSTR lpszClassName; // An identifier for this WNDCLASSEX (This has nothing to do with C++ classes)
HICON hIconSm; // The small icon that identifies the app. Set it to NULL for default
}
</code></pre>
<p>For more details on what these all mean see <a href="https://msdn.microsoft.com/en-gb/library/windows/desktop/ms633577.aspx" title="Title">MSDN</a>:<br />
We create a <code>WNDCLASSEX</code> as follows:</p>
<pre><code class="language-c++">WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance; // The instance we got above
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L&quot;owp_window&quot;; // The class name, this could be anything
wcex.hIconSm = NULL;
</code></pre>
<p>We then register the <code>WNDCLASSEX</code> by calling:</p>
<pre><code class="language-c++">RegisterClassEx(wcex);
</code></pre>
<p>Now we have an <code>HINSTANCE</code> and a <code>WNDCLASSEX</code> we can create our window</p>
<pre><code class="language-c++">window_handle = CreateWindow(
L&quot;tutorial_1_window&quot;, // The class we created above
L&quot;Tutorial&quot;, // The title of the window
WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, // The style of the window
200, // The x position of the top left
200, // The y position of the window
600, // The width of the window
400, // The height of the window
NULL, // The parent of the window
NULL, // The menu for the window
instance, // The module instance
NULL // Not sure don't worry about it
);
</code></pre>
<p>Now the window has been created we just need to tell it to show and to update so:</p>
<pre><code class="language-c++">ShowWindow(window_handle, SW_SHOW);
UpdateWindow(window_handle);
</code></pre>
<p>And finally we need to deal with windows messages, otherwise it becomes unresponsive. This is pretty much boilerplate. We need to check for the message_return value otherwise the application won’t exit properly.</p>
<pre><code class="language-c++">MSG msg;
BOOL message_return;
bool run_loop = true;
while(run_loop &amp;&amp; (message_return = GetMessage(&amp;msg, window_handle, 0, 0)) != 0)
{
if(message_return == -1)
{
run_loop = false;
}
else
{
TranslateMessage(&amp;msg);
DispatchMessage(&amp;msg);
}
}
DestroyWindow(window_handle); // Clean up after ourselves
</code></pre>
<p>So now we have a window, but we still don’t have an OpenGL context.
The most basic OpenGL context is simple enough to do. We just need a <code>PIXELFORMATDESCRIPTOR</code> , an <code>HDC</code> (Device Context) and an <code>HGLRC</code> (OpenGLContext).
(Note: All the following code should be added before the call to <code>ShowWindow</code>)
We first need to create a <code>PIXELFORMATDESCRIPTOR</code> object, the <code>PIXELFORMATDESCRIPTOR</code> is a struct that has the following form.</p>
<pre><code class="language-c++">struct PIXELFORMATDESCRIPTOR {
WORD nSize;
WORD nVersion;
DWORD dwFlags;
BYTE iPixelType;
BYTE cColorBits;
BYTE cRedBits;
BYTE cRedShift;
BYTE cGreenBits;
BYTE cGreenShift;
BYTE cBlueBits;
BYTE cBlueShift;
BYTE cAlphaBits;
BYTE cAlphaShift;
BYTE cAccumBits;
BYTE cAccumRedBits;
BYTE cAccumGreenBits;
BYTE cAccumBlueBits;
BYTE cAccumAlphaBits;
BYTE cDepthBits;
BYTE cStencilBits;
BYTE cAuxBuffers;
BYTE iLayerType;
BYTE bReserved;
DWORD dwLayerMask;
DWORD dwVisibleMask;
DWORD dwDamageMask;
}
</code></pre>
<p>Create an instance of <code>PIXELFORMATDESCRIPTOR</code> in the following way:</p>
<pre><code class="language-c++">PIXELFORMATDESCRIPTOR pixel_format_desc =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
24,
8,
0,
PFD_MAIN_PLANE,
0,
0,
0,
0
};
</code></pre>
<p>Then get the <code>HDC</code> for the window:</p>
<pre><code class="language-c++">GetHDC(window_handle);
</code></pre>
<p>And then get the best match pixel format:</p>
<pre><code class="language-c++">int pixel_format = ChoosePixelFormat(device_context, &amp;pixel_format_desc);
</code></pre>
<p>Set the pixel format:</p>
<pre><code class="language-c++">SetPixelFormat(device_context, pixel_format, &amp;pixel_format_desc);
</code></pre>
<p>And then finally make the OpenGL Context:</p>
<pre><code class="language-c++">HGLRC gl_context = wglCreateContext(device_context);
</code></pre>
<p>Once we have an OpenGL context we have to set it as the current context:</p>
<pre><code class="language-c++">wglMakeCurrent(device_context, gl_context);
</code></pre>
<p>Also in the spirit of encouraging good coding practice we should delete the context before we exit (just before we destroy the window).</p>
<pre><code class="language-c++">wglDeleteContext(gl_context);
</code></pre>
<p>And that’s it we have an OpenGL context.<br />
But Wait…<br />
What we actually have is a pretty crappy OpenGL context.<br />
You may have noticed that all the functions dealing with OpenGL creation started with <code>wgl</code>, that’s because WGL is the windows API for interacting with OpenGL, on other OS’s you may use GLX, which provides similar functions.
The base API is pretty basic and you can’t really do much, (I&rsquo;ve never even tried to use it), so what we want to do is enable extensions, that way we can create more useful contexts. The first problem is that the call to enable extensions requires a context to be created. So what we do is create a simple context, enable the extensions, destroy the simple context, and then create a new and improved one.
The best thing is we have just created a simple context so what do we need next? We need to enable extensions.
There are loads of extensions, and it is possible to load them manually but generally it is better to use an extensions library. The most common one is GLEW, which is available as source or binary, linking to it can be a bit of a pain, so I find it easiest to just get the source and it directly to my own project.
Just download the source, extract it and copy the glew.h and wglew.h files from the include\GL folder and glew.c from the src folder into a folder called GL beside your own source files.</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_1.PNG" />
</figure>
<p>You then need to add the files to your project:</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_2.PNG" />
</figure>
<p>And then add the path to the GLEW headers to your project properties.</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_3.PNG" />
</figure>
<p>I recommend using the built in Macros for Visual Studio paths wherever possible. It really does simplify things.
Finally we need to turn pre-compiled headers off for glew.c.</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_4.PNG" />
</figure>
<p>It might be best just to build the project and run just now to make sure it still works.
As long as everything still builds and we still get an empty window we can start to proceed with our improved context.
To do this first need to add three includes at the top of the file (after the existing #includes):</p>
<pre><code class="language-c++">#include &quot;GL\glew.h&quot;
#include &quot;GL\wglew.h&quot;
#include &lt;iostream&gt;
</code></pre>
<p>Then starting above the call to <code>ShowWindow()</code> we add:</p>
<pre><code class="language-c++">glewExperimental = TRUE;
auto err = glewInit();
if(err != GLEW_OK)
{
//Problem: glewInit failed, something is seriously wrong.
std::cout &lt;&lt; &quot;glewInit failed, aborting.&quot; &lt;&lt; std::endl;
exit(0);
}
std::cout &lt;&lt; &quot;Using GLEW Version: &quot; &lt;&lt; glewGetString(GLEW_VERSION) &lt;&lt; std::endl;
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; supported&quot; &lt;&lt; std::endl;
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(gl_context);
</code></pre>
<p>We need to enable <code>glewExperimental</code> due to an ongoing bug in GLEW, then we call <code>glewInit()</code> , it seems fairly obvious that initializes GLEW. Then we destroy the simple context that we created. Before we destroy it though we have to make it not the current context. To do this just call <code>wglMakeCurrent(nullptr, nullptr);</code> (This isn&rsquo;t strictly necessary as <code>wglDeleteContext(gl_context)</code> should make the context non-current before deleting it. But this makes it more obvious to the developer that this is happening).
If we run the app now we should see something like (The version numbers will depend on your system):</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_5.PNG" />
</figure>
<p>Hopefully your supported OpenGL version is above 4.x.x or else some of the other tutorials I&rsquo;m planning on writing might not work. In fact if your supported version is less than 3.1 then even this tutorial probably won’t work.
We&rsquo;ve enabled GLEW so now we can create our fancy context.
The first thing we have to do is get a Pixel Format. This is similar to when we got a pixel format for the simple context, but this time we use the <code>wglChoosePixelFormatARB</code> extension.
First we check that this has been enabled properly:</p>
<pre><code class="language-c++">
if (!WGLEW_ARB_pixel_format)
{
std::cout &lt;&lt; &quot;Pixel Format Extension Not Supported; Exiting...&quot; &lt;&lt; std::endl;
exit(0);
}
</code></pre>
<p>Then we define our list of requested attributes. Unlike the simple context we don&rsquo;t populate a struct, instead we define a set of paired values. The first value is the attribute we want to define, the second is the value that we want to use. This can have an arbitrary number of values</p>
<pre><code class="language-c++">const int pixel_format_attribute_list[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0 // End of attributes list
};
</code></pre>
<p>Now we get the pixel format using <code>wglChoosePixelFormat</code> which has the following signature:</p>
<pre><code>!#c++
BOOL wglChoosePixelFormatARB(
HDC hdc, // The HDC of the window
const int *piAttribIList, // A list of integer attributes
const FLOAT *pfAttribFList, // A list of floating point
UINT nMaxFormats, // Number of pixel formats that will be returned
int *piFormats, // array of pixel formats from best match down
UINT *nNumFormats // The number of pixel formats returned
);
</code></pre>
<p>We call it with the following parameters:</p>
<pre><code class="language-c++">int improved_pixel_format;
UINT num_formats;
if(wglChoosePixelFormatARB(device_context,
pixel_format_attribute_list,
nullptr, // We don’t use this
1, // Just get the best match
&amp; improved_pixel_format,
&amp;num_formats) == 0)
{
std::cout &lt;&lt; &quot;Couldn't find valid pixel format&quot; &lt;&lt; std::endl;
exit(0);
}
</code></pre>
<p>We then call <code>SetPixelFormat</code>, unlike in the simple context case above we don’t need a <code>PIXELFORMATDESCRIPTOR</code> object and can just pass in a <code>nullptr</code>.</p>
<pre><code class="language-c++">SetPixelFormat(device_context, improved_pixel_format, nullptr);
</code></pre>
<p>The last thing we need to do is create the actual context. For this we need some context attributes. These are created in a similar way to the pixel format attributes we created a little while ago.
First we check for the extension:</p>
<pre><code class="language-c++">if(!WGLEW_ARB_create_context)
{
std::cout &lt;&lt; &quot;Create Context Extension Not Supported; Exiting...&quot; &lt;&lt; std::endl;
exit(0);
}
</code></pre>
<p>Then we populate an array of attributes:</p>
<pre><code class="language-c++">int context_attributes[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0 // End of attributes list
};
</code></pre>
<p>The first two are fairly obvious, these are the major/minor version of OpenGl that you want your context to create.<br />
The last attribute (<code>WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB</code>) tells the context to completely disable all deprecated features up to the requested context.
We then call:</p>
<pre><code class="language-c++">gl_context = wglCreateContextAttribsARB(device_context, 0, context_attributes);
wglMakeCurrent(device_context, gl_context);
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; created.&quot; &lt;&lt; std::endl &lt;&lt; std::endl;
</code></pre>
<p>The call to <code>wglCreateContextAttribsARB</code> replaces the call to <code>wglCreateContext</code> that the simple context used.<br />
Then we call <code>wglMakeCurrent</code> in the same way as we did before, and the message being sent to the console should tell you which version of OpenGL the created context actually supports.<br />
If that’s not enough to convince you its working then add:</p>
<pre><code class="language-c++">glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
SwapBuffers(device_context);
</code></pre>
<p>Into the message loop (after the <code>if()…else</code>) and your window will show up yellow.</p>
<h4 id="tidying-up:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Tidying Up</h4>
<p>So we have a working context.<br />
But nobody wants to write all that code every time they start a new project so we should tidy this up a bit.<br />
Normally I would just go straight to turning this into a class, but for the sake of clarity I&rsquo;m going to just break it into local methods in the main.cpp file for now.<br />
But even before that there are some things that can be done.<br />
The first thing is that we can zero out the <code>WNDCLASSEX</code> and <code>PIXELFORMATDESCRIPTOR</code> structs before setting just the values we care about. This basically means that the entire struct is set to 0.
So for <code>WNDCLASSEX</code>:</p>
<pre><code class="language-c++">WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance; // The instance we got above
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L&quot;tutorial_1_window&quot;; // The class name
wcex.hIconSm = NULL;
</code></pre>
<p>Becomes:</p>
<pre><code class="language-c++">WNDCLASSEX wcex;
SecureZeroMemory(&amp;wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.hInstance = instance; // The instance we got above
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszClassName = L&quot;tutorial_1_window&quot;; // The class name
</code></pre>
<p>And for the <code>PIXELFORMATDESCRIPTOR</code>:</p>
<pre><code class="language-c++">PIXELFORMATDESCRIPTOR pixel_format_desc =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
24,
8,
0,
PFD_MAIN_PLANE,
0,
0,
0,
0
};
</code></pre>
<p>Becomes:</p>
<pre><code class="language-c++">PIXELFORMATDESCRIPTOR pixel_format_desc;
SecureZeroMemory(&amp;pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixel_format_desc.nVersion = 1;
pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
pixel_format_desc.cColorBits = 32;
pixel_format_desc.cDepthBits = 24;
pixel_format_desc.cStencilBits = 8;
pixel_format_desc.iLayerType = PFD_MAIN_PLANE;
</code></pre>
<p>So that’s a couple of basic things tidied up, I only included the more verbose versions initially so that the structs could be described in slightly more detail.<br />
Now to split it into methods.<br />
To me at least it seems that 3 things are happening:</p>
<ol>
<li>A window is created</li>
<li>GLEW is initialized</li>
<li>An OpenGL context is created</li>
</ol>
<p>These seem like candidates for methods so let’s start with the first:
Define a method with the signature</p>
<pre><code class="language-c++">HWND createApplicationWindow();
</code></pre>
<p>Move the first section of code into the body of that method. Basically everything from:</p>
<pre><code class="language-c++">HINSTANCE instance = GetModuleHandle(nullptr);
</code></pre>
<p>to, with th eonly change being that we return the call to <code>CreateWindow(...)</code> rather than assigning from it.</p>
<pre><code class="language-c++">window_handle = CreateWindow(…)
</code></pre>
<p>So the method looks like:</p>
<pre><code class="language-c++">HWND createApplicationWindow(
) {
HINSTANCE instance = GetModuleHandle(nullptr);
WNDCLASSEX wcex;
SecureZeroMemory(&amp;wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.hInstance = instance; // The instance we got above
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszClassName = L&quot;tutorial_1_window&quot;; // The class name
RegisterClassEx(&amp;wcex);
return CreateWindow(
L&quot;tutorial_1_window&quot;, // The class we created above
L&quot;Tutorial 1&quot;, // The title of the window
WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, // The style of the window
200, // The x position of the top left
200, // The y position of the window
600, // The width of the window
400, // The height of the window
NULL, // The parent of the window
NULL, // The menu for the window
instance, // The module instance
NULL // Not sure don't worry about it
);
}
</code></pre>
<p>Then create another method with the signature</p>
<pre><code class="language-c++">bool initializeGLEW(const HWND&amp; win_handle, const HDC&amp; device_context);
</code></pre>
<p>and populate the body of that method with the code related to setting up the simple context and initialising GLEW.
It should look something like:</p>
<pre><code class="language-c++">bool initializeGLEW(
const HWND&amp; win_handle,
const HDC&amp; device_context
) {
PIXELFORMATDESCRIPTOR pixel_format_desc;
SecureZeroMemory(&amp;pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixel_format_desc.nVersion = 1;
pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
pixel_format_desc.cColorBits = 32;
pixel_format_desc.cDepthBits = 24;
pixel_format_desc.cStencilBits = 8;
pixel_format_desc.iLayerType = PFD_MAIN_PLANE;
int pixel_format = ChoosePixelFormat(device_context, &amp;pixel_format_desc);
SetPixelFormat(device_context, pixel_format, &amp;pixel_format_desc);
HGLRC gl_context = wglCreateContext(device_context);
wglMakeCurrent(device_context, gl_context);
glewExperimental = TRUE;
auto err = glewInit();
if(err != GLEW_OK)
{
//Problem: glewInit failed, something is seriously wrong.
std::cout &lt;&lt; &quot;glewInit failed, aborting.&quot; &lt;&lt; std::endl;
return false;
}
std::cout &lt;&lt; &quot;Using GLEW Version: &quot; &lt;&lt; glewGetString(GLEW_VERSION) &lt;&lt; std::endl;
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; supported&quot; &lt;&lt; std::endl;
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(gl_context);
return true;
}
</code></pre>
<p>note that we pass in the device context rather than creating it within thi smethod, also we return false rather than exiting on an error.</p>
<p>The final method should have the signature</p>
<pre><code class="language-c++">bool createOpenGLContext(const HWND&amp; win_handle, const HDC&amp; device_context, HGLRC* gl_context);
</code></pre>
<p>and look something like:</p>
<pre><code class="language-c++">bool createOpenGLContext(
const HWND&amp; win_handle,
const HDC&amp; device_context,
HGLRC* gl_context
) {
if(!WGLEW_ARB_pixel_format)
{
std::cout &lt;&lt; &quot;Pixel Format Extension Not Supported; Exiting...&quot; &lt;&lt; std::endl;
return false;
}
const int pixel_format_attribute_list[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0 // End of attributes list
};
int improved_pixel_format;
UINT num_formats;
if(wglChoosePixelFormatARB(device_context,
pixel_format_attribute_list,
nullptr, // We don’t use this
1, // Just get the best match
&amp;improved_pixel_format,
&amp;num_formats) == 0)
{
std::cout &lt;&lt; &quot;Couldn't find valid pixel format&quot; &lt;&lt; std::endl;
return false;
}
SetPixelFormat(device_context, improved_pixel_format, nullptr);
if(!WGLEW_ARB_create_context)
{
std::cout &lt;&lt; &quot;Create Context Extension Not Supported; Exiting...&quot; &lt;&lt; std::endl;
return false;
}
int context_attributes[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0 // End of attributes list
};
*gl_context = wglCreateContextAttribsARB(device_context, 0, context_attributes);
wglMakeCurrent(device_context, *gl_context);
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; created.&quot; &lt;&lt; std::endl &lt;&lt; std::endl;
return true;
}
</code></pre>
<p>Again, failulres return false now rather than just exiting.</p>
<p>Then using these new methods main can be changed to look like:</p>
<pre><code class="language-c++">int main(int argc, const char* argv[])
{
HWND window_handle = createApplicationWindow();
HDC device_context = GetDC(window_handle);
if(!initializeGLEW(window_handle, device_context))
{
exit(0); // GLEW failed to initialize. Just exit now.
}
HGLRC gl_context;
if(!createOpenGLContext(window_handle, device_context, &amp;gl_context))
{
exit(0);
}
// Add nothing after this
ShowWindow(window_handle, SW_SHOW);
UpdateWindow(window_handle);
MSG msg;
BOOL message_return;
bool run_loop = true;
while(run_loop &amp;&amp; (message_return = GetMessage(&amp;msg, window_handle, 0, 0)) != 0)
{
if(message_return == -1)
{
run_loop = false;
}
else
{
TranslateMessage(&amp;msg);
DispatchMessage(&amp;msg);
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
SwapBuffers(device_context);
}
wglDeleteContext(gl_context);
DestroyWindow(window_handle); // Clean up after ourselves
return 0;
}
</code></pre>
<p>To me that main function still looks a bit busy. Ideally I&rsquo;d like it to have no Win32 calls in it at all. To do that we can create a new class that will take care of creating a window and attaching an OpenGL context.
How you approach the design of a class, and what you define in its public API is a big topic, so I&rsquo;m not going to cover it in detail here. The one thing I think is important is that you should define the API first and then create a class around that. Rather than just jump in writing code.
So what would an OpenGL windowing class need?
Obviously a name, let’s call it <code>GLWindow</code>.
Create two new files GLWindow.h and GLWindow.cpp in your project</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial1/image_6.PNG" />
</figure>
<p>And add the following code:</p>
<p>GLWindow.h</p>
<pre><code class="language-c++">#pragma once
class GLWindow
{
public:
GLWindow();
~GLWindow();
};
</code></pre>
<p>GLWindow.cpp:</p>
<pre><code class="language-c++">#include &quot;stdafx.h&quot;
#include &quot;GLWindow.h&quot;
GLWindow::GLWindow(
) {}
GLWindow::~GLWindow(
) {}
</code></pre>
<p>We’ll need various class members. So add</p>
<pre><code class="language-c++">private:
HWND m_window_handle;
HDC m_device_context;
HGLRC m_gl_context;
</code></pre>
<p>to the header.
We can also use the three methods we defined earlier as a basis for the class. They will be slightly different here so we’ll declare them at the class level as:</p>
<pre><code class="language-c++">private: // Helper Methods
void createApplicationWindow();
void initializeGlew();
void createOpenGLContext();
</code></pre>
<p>Notice they don’t have parameters now, they will just be acting on the class members, also they all return void. You could have them return bool to check for success as we did earlier, however I don’t particularly like that pattern. I&rsquo;m not going to cover the pros and cons of things like exceptions or returning failure codes, that’s a topic for a different time so we’re going to make the rather naïve assumption that all our stuff works.
Define them in the .cpp file as:</p>
<pre><code class="language-c++">void GLWindow::createApplicationWindow(
) {
HINSTANCE instance = GetModuleHandle(nullptr);
WNDCLASSEX wcex;
SecureZeroMemory(&amp;wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.hInstance = instance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszClassName = L&quot;tutorial_1_window&quot;;
RegisterClassEx(&amp;wcex);
m_window_handle = CreateWindow(
L&quot;tutorial_1_window&quot;,
L&quot;Tutorial 1&quot;,
WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
200,
200,
600,
400,
NULL,
NULL,
instance,
NULL);
m_device_context = GetDC(m_window_handle);
}
</code></pre>
<pre><code class="language-c++">void GLWindow::initializeGlew(
) {
// Initialize basic OpenGl
// We need a PIXELFORMATDESCRIPTOR
PIXELFORMATDESCRIPTOR pixel_format_desc;
SecureZeroMemory(&amp;pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixel_format_desc.nVersion = 1;
pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
pixel_format_desc.cColorBits = 32;
pixel_format_desc.cDepthBits = 24;
pixel_format_desc.cStencilBits = 8;
pixel_format_desc.iLayerType = PFD_MAIN_PLANE;
int pixel_format = ChoosePixelFormat(m_device_context, &amp;pixel_format_desc);
SetPixelFormat(m_device_context, pixel_format, &amp;pixel_format_desc);
m_gl_context = wglCreateContext(m_device_context);
wglMakeCurrent(m_device_context, m_gl_context);
glewExperimental = TRUE;
auto err = glewInit();
if(err != GLEW_OK)
{
//Problem: glewInit failed, something is seriously wrong.
std::cout &lt;&lt; &quot;glewInit failed.&quot; &lt;&lt; std::endl;
}
std::cout &lt;&lt; &quot;Using GLEW Version: &quot; &lt;&lt; glewGetString(GLEW_VERSION) &lt;&lt; std::endl;
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; supported&quot; &lt;&lt; std::endl;
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(m_gl_context);
}
</code></pre>
<pre><code class="language-c++">void GLWindow::createOpenGLContext(
) {
// Pixel Format
if(!WGLEW_ARB_pixel_format || !WGLEW_ARB_create_context)
{
std::cout &lt;&lt; &quot;Extensions Not Supported Exiting...&quot; &lt;&lt; std::endl;
}
const int pixel_format_attribute_list[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
WGL_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0 // End of attributes list
};
int improved_pixel_format;
UINT num_formats;
if(wglChoosePixelFormatARB(m_device_context,
pixel_format_attribute_list,
nullptr, // We don’t use this
1, // Just get the best match
&amp;improved_pixel_format,
&amp;num_formats) == 0)
{
std::cout &lt;&lt; &quot;Couldn't find valid pixel format&quot; &lt;&lt; std::endl;
}
SetPixelFormat(m_device_context, improved_pixel_format, nullptr);
int context_attributes[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, 1,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0 // End of attributes list
};
m_gl_context = wglCreateContextAttribsARB(m_device_context, 0, context_attributes);
wglMakeCurrent(m_device_context, m_gl_context);
std::cout &lt;&lt; &quot;OpenGL: OpenGL version &quot; &lt;&lt; glGetString(GL_VERSION) &lt;&lt; &quot; created.&quot; &lt;&lt; std::endl &lt;&lt; std::endl;
}
</code></pre>
<p>These are practically identical to the way they were in the main.cpp file, so I&rsquo;m not going to go into any further detail on them.
Add calls to these into the constructor:</p>
<pre><code class="language-c++">GLWindow::GLWindow(
) {
createApplicationWindow();
initializeGlew();
createOpenGLContext();
}
</code></pre>
<p>And also move the calls to ShowWindow(… and UpdateWindow(…) into the constructor:</p>
<pre><code class="language-c++">GLWindow::GLWindow(
) {
createApplicationWindow();
initializeGlew();
createOpenGLContext();
ShowWindow(m_window_handle, SW_SHOW);
UpdateWindow(m_window_handle);
}
</code></pre>
<p>Move the clean-up code into the destructor:</p>
<pre><code class="language-c++">GLWindow::~GLWindow(
) {
wglDeleteContext(m_gl_context);
DestroyWindow(m_window_handle);
}
</code></pre>
<p>The next thing is we need to move the message loop into the class. At the moment we will keep this very simple. We’re not interacting with anything or detecting user input yet so this doesn&rsquo;t have to be very clever.
Just add a public method:</p>
<pre><code class="language-c++">bool run();
</code></pre>
<p>and move the whole message loop into it as below, this is slightly different than we had before, and this is due to the fact that the message loop will run separately from the main update loop:</p>
<pre><code class="language-c++">bool GLWindow::run()
{
MSG msg;
if(!IsWindow(m_window_handle))
{
return false;
}
while(PeekMessage(&amp;msg, m_window_handle, 0, 0, PM_REMOVE))
{
TranslateMessage(&amp;msg);
DispatchMessage(&amp;msg);
}
return true;
}
</code></pre>
<p>Finally we need to provide a method for swapping the back buffer. Define a method with the signature:</p>
<pre><code class="language-c++">void swapBuffers();
</code></pre>
<p>And define it as:</p>
<pre><code class="language-c++">void GLWindow::swapBuffers()
{
SwapBuffers(m_device_context);
}
</code></pre>
<p>The main function can now be defined as:</p>
<pre><code class="language-c++">int main(int argc, const char* argv[])
{
GLWindow test_window;
while(test_window.run())
{
// App running
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
// Your OpenGLCode here...
test_window.swapBuffers();
}
return 0;
}
</code></pre>
<p>If we try and build this now the build will fail.
All we need to do is move the <code>#includes</code> from the main.cpp file into the GLWindow.h file
And add an include for GLWindow.h into main.cpp</p>
<h4 id="further-work:dd7bd0f9e9fc4c943ebbf7fe7a0ad822">Further Work</h4>
<p>Obviously there is much more that could be done here, some ideas to get started:</p>
<ol>
<li>Parameterize the GLWindow class so the version of OpenGL requested can be changed.</li>
<li>Add proper error handling.</li>
<li>Create a fallback option if the extensions don’t load, that will allow the use of older styles of OpenGL.</li>
</ol>
Tutorial 2 (Dealing With User Input)http://keithgrant.co.uk/tutorials/Tutorial2/
Mon, 01 Jan 0001 00:00:00 +0000http://keithgrant.co.uk/tutorials/Tutorial2/
<h4 id="background:77c229d7c1b16e8e4f5d866aedb29d3f">Background</h4>
<p>When approaching this tutorial I hummed and hawed over whether it should build on <a href="http://keithgrant.co.uk/tutorials/Tutorial1/">Tutorial1</a> or not. Technically it has nothing to do with OpenGL,
but user input is so closely tied to creating a window that in the end I decided that I would.<br />
Bearing that in mind the starting point for this tutorial more or less picks up at the end of <a href="http://keithgrant.co.uk/tutorials/Tutorial1/">Tutorial1</a>.
If you haven&rsquo;t worked through it, then the source and Visual Studio project files for it are available on Bitbucket <a href="https://bitbucket.org/kgrant/openglcontexttutorials.git">here</a>.</p>
<h4 id="getting-started:77c229d7c1b16e8e4f5d866aedb29d3f">Getting Started</h4>
<p>So assuming we have a working copy of the source from <a href="http://keithgrant.co.uk/tutorials/Tutorial1/">Tutorial1</a> lets get started.
The first thing you might notice is that there are a lot of warnings being generated when we compile the project. This can easily be fixed by defining <code>GLEW_BUILD</code>,
this can either be done using <code>#define GLEW_BUILD</code> before including the GLEW headers, but I personally prefer to add it to the preprocessor definitions.</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial2/image_1.PNG" />
</figure>
<p>Now that that&rsquo;s taken care off, we can look at how to properly handle inputs.<br />
In the previous tutorial we defined a basic <code>GL::run</code> method that made sure that all messages were being handled, but I kind of glossed over the details, mainly because I was just concerned with getting the OpenGL context created.<br />
In Windows applications are event driven, I&rsquo;m not going to go into loads of detail here (it&rsquo;s quite dull), but the <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx">MSDN</a> page is quite informative. To get started all you really need to know is that whenever an input event occurs a <code>MSG</code> is sent to the application.
A <code>MSG</code> is defined as:</p>
<pre><code class="language-c++">typedef struct tagMSG {
HWND hwnd; // The window handle that should receive the message
UINT message; // The message ID (What kind of message it is)
WPARAM wParam; // Additional info (Message Specific)
LPARAM lParam; // Additional info (Message Specific)
DWORD time; // Time the message was posted
POINT pt; // Screen coordinates of the cursor when the message was posted.
} MSG, *PMSG, *LPMSG;
</code></pre>
<p>These messages are processed by the window procedure of the window that the OpenGL context was attached to. Currently the windows procedure is a default one that is called to handle any messages that are generated for the window. You can see this being set up where the window is created (in <code>GLWindow::createApplicationWindow</code>) the <code>WNDCLASSEX::lpfnWndProc</code> member is a function pointer and we currently assign <code>DefWinProc</code> to it. The type of <code>WNDCLASSEX::lpfnWndProc</code> is <code>WNDPROC</code> which is a typedef for a function in the form:</p>
<pre><code class="language-c++">typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
// | type | name | parameters |
</code></pre>
<p>So rather than use <code>DefWinProc</code> we need to define our own, the over eager may just go ahead and define it as:</p>
<pre><code class="language-c++">class GLWindow
{
GLWindow();
~GLWindow()
// other methods...
LRESULT CALLBACK MyWinProc(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
);
}
</code></pre>
<p>The more observant may notice that WNDPROC can&rsquo;t be an instance method so we have to declare it as a static.</p>
<pre><code class="language-c++">class GLWindow
{
GLWindow();
~GLWindow()
// other methods...
private:
// other methods...
static LRESULT CALLBACK staticWindowsProcedure(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
);
}
</code></pre>
<p>The problem with this is that we may (almost certaintly) want to access instance members/methods which can&rsquo;t be done in a static method.</p>
<p>The solution is fairly standard, and multiple versions of it can be found elswhere on the web, but here it is in one of it&rsquo;s simplest forms.</p>
<p>Add the above method decleration in the GLWindow.h file (it can be a <code>private</code> method), and then in the .cpp file start to define it as:</p>
<pre><code class="language-c++">LRESULT CALLBACK GLWindow::staticWindowsProcedure(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
) {
}
</code></pre>
<p>This method is going to do three things:</p>
<ol>
<li>If the <code>MSG</code> is <code>WM_NCCREATE</code> we will use <code>SetWindowLongPtr</code> to associate a pointer to this instance of our <code>GLWindow</code> class to the window.</li>
<li>If the pointer has been set correctly then we retrieve it and use to call the non-static windows procedure method.</li>
<li>Default to calling<code>DefWinProc</code> if neither of the two options succeed.</li>
</ol>
<p>The first thing we check is the value of the <code>message</code> parameter, if it is <code>WM_NCCREATE</code>, then we can use the <code>l_param</code> to access the data we need.</p>
<blockquote>
<p>As you may remember <code>w_param</code> and <code>l_param</code> contain additional information pertaining to the <code>MSG</code>, the kinds of information they contain is different according the type of the message, by the time our windows procedure is called we aren&rsquo;t getting the whole <code>MSG</code> struct passed through anymore, just the members that Win32 has decided we are allowed. In the case of <code>WM_NCCREATE</code> the <code>w_param</code> is empty, and the <code>l_param</code> contains an instance of <code>CREATESTRUCT</code>, all we need to know about <code>CREATESTRUCT</code> is that it&rsquo;s members are identical to the parameters of <code>CreateWindow</code>. So we have access to all the values passed in to <code>CreateWindow</code> when we recieve the <code>WM_NCCREATE</code> message.</p>
</blockquote>
<p>Update the <code>staticWindowsProcedure</code> so it looks like this:</p>
<pre><code> #!c++
LRESULT CALLBACK GLWindow::staticWindowsProcedure(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
) {
if(message == WM_NCCREATE)
{
// Cast the l_param to a CREATESTRUCT
CREATESTRUCT* create_struct = reinterpret_cast&lt;CREATESTRUCT*&gt;(l_param);
// cast the lpCreateParams member to a LONG_PTR
LONG_PTR this_window_ptr = reinterpret_cast&lt;LONG_PTR&gt;(create_struct-&gt;lpCreateParams);
// Set the pointer to the GLWindow instance as the user data for the window
SetWindowLongPtr(handle, GWLP_USERDATA, this_window_ptr);
}
return DefWindowProc(handle, message, w_param, l_param);
}
</code></pre>
<p>This will now catch any <code>WM_NCCREATE</code> messages associate with the window and make sure that a pointer to the correct instance of <code>GLWindow</code> is associated with it. To do this it first casts the <code>l_param</code> to <code>CREATESTRUCT</code>, accesses the <code>lpCreateParams</code> member of the <code>CREATESTRUCT</code> struct, casts it to a <code>LONG_PTR</code> and then assigns it using <code>SetWindowLongPtr</code>.<br />
The <code>SetWindowLongPtr</code> method takes three parameters:</p>
<ol>
<li><code>HWND hWnd</code>
<ol>
<li>The handle to the window you want to assign data for.</li>
</ol></li>
<li><code>int nIndex</code>
<ol>
<li>The offset to the value to be assigned (we are using <code>GWLP_USERDATA</code>)</li>
</ol></li>
<li><code>LONG_PTR swNewLong</code>
<ol>
<li>The data being assigned.</li>
</ol></li>
</ol>
<p>In order for this to work correctly though we need to alter the code inside <code>GLWindow::createApplicationWindow</code>. First we change the line:</p>
<pre><code class="language-c++">wcex.lpfnWndProc = DefWinProc;
</code></pre>
<p>to:</p>
<pre><code class="language-c++">wcex.lpfnWndProc = staticWindowsProcedure;
</code></pre>
<p>and then we change the last parameter of the call to <code>CreateWindow</code> from <code>NULL</code> to <code>this</code>:</p>
<pre><code class="language-c++">m_window_handle = CreateWindow(
L&quot;tutorial_2_window&quot;,
L&quot;Tutorial 2&quot;,
WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
200,
200,
600,
400,
NULL,
NULL,
instance,
this
);
</code></pre>
<blockquote>
<p>I also took this opportunity to update the <code>lpszClassName</code> and window title to reflect the fact this was tutorial 2.</p>
</blockquote>
<p>Next we need to define a non-static windows procedure that will do all the actual heavy lifting. In the GLWindow header declare a method:</p>
<pre><code class="language-c++">LRESULT winProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
</code></pre>
<p>and in the .cpp just declare it as:</p>
<pre><code class="language-c++">LRESULT CALLBACK GLWindow::windowsProcedure(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
) {
return DefWindowProc(handle, message, w_param, l_param);
}
</code></pre>
<p>For the moment we will just leave it returning <code>DefWinProc</code> and will come back to it in a bit.</p>
<p>Inside <code>GLWindow::staticWinProc</code> we have already dealt with the <code>WM_NCCREATE</code> message, but now we want to forward any other messages to our non-static method.<br />
To do this we get the pointer to the <code>GLWindow</code> instance that we stored on the <code>WM_NCCREATE</code> message. This uses the method <code>GetWindowLongPtr</code> which is the complimentery method to <code>SetWindowLongPtr</code>, we pass in the handle to the window and the offset to the data we want, we assigned to <code>GWLP_USERDATA</code> so we fetch from there as well. The method <code>GetWindowLongPtr</code> has the following signature:</p>
<pre><code class="language-c++">LONG_PTR WINAPI GetWindowLongPtr(
HWND hWnd, // The window handle we want to get info from (we pass in the one passed into the procedure)
int nIndex // The offset (GWLP_USERDATA) in our case
);
</code></pre>
<p>The <code>LONG_PTR</code> it returns is the data we are interested in, if the method returns <code>0</code> then the method failed for whatever reason. To use it add the following condition into the <code>GLWindow::staticWindowsProcedure</code> method directly after the check for <code>WM_CREATE</code>:</p>
<pre><code class="language-c++">if(message == WM_NCCREATE)
{
// ...
}
if(auto user_data = GetWindowLongPtr(handle, GWLP_USERDATA)) // `=` returns value assigned if method fails this will be 0(false)
{
// Once it's fully created pass on messages to non-static member function.
auto this_window = reinterpret_cast&lt;GLWindow*&gt;(user_data);
return this_window-&gt;windowsProcedure(handle, message, w_param, l_param);
}
</code></pre>
<h4 id="keyboard-support:77c229d7c1b16e8e4f5d866aedb29d3f">Keyboard Support</h4>
<p>Now we can add a simple test to the non static windows procedure just to make sure everything is working.<br />
Add:</p>
<pre><code class="language-c++">switch(message)
{
// Key-down event
case WM_KEYDOWN:
{
std::cout &lt;&lt; &quot; Key: &quot; &lt;&lt; char(w_param) &lt;&lt; &quot; pressed&quot; &lt;&lt; std::endl;
break;
}
// Key-up event
case WM_KEYUP:
{
std::cout &lt;&lt; &quot; Key: &quot; &lt;&lt; char(w_param) &lt;&lt; &quot; released&quot; &lt;&lt; std::endl;
break;
}
default:
break;
}
}
</code></pre>
<p>to the start of <code>GLWindow::windowsProcedure</code>. If we run the program now and tap the &lsquo;Q&rsquo; key, we&rsquo;ll see something like:</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial2/image_2.PNG" />
</figure>
<p>If you keep the key pressed then release it you&rsquo;ll see:</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial2/image_3.PNG" />
</figure>
<p>Obviously this isn&rsquo;t entirely correct. Ideally we want to be able to differentiate between key-down and key-repeat events. The reason we get what we currently get is that <code>WM_KEYDOWN</code> will auto-repeat if the key is held long enough. Luckily the <code>l_param</code> of a <code>WM_KEYDOWN</code> message can tell us the previous keystate. For <code>WM_KEYDOWN</code> the <code>l_param</code> will hold the following values:</p>
<p>| Bits | Data |
|&ndash;|&ndash;|
| 0-15 | The repeat count for auto-repeat |
| 16-23 | The scan code (Don&rsquo;t worry about this) |
| 24 | 1 if its an extended key (i.e Right-Ctrl, or Right-Alt) |
| 25-28 | Reserved |
| 29 | Context Code (Always 0) |
| 30 | Previous key state; 1 if key was already down otherwise 0 |
| 31 | Transition state (Always 0) |</p>
<p>Looking at this it&rsquo;s clear we can check the bit associated with the previous key state to check for key-repeats. We could do this with some wonderful bit arithmetic but I prefer to use the <code>std::bitset</code> functionality. So we change the <code>WM_KEYDOWN</code> case to look like:</p>
<pre><code class="language-c++">// Key-down event
case WM_KEYDOWN:
{
if(std::bitset&lt;32&gt;(l_param).test(30))
{
std::cout &lt;&lt; &quot; Key: &quot; &lt;&lt; char(w_param) &lt;&lt; &quot; repeating&quot; &lt;&lt; std::endl;
}
else
{
std::cout &lt;&lt; &quot; Key: &quot; &lt;&lt; char(w_param) &lt;&lt; &quot; pressed&quot; &lt;&lt; std::endl;
}
break;
}
</code></pre>
<p>If we run the program now we should see something like:</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial2/image_4.PNG" />
</figure>
<p>Obviously we don&rsquo;t actually want to put all our input handling inside the <code>GLWindow</code>class, so we need a way to inform the rest of the application about the inputs that are recieved. To do this we&rsquo;ll store each input we care about and pass it through to the rest of the application. One way to do this is to change the update-loop in our <code>main</code> to look something like:</p>
<pre><code class="language-c++">while(test_window.run())
{
// Process Inputs
InputEvent e;
while (test_window.popEvent(e))
{
// Do things with 'e'
printEvent(e);
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
// Your OpenGLCode here...
test_window.swapBuffers();
}
</code></pre>
<p>What is an <code>InputEvent</code>? An <code>InputEvent</code> is a struct that we will define which will store input events in a way more suited to our purposes than raw windows messages.</p>
<p>To define <code>InputEvent</code> first create a header file called <em>InputEvent.h</em> and define a struct like:</p>
<pre><code class="language-c++">struct InputEvent
{
}
</code></pre>
<p>For the moment we are going to limit ourselves to keyboard events, so all the struct needs is a member to hold the key identifier, and a member holding the action (pressed, repeating or released). To hold these we are going to define two enums. One for holding the key value (which key was pressed) and the other for the state. So in <em>InputEvent.h</em> add the following before the struct:</p>
<pre><code class="language-c++">enum class key_state
{
e_pressed,
e_repeat,
e_released
};
enum class key_code
{
e_unknown = 0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Num0,
Num1,
Num2,
Num3,
Num4,
Num5,
Num6,
Num7,
Num8,
Num9,
Escape,
LControl,
LShift,
LAlt,
LSystem,
RControl,
RShift,
RAlt,
RSystem,
Menu,
LBracket,
RBracket,
SemiColon,
Comma,
Period,
Quote,
Slash,
BackSlash,
Tilde,
Equal,
Dash,
Space,
Return,
BackSpace,
Tab,
PageUp,
PageDown,
End,
Home,
Insert,
Delete,
Add,
Subtract,
Multiply,
Divide,
Left,
Right,
Up,
Down,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
Pause
};
</code></pre>
<p>and then update the struct to look like:</p>
<pre><code class="language-c++">struct InputEvent
{
key_code key;
key_state state;
};
</code></pre>
<p>And thats our basic event defined. (It will get more complex later).</p>
<p>In <em>GLWindow.h</em>, add:</p>
<pre><code class="language-c++">std::deque&lt;InputEvent&gt; m_events;
</code></pre>
<p>to the private members (remember to add update the #includes), and add two method declerations:</p>
<pre><code class="language-c++">
// In the public visibility section
bool popEvent(
InputEvent&amp; e
);
// In the private section
key_code readKey(
WPARAM key,
LPARAM flags
);
</code></pre>
<p>In the source file define <code>GLWindow::popEvent</code> as:</p>
<pre><code class="language-c++">bool GLWindow::popEvent(InputEvent&amp; e) {
if(m_events.empty())
{
return false;
}
else
{
e = m_events.front();
m_events.pop_front();
}
return true;
}
</code></pre>
<p>This method will now allow access to any input events the window has stored.</p>
<p>The main concern now is that we have to translate the windows keycodes into our own enum (<code>key_code</code>). The reason we do this is that windows keycodes are akward to work with and also gives us a head start if we want to start porting our projects to other platforms.
To do this we define the <code>GLWindow::readKey</code> as:</p>
<pre><code class="language-c++">key_code GLWindow::readKey(
WPARAM key,
LPARAM flags // We will use this parameter later
) {
switch(key)
{
case VK_LWIN: return key_code::LSystem;
case VK_RWIN: return key_code::RSystem;
case VK_APPS: return key_code::Menu;
case VK_OEM_1: return key_code::SemiColon;
case VK_OEM_2: return key_code::Slash;
case VK_OEM_PLUS: return key_code::Equal;
case VK_OEM_MINUS: return key_code::Dash;
case VK_OEM_4: return key_code::LBracket;
case VK_OEM_6: return key_code::RBracket;
case VK_OEM_COMMA: return key_code::Comma;
case VK_OEM_PERIOD: return key_code::Period;
case VK_OEM_7: return key_code::Quote;
case VK_OEM_5: return key_code::BackSlash;
case VK_OEM_3: return key_code::Tilde;
case VK_ESCAPE: return key_code::Escape;
case VK_SPACE: return key_code::Space;
case VK_RETURN: return key_code::Return;
case VK_BACK: return key_code::BackSpace;
case VK_TAB: return key_code::Tab;
case VK_PRIOR: return key_code::PageUp;
case VK_NEXT: return key_code::PageDown;
case VK_END: return key_code::End;
case VK_HOME: return key_code::Home;
case VK_INSERT: return key_code::Insert;
case VK_DELETE: return key_code::Delete;
case VK_ADD: return key_code::Add;
case VK_SUBTRACT: return key_code::Subtract;
case VK_MULTIPLY: return key_code::Multiply;
case VK_DIVIDE: return key_code::Divide;
case VK_PAUSE: return key_code::Pause;
case VK_F1: return key_code::F1;
case VK_F2: return key_code::F2;
case VK_F3: return key_code::F3;
case VK_F4: return key_code::F4;
case VK_F5: return key_code::F5;
case VK_F6: return key_code::F6;
case VK_F7: return key_code::F7;
case VK_F8: return key_code::F8;
case VK_F9: return key_code::F9;
case VK_F10: return key_code::F10;
case VK_F11: return key_code::F11;
case VK_F12: return key_code::F12;
case VK_F13: return key_code::F13;
case VK_F14: return key_code::F14;
case VK_F15: return key_code::F15;
case VK_LEFT: return key_code::Left;
case VK_RIGHT: return key_code::Right;
case VK_UP: return key_code::Up;
case VK_DOWN: return key_code::Down;
case VK_NUMPAD0: return key_code::Numpad0;
case VK_NUMPAD1: return key_code::Numpad1;
case VK_NUMPAD2: return key_code::Numpad2;
case VK_NUMPAD3: return key_code::Numpad3;
case VK_NUMPAD4: return key_code::Numpad4;
case VK_NUMPAD5: return key_code::Numpad5;
case VK_NUMPAD6: return key_code::Numpad6;
case VK_NUMPAD7: return key_code::Numpad7;
case VK_NUMPAD8: return key_code::Numpad8;
case VK_NUMPAD9: return key_code::Numpad9;
case 'A': return key_code::A;
case 'B': return key_code::B;
case 'C': return key_code::C;
case 'D': return key_code::D;
case 'E': return key_code::E;
case 'F': return key_code::F;
case 'G': return key_code::G;
case 'H': return key_code::H;
case 'I': return key_code::I;
case 'J': return key_code::J;
case 'K': return key_code::K;
case 'L': return key_code::L;
case 'M': return key_code::M;
case 'N': return key_code::N;
case 'O': return key_code::O;
case 'P': return key_code::P;
case 'Q': return key_code::Q;
case 'R': return key_code::R;
case 'S': return key_code::S;
case 'T': return key_code::T;
case 'U': return key_code::U;
case 'V': return key_code::V;
case 'W': return key_code::W;
case 'X': return key_code::X;
case 'Y': return key_code::Y;
case 'Z': return key_code::Z;
case '0': return key_code::Num0;
case '1': return key_code::Num1;
case '2': return key_code::Num2;
case '3': return key_code::Num3;
case '4': return key_code::Num4;
case '5': return key_code::Num5;
case '6': return key_code::Num6;
case '7': return key_code::Num7;
case '8': return key_code::Num8;
case '9': return key_code::Num9;
}
return key_code::e_unknown;
}
</code></pre>
<p>To store them update the <code>GLWindow::windowsProcedure</code> to look like:</p>
<pre><code class="language-c++">LRESULT CALLBACK GLWindow::windowsProcedure(
HWND handle,
UINT message,
WPARAM w_param,
LPARAM l_param
) {
switch(message)
{
// Key-down event
case WM_KEYDOWN:
{
InputEvent e;
if(std::bitset&lt;32&gt;(l_param).test(30))
{
e.key = readKey(w_param, l_param);
e.state = key_state::e_repeat;
}
else
{
e.key = readKey(w_param, l_param);
e.state = key_state::e_pressed;
}
m_events.push_back(e);
break;
}
// Key-up event
case WM_KEYUP:
{
InputEvent e;
e.key = readKey(w_param, l_param);
e.state = key_state::e_released;
m_events.push_back(e);
break;
}
default:
break;
}
return DefWindowProc(handle, message, w_param, l_param);
}
</code></pre>
<p>To test this we will add a method to the <em>main.cpp</em> <code>printEvent</code> which we will define as:</p>
<pre><code class="language-c++">void printEvent(
const InputEvent&amp; e
) {
std::string key;
std::string state;
switch(e.key)
{
case key_code::Q:
key = &quot;Q&quot;;
break;
case key_code::A:
key = &quot;A&quot;;
break;
}
switch(e.state)
{
case key_state::e_pressed:
state = &quot;pressed&quot;;
break;
case key_state::e_released:
state = &quot;released&quot;;
break;
case key_state::e_repeat:
state = &quot;repeating&quot;;
break;
}
std::cout &lt;&lt; &quot;Key: &quot; &lt;&lt; key &lt;&lt; &quot; &quot; &lt;&lt; state &lt;&lt; std::endl;
}
</code></pre>
<p>You will need to add <code>#include &lt;string&gt;</code> as well.</p>
<p>So what is the <code>LPARAM</code> parameter in the <code>GLWindow::readKeyState</code> method for? Well it allows us to differentiate between left and right versions of the <em>Ctrl</em>, <em>Shift</em> and <em>Alt</em> keys.</p>
<p>For the <em>Ctrl</em> keys this is as straightforward as adding the following case to the <code>switch</code> statement.</p>
<pre><code class="language-c++">
// Check the &quot;extended&quot; flag to distinguish between left and right control
case VK_CONTROL: return (HIWORD(flags) &amp; KF_EXTENDED) ? key_code::RControl : key_code::LControl;
</code></pre>
<p>The <em>Shift</em> case is slightly more complex as it doesn&rsquo;t use the extended flag, rather it uses the scan code. What you need to do is add the following case to the <code>switch</code>:</p>
<pre><code class="language-c++">case VK_SHIFT:
{
// Check the scancode to distinguish between left and right shift
// Map the VK_LSHIFT to the scan code for lshift
static auto lShift = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC);
// Get the bits 16-23 (the scancode) and cast to a UINT
auto scancode = static_cast&lt;UINT&gt;((flags &amp; (0xFF &lt;&lt; 16)) &gt;&gt; 16);
// If the scancode is equal to the VK_LSHIFT scancode mapping then this is the l-shift
// otherwise it's the right-shift
return scancode == lShift ? key_code::LShift : key_code::RShift;
}
</code></pre>
<p>The <em>Alt</em> case is slightly more akward again. Like the <em>Ctrl</em> case it takes the simpler form of:</p>
<pre><code class="language-c++">// Check the &quot;extended&quot; flag to distinguish between left and right alt
case VK_MENU: return (HIWORD(flags) &amp; KF_EXTENDED) ? key_code::RAlt : key_code::LAlt;
</code></pre>
<p>However <em>Alt</em> keys require a <code>WM_SYSKEYDOWN</code> or <code>WM_SYSKEYUP</code> message to have been generated (<em>F10</em> also requires these messages), so we need to alter the <code>GLWindow::windowsProcedure</code> method to cater for these.</p>
<pre><code class="language-c++">//...
// Key-down event
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
InputEvent e;
if(std::bitset&lt;32&gt;(l_param).test(30))
{
e.key = readKey(w_param, l_param);
e.state = key_state::e_repeat;
}
else
{
e.key = readKey(w_param, l_param);
e.state = key_state::e_pressed;
}
m_events.push_back(e);
break;
}
// Key-up event
case WM_KEYUP:
case WM_SYSKEYUP:
{
InputEvent e;
e.key = readKey(w_param, l_param);
e.state = key_state::e_released;
m_events.push_back(e);
break;
}
//...
</code></pre>
<p>Even with this the <em>Alt</em> keys may not be as stable, for instance on keyboards with an <em>Alt Gr</em> key, pressing it will generate a <em>Right-Alt</em> and a <em>Left-Ctrl</em>.<br />
Adding the following will allow the test method in main.cpp to confirm that these are working:</p>
<pre><code class="language-c++">switch(e.key)
{
///...
case key_code::LShift:
key = &quot;Left Shift&quot;;
break;
case key_code::RShift:
key = &quot;Right Shift&quot;;
break;
case key_code::LAlt:
key = &quot;Left Alt&quot;;
break;
case key_code::RAlt:
key = &quot;Right Alt&quot;;
break;
case key_code::LControl:
key = &quot;Left Ctrl&quot;;
break;
case key_code::RControl:
key = &quot;Right Ctrl&quot;;
break;
}
</code></pre>
<h4 id="mouse-support:77c229d7c1b16e8e4f5d866aedb29d3f">Mouse Support</h4>
<p>Now we have support for key&rsquo;s lets look at supporting mouse input as well.
The first thing we have to do though is extend our <code>InputEvent</code> struct.<br />
Currently our <code>InputEvent</code> holds a <code>key_code</code> and a <code>key_state</code>, we could just add new mouse specific fields such as mouse position and fill in the relevant sections for each different event time. That would leave us with a structure that was larger than it had to be, and would grow for every event type that we introduced.<br />
Instead we will extract the existing members of <code>InputEvent</code> anto a new struct called <code>key_args</code> and then define a similar struct for the mouse arguments (this gets a bit complex):</p>
<pre><code class="language-c++">//...
//...
struct key_args
{
key_code key;
key_state state;
};
struct bit_helper
{
unsigned short ctrl : 1;
unsigned short l_button : 1;
unsigned short m_button : 1;
unsigned short r_button : 1;
unsigned short shift : 1;
unsigned short x1_button : 1;
unsigned short x2_button : 1;
};
using move_modifier_flag = std::bitset&lt;7&gt;;
struct mouse_move_modifier
{
union
{
move_modifier_flag bits;
bit_helper named_keys;
};
};
struct mouse_move_args
{
int mouse_x;
int mouse_y;
mouse_move_modifier move_modifier;
};
InputEvent
{
};
//...
//...
</code></pre>
<p>Basically <code>mouse_move_modifier</code> is a struct that contains a bitset that stores modifiers(such as which mouse button is also pressed, or if <em>Ctrl</em> or <em>Shift</em> is pressed). I&rsquo;ve created it as a union of a <code>std::bitset&lt;7&gt;</code> and <code>bit_helper</code> where <code>bit_helper</code> is just a convenience to access the bits by name.</p>
<p>we will also define an enum called <code>event_type</code> (place it at near the beginning of <em>InputEvent.h</em> withthe other enums):</p>
<pre><code class="language-c++">enum class event_type
{
e_key_event,
e_mouse_event
};
</code></pre>
<p>Finally we can redefine <code>InputEvent</code> as:</p>
<pre><code class="language-c++"> struct InputEvent
{
InputEvent() {}; // To stop errorC2280(https://msdn.microsoft.com/en-us/library/bb531344.aspx#BK_compiler)
event_type m_event_type;
union
{
key_args m_key_args;
mouse_args m_mouse_args;
};
};
</code></pre>
<p>using a union to hold the event specific values allows us to save space and present a cleaner interface.<br />
In order to differentiate between keyboard and mouse eventsthe <code>GLWindow::windowsProcedure</code> method will also need updated so that it handles the new <code>InputEvent</code> interface.</p>
<pre><code class="language-c++">// Key-down event
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
InputEvent e;
e.m_event_type = event_type::e_key_event;
if(std::bitset&lt;32&gt;(l_param).test(30))
{
e.m_key_args.key = readKey(w_param, l_param);
e.m_key_args.state = key_state::e_repeat;
}
else
{
e.m_key_args.key = readKey(w_param, l_param);
e.m_key_args.state = key_state::e_pressed;
}
m_events.push_back(e);
break;
}
// Key-up event
case WM_KEYUP:
case WM_SYSKEYUP:
{
InputEvent e;
e.m_event_type = event_type::e_key_event;
e.m_key_args.key = readKey(w_param, l_param);
e.m_key_args.state = key_state::e_released;
m_events.push_back(e);
break;
}
</code></pre>
<p>and change the <code>switch</code> statement in the <code>printEvent</code> test method in <em>main.cpp</em> to use the correct value as well:</p>
<pre><code class="language-c++">//...
switch(e.m_key_args.key)
{
//...
//...
}
switch(e.m_key_args.state)
{
//...
//...
}
</code></pre>
<p>Now we&rsquo;ll look at actually dealing with mouse movement.<br />
When the mouse is moved a windows message is geberated with type <code>WM_MOUSEMOVE</code>, like all messages it comes with a <code>WPARAM</code> and an <code>LPARAM</code>. For <code>WM_MOUSEMOVE</code> these parameters hold the following information:</p>
<ul>
<li><code>WPARAM</code>
<ul>
<li>A Bitset that stores the following state.</li>
</ul></li>
</ul>
<p>| Value | Meaning |
|&mdash;&mdash;&mdash;&mdash;&mdash;&ndash;|
| <code>MK_CONTROL</code> | Ctrl Key is down |
| <code>MK_LBUTTON</code> | Left Mouse Button is down |
| <code>MK_MBUTTON</code> | Middle Mouse Button is down |
| <code>MK_RBUTTON</code> | Right Mouse Button is down |
| <code>MK_SHIFT</code> | Shift Key is down |
| <code>MK_XBUTTON1</code> | First X button is down |
| <code>MK_XBUTTON2</code> | Second X Button is down |</p>
<ul>
<li><code>LPARAM</code>
<ul>
<li>The low word holds the x-coordinate of the mouse</li>
<li>the high word holds the y-coordinate of the mouse</li>
</ul></li>
</ul>
<p>Add the following case to the <code>switch</code> statement in the <code>GLWindow::windowsProcedure</code> method:</p>
<pre><code class="language-c++">// Mouse move event
case WM_MOUSEMOVE :
{
InputEvent e;
e.m_event_type = event_type::e_mouse_event;
e.m_mouse_args.mouse_x = GET_X_LPARAM(l_param);
e.m_mouse_args.mouse_y = GET_Y_LPARAM(l_param);
e.m_mouse_args.move_modifier.bits = GetModifier(w_param);
m_events.push_back(e);
break;
}
</code></pre>
<p>The <code>GetModifier</code> is a helper method that translates the <code>WPARAM</code> into a <code>move_modifier_flag</code>. It is fairly straightforward and as it&rsquo;s esentially a private helper I have defined it in an anonymous namespace at the top of the cpp file This stops it cluttering up the header file and hides implementation details from the user. It is defined as:</p>
<pre><code>#!C++
namespace
{
move_modifier_flag GetModifier(WPARAM w_param) {
move_modifier_flag ret_val;
if(w_param &amp; MK_CONTROL)
{
ret_val.set(0, 1);
}
if(w_param &amp; MK_LBUTTON)
{
ret_val.set(1, 1);
}
if(w_param &amp; MK_MBUTTON)
{
ret_val.set(2, 1);
}
if(w_param &amp; MK_RBUTTON)
{
ret_val.set(3, 1);
}
if(w_param &amp; MK_SHIFT)
{
ret_val.set(4, 1);
}
if(w_param &amp; MK_XBUTTON1)
{
ret_val.set(5, 1);
}
if(w_param &amp; MK_XBUTTON2)
{
ret_val.set(6, 1);
}
return ret_val;
}
}
</code></pre>
<p>And in the main.cpp alter the <code>while</code> loop that pops each event to look like:</p>
<pre><code class="language-c++">while (test_window.popEvent(e))
{
// Do things with 'e'
switch(e.m_event_type)
{
case event_type::e_key_event:
printEvent(e);
break;
case event_type::e_mouse_event:
testMouse(e);
break;
}
}
</code></pre>
<p>Define the <code>testMouse</code> method in main as:</p>
<pre><code class="language-c++">void testMouse(
const InputEvent&amp; e
) {
if(e.m_mouse_args.move_modifier.named_keys.l_button &amp;&amp; e.m_mouse_args.move_modifier.named_keys.r_button)
{
std::cout &lt;&lt; &quot;Left &amp; Right drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.l_button &amp;&amp; e.m_mouse_args.move_modifier.named_keys.shift)
{
std::cout &lt;&lt; &quot;Shift &amp; Left drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.ctrl)
{
std::cout &lt;&lt; &quot;Ctrl drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.l_button)
{
std::cout &lt;&lt; &quot;Left drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.m_button)
{
std::cout &lt;&lt; &quot;Middle drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.r_button)
{
std::cout &lt;&lt; &quot;Right drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else if(e.m_mouse_args.move_modifier.named_keys.shift)
{
std::cout &lt;&lt; &quot;Shift drag &quot; &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
else
{
std::cout &lt;&lt; &quot;mouse X: &quot; &lt;&lt; e.m_mouse_args.mouse_x &lt;&lt; &quot; mouse Y:&quot; &lt;&lt; e.m_mouse_args.mouse_y &lt;&lt; std::endl;
}
}
</code></pre>
<p>If you run the program now you can try various mouse moves/drags and should see something like:</p>
<figure >
<img src="http://keithgrant.co.uk/img/Tutorial2/image_5.PNG" />
</figure>
<h4 id="further-work:77c229d7c1b16e8e4f5d866aedb29d3f">Further Work</h4>
<p>This tutorial just covers the bare minimum to capture keyboard and mouse events, your event system could be extended to detect any other message such as when the window gains/loses focus. I&rsquo;m trying to keep these tutorials as non-specific as possible so that the anyone who comes across them can modify them easily to their own needs.</p>
Tutorial2.5 (An alternative method for user input)http://keithgrant.co.uk/tutorials/Tutorial2_5/
Mon, 01 Jan 0001 00:00:00 +0000http://keithgrant.co.uk/tutorials/Tutorial2_5/
<h4 id="background:5a39c40b3ee5cea2fcee1a7dc55c13ff">Background</h4>
<p>In [Tutorial2]http://keithgrant.co.uk/tutorials/Tutorial2/ I outlined how to add basic input handling to an application, in particular one using the OpenGLContext from [Tutorial1]http://keithgrant.co.uk/tutorials/Tutorial1/. This method is fine and you can pass the event that is generated down into whatever you application you create very easily, the main loop would look something like:</p>
<pre><code>#!C++
while(my_app.run())
{
// Process Inputs
InputEvent e;
while (test_window.popEvent(e))
{
my_app-&gt;processInputs(e);
}
my_app-&gt;update();
my_app-&gt;draw();
}
</code></pre>
<p>this is fine I suppose, but it does impose some restrictions. Any place you want to interact, you have to wire the event down through your heirarchy to get to it. So I prefer to use a listener based system.</p>
<h4 id="create-a-handler:5a39c40b3ee5cea2fcee1a7dc55c13ff">Create a &lsquo;Handler&rsquo;</h4>
<p>To start with we are going to create a class that will handle any <code>InputEvent</code> that our window produces. For this tutorial we&rsquo;ll just go ahead and declare it in the same file as the <code>InputEvent</code> itself.</p>
<pre><code>#!c++
class InputEventHandler
{
public: // Internal (only used within the context, not used by the application)
static InputEventHandler&amp; getEventHandler();
~InputEventHandler() = default;
void handleEvent(InputEvent e);
public: // External (This is the only thing an application would care about)
void addListener(event_type e, WindowsInputDelegate listener);
void removeListener(event_type e, WindowsInputDelegate listener);
private: // Implementation details only
InputEventHandler() = default;
WindowsEventMap m_windows_event_map;
};
</code></pre>
<p>This is made up two main sections; the first <code>Internal</code> section contains a default constructor and destructor as well as one method that is called to handle a specific event type and once set up will not be needed by the application developer, the second section would be the &ldquo;public&rdquo; api of the class. By this I mean that it would be the only section that day to day users should be interested in.</p>
<p>Essentially what this class does is it store a multi-map of methods to event types, this will store a list of methods(listeners) that should be called for each event type (mouse or keyboard). This will be updated by the <code>addListener(...)</code> and removeListener(&hellip;) methods, when each event is generated, all methods associated with that event will be triggered.</p>
<h5 id="details:5a39c40b3ee5cea2fcee1a7dc55c13ff">Details</h5>
<p>As you can see the class depends on a couple of other types, namely <code>WindowsInputDelegate</code> and <code>delegate_entry</code>, so I&rsquo;ll give you a quick rundown on what they are.<br />
<code>WindowsInputDelagate</code> is an alias for templated type <code>Delegate&lt;void, InputEvent&gt;</code>, The delegate type is my own custom type that has similar functionality to <code>std::function</code>, it&rsquo;s not as fully-featured (for instance it doesn&rsquo;t support lambdas), but for free, static and class methods it works well. The reason I use this rather than <code>std::function</code> is that it does implement the <code>==</code> operator. This allows us to remove listeners more simply. I&rsquo;m not going to go into the detail of how it works here. but I&rsquo;ll include the full source for it at the end. Essentially you can assign any method with the signature <code>void myMethod(InputEvent param)</code> to it. This is done using various overloads of <code>Delegate&lt;&gt;::create()</code>.<br />
<code>delegate_entry</code> is a simple struct that stores a <code>WindowsInputDelegate</code> and a <code>bool</code>.</p>
<pre><code>#!c++
using WindowsInputDelegate = Delegate&lt;void, InputEvent&gt;;
struct delegate_entry
{
WindowsInputDelegate delegate;
bool active;
};
</code></pre>
<p>To implement the <code>InputEventHandler</code> we add a new file, <code>InputEvent.cpp</code> and populate it as follows:</p>
<pre><code>#!c++
#include &quot;stdafx.h&quot;
#include &quot;InputEvent.h&quot;
#include &lt;utility&gt;
InputEventHandler&amp; InputEventHandler::getEventHandler() {
static InputEventHandler instance;
return instance;
}
void InputEventHandler::handleEvent(InputEvent e) {
auto&amp; range = m_windows_event_map.equal_range(e.m_event_type);
for(auto&amp; it = range.first; it != range.second; )
{
if(it-&gt;second.active)
{
it-&gt;second.delegate(e);
++it;
}
else
{
it = m_windows_event_map.erase(it);
}
}
}
void InputEventHandler::addListener(event_type e, WindowsInputDelegate listener) {
auto entry = std::pair&lt;event_type, delegate_entry&gt;(e, {listener, true});
m_windows_event_map.insert(entry);
}
void InputEventHandler::removeListener(event_type e, WindowsInputDelegate listener) {
auto&amp; range = m_windows_event_map.equal_range(e);
for(auto&amp; it = range.first; it != range.second;)
{
if(it-&gt;second.delegate == listener)
{
it-&gt;second.active = false;
}
++it;
}
}
</code></pre>
<p>This is fairly straightforward, basically <code>InputEventHandler::addListener(...)</code> and <code>InputEventHandler::removeListener(...)</code> are used to add/remove methods to the <code>m_windows_event_map</code>, and the <code>InputEventHandler::handleEvent(InputEvent e)</code> takes the event <code>e</code> and calls all listeners associated with it.</p>
<p>You may notice this <code>InputEventHandler</code> is a singleton, before anyone gets all stroppy about this, remember this is just a small example program, in a bigger system I <em>might</em> try to avoid this.</p>
<p>Also note the way that <code>removeListener</code> doesn&rsquo;t actually remove the listener, rather it marks it inactive, this mitigates the case where one of the listeners that is registered might execute some code that would cause a seperate listener to be removed. For example a method might destroy an instance of a class, and that instance may have had one of it&rsquo;s members registered as a listener, if it was removed straight away then the maps iterators would get all screwy and nobody wants that, rather we mark it as inactive so thatr next time we try and access it we see its inactive and remove it from the list then.</p>
<h4 id="update-our-glwindow-class:5a39c40b3ee5cea2fcee1a7dc55c13ff">Update our GLWindow class</h4>
<p>In order to use our new system we need to make some minor changes to the <code>GLWindow</code> class. Basically we remove the <code>GLWindow::popEvent()</code> method, and also the <code>m_events</code> member.<br />
Then in the <code>GLWindow::windowsProcedure</code> method we replace the calls to <code>m_events.push_back(e)</code>, with <code>InputEventHandler::getEventHandler().handleEvent()</code>.</p>
<p>Finally in order to test this functionality we&rsquo;ll replace the various methods in <code>main.cpp</code> so that <code>main.cpp</code> looks like</p>
<pre><code>#!c++
// main.cpp : Defines the entry point for the console application.
//
#include &quot;stdafx.h&quot;
#include &quot;GLWindow.h&quot;
#include &lt;string&gt;
class test_app
{
public:
test_app(
) {
auto k = WindowsInputDelegate::create&lt;test_app, &amp;test_app::onKey&gt;(this);
InputEventHandler::getEventHandler().addListener(event_type::e_key_event, k);
auto m = WindowsInputDelegate::create&lt;test_app, &amp;test_app::onMouse&gt;(this);
InputEventHandler::getEventHandler().addListener(event_type::e_mouse_event, m);
}
~test_app(
) {
auto k = WindowsInputDelegate::create&lt;test_app, &amp;test_app::onKey&gt;(this);
InputEventHandler::getEventHandler().removeListener(event_type::e_key_event, k);
auto m = WindowsInputDelegate::create&lt;test_app, &amp;test_app::onMouse&gt;(this);
InputEventHandler::getEventHandler().removeListener(event_type::e_mouse_event, m);
}
void onKey(
InputEvent e
) {
if(e.m_key_args.key == key_code::A)
{
std::cout &lt;&lt; &quot;test_app key event&quot; &lt;&lt; std::endl;
}
}
void onMouse(
InputEvent e
) {
std::cout &lt;&lt; &quot;test_app mouse event&quot; &lt;&lt; std::endl;
}
static void onStaticKey(
InputEvent e
) {
if(e.m_key_args.key == key_code::S)
{
std::cout &lt;&lt; &quot;test_app static key event&quot; &lt;&lt; std::endl;
}
}
};
test_app* t;
void createTestApp(
InputEvent e
) {
if(e.m_key_args.key == key_code::O)
{
if(!t)
{
std::cout &lt;&lt; &quot;Creating test app&quot; &lt;&lt; std::endl;
t = new test_app();
}
}
}
void deleteTestApp(
InputEvent e
) {
if(e.m_key_args.key == key_code::P)
{
if(t)
{
std::cout &lt;&lt; &quot;deleting test app&quot; &lt;&lt; std::endl;
delete t;
t = nullptr;
}
}
}
void onFreeKey(
InputEvent e
) {
if(e.m_key_args.key == key_code::D)
{
std::cout &lt;&lt; &quot;free function key event&quot; &lt;&lt; std::endl;
}
}
int main()
{
GLWindow test_window;
auto s_k = WindowsInputDelegate::create&lt;&amp;test_app::onStaticKey&gt;();
InputEventHandler::getEventHandler().addListener(event_type::e_key_event, s_k);
auto f_k = WindowsInputDelegate::create&lt;&amp;onFreeKey&gt;();
InputEventHandler::getEventHandler().addListener(event_type::e_key_event, f_k);
InputEventHandler::getEventHandler().addListener(event_type::e_key_event, WindowsInputDelegate::create&lt;&amp;createTestApp&gt;());
InputEventHandler::getEventHandler().addListener(event_type::e_key_event, WindowsInputDelegate::create&lt;&amp;deleteTestApp&gt;());
while(test_window.run())
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
// Your OpenGLCode here...
test_window.swapBuffers();
}
return 0;
}
</code></pre>
<p>If we run the program now we can use the various keys to generate events, as well as using &lsquo;o&rsquo; and &lsquo;p&rsquo; to create and delete an object which in turn registers and unregisters additional listeners.</p>
<h4 id="final-thoughts:5a39c40b3ee5cea2fcee1a7dc55c13ff">Final Thoughts</h4>
<p>This is a very basic add-on to [Tutorial2]http://keithgrant.co.uk/tutorials/Tutorial2/, and should be seen more as a starting point rather than a reccomended way of doing <em>anything</em>, in particular I would like to use std::function rather than my own delegate type.<br />
There are areas to look at as well, the range of event types could be expanded so that there was a seperate event for each key-state (pressed, repeat and released) rather than one event type with additional parameters.<br />
The <code>InputEventHandler</code> could also queue the events for handling later rather than just dealing with them instantly, this may allow you to have more fine-grained control on the order or speed that things happen. This can be important when you&rsquo;re trying to run a game-loop on a tight timing budget.</p>
<h4 id="source-for-delegate-cpp:5a39c40b3ee5cea2fcee1a7dc55c13ff">Source for delegate.cpp</h4>
<pre><code>#!c++
#pragma once
template&lt;typename ret_type, typename... param_types&gt;
class Delegate
{
using Type = ret_type(*)(void*, param_types...);
public:
Delegate(void* inst, Type function):
m_instance(inst),
m_func(function)
{}
template&lt;typename T, ret_type(T::*TMethod)(param_types...)&gt;
static Delegate create(T* inst = nullptr)
{
Delegate ret_val(inst, &amp;callMethod&lt;T, TMethod&gt;);
return ret_val;
}
template&lt;ret_type(TMethod)(param_types...)&gt;
static Delegate create()
{
Delegate ret_val(nullptr, &amp;callMethod&lt;TMethod&gt;);
return ret_val;
}
ret_type operator() (param_types... args)
{
return (*m_func)(m_instance, args...);
}
bool Delegate::operator==(const Delegate &amp;other) const {
return (m_instance == other.m_instance) &amp;&amp;
(m_func == other.m_func);
}
private:
void* m_instance;
Type m_func;
template &lt;class T, ret_type(T::*TMethod)(param_types...)&gt;
static ret_type callMethod(void* inst, param_types... args)
{
T* p = static_cast&lt;T*&gt;(inst);
return (p-&gt;*TMethod)(args...);
}
template &lt;ret_type(TMethod)(param_types...)&gt;
static ret_type callMethod(void* inst, param_types... args)
{
return (*TMethod)(args...);
}
};
</code></pre>