Monday, 18 February 2013

WGL and the Wiggle Functions in C++

The simple fact that OpenGL is only a graphics API means that any user interaction, or things like screen and window issues needs to be handled by the operating system. Most operating systems come with a set of extensions that tie OpenGL together with user interaction and window management. This tends to make our lives a little bit easier, but only if we know how to take advantage of this option. In Windows this set of functions are called the wiggle functions. Each of these functions is prefixed with wgl.
To maintain the portability of OpenGL, each operating system must supply functionality for specifying the rendering window before OpenGL can use it. In Windows the Graphics Device Interface uses a device context to remember settings about drawing modes and commands, but in OpenGL the rendering context is used to remember these things. You need to remember, however, that the device context is not a rendering context. The device context MUST be specified in a GDI call, unlike a rendering context, which is specified in an OpenGL call.

If you wish to render more than one window at once, multiple rendering contexts are allowed. You must ensure, however, that the proper rendering context is being used on the proper window. Another thing to remember is that OpenGL is thread-safe. You can have multiple threads rendering to the same device context at one time.

As I mentioned to you earlier, wiggle functions bring Windows API support into OpenGL. Well let's take this time to have a look at a few common wiggle functions:
  • wglCreateContext();
  • wglDeleteContext();
  • wglMakeCurrent();
The wglCreateContext() function creates a handle to the OpenGL rendering context while being passed like a handle to a GDI device context. The correct prototype for this function is:
 HGLRC wglCreateContext(HDC hDC);
This function should only be called after the pixel format for the device context has been set. Don't worry, pixel format is coming into teaching shortly.

Keep in mind that as with a device context, a rendering context must be deleted after you are finished with it. This is where the wglDeleteContext() function comes into play. Let's have a look at the prototype for good measure:
 BOOL  wglDeleteContext(HGLRC hRC);
The name wglMakeCurrent() is highly accurate to what the function does. The function makes the rendering context passed to it the current rendering context. Makes a lot of sense, doesn't it? The device context used must have the same pixel format characteristics as the device context that was used to create the rendering context. This means that the device context used to create the rendering context does not need to be the same as the device context assigned to the wglMakeCurrent() function. Without further delay, let's see the prototype!
 BOOL  wglMakeCurrent(HDC hDC, HGLRC hRC);
You need to ensure that the device context and the rendering context passed to the function have the same pixel format, or the function will not work. To deselect the rendering context you can simply pass NULL for the hRC parameter, or simply pass another rendering context.

Both the wglCreateContext() and the wglMakeCurrent() functions should be called upon window creation. Let's look at a code snippet for an example of these in use:
LRESULT CALLBACK WndProc (HWND hwnd, UNIT message, WPARAM wParam, LPARAM lParam)
{
 static HGLRC hRC; //rendering context
 static HDC hDC; //device context
 switch (message)
 {
 case WM_CREATE:
 hDC = GetDC(hwnd); //get the device context for window
 hRC = wglCreateContext(hDC); //create rendering context
 wglMakeCurrent(hDC,hRC); //make rendering context current
 break;
 case WM_DESTROY:
 wglMakeCurrent(hDC,NULL); //deselect rendering context
 wglDeleteContext(hRC);  //delete rendering context
 PostQuitMessage(0);  //send wm_quit
 break;
 } } 
This code creates and destroys your OpenGL window. Before you actually do this, however, we must first discuss the PIXELFORMATDESCRIPTOR, which we will do in coming up lesson
Next: Getting Started Using OpenGL 
 

First Windows Application

The following code is a build up on the basic "hello world" program I showed you earlier. Take a minute to review it, maybe even key it into your compiler and run it, and then we will break it down so that it is easier to understand what is actually going on.
/* Trim fat from windows*/
#define WIN32_LEAN_AND_MEAN 
#pragma comment(linker, "/subsystem:windows")
/* Pre-processor directives*/
#include "stdafx.h"
#include <windows.h>
/* Windows Procedure Event Handler*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 PAINTSTRUCT paintStruct;
 /* Device Context*/
 HDC hDC; 
 /* Text for display*/
 char string[] = "Hello, World!"; 
 /* Switch message, condition that is met will execute*/
 switch(message)
 {
  /* Window is being created*/
  case WM_CREATE: 
   return 0;
   break;
  /* Window is closing*/
  case WM_CLOSE: 
   PostQuitMessage(0);
   return 0;
   break;
  /* Window needs update*/
  case WM_PAINT: 
   hDC = BeginPaint(hwnd,&paintStruct);
   /* Set txt color to blue*/
   SetTextColor(hDC, COLORREF(0x00FF0000));
   /* Display text in middle of window*/
   TextOut(hDC,150,150,string,sizeof(string)-1);
   EndPaint(hwnd, &paintStruct);
   return 0;
   break;
  default:
   break;
 }
 return (DefWindowProc(hwnd,message,wParam,lParam));
}
/* Main function*/
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
 WNDCLASSEX  windowClass;  //window class
 HWND  hwnd;    //window handle
 MSG   msg;    //message
 bool  done;    //flag saying when app is complete
 /* Fill out the window class structure*/
 windowClass.cbSize = sizeof(WNDCLASSEX);
 windowClass.style = CS_HREDRAW | CS_VREDRAW;
 windowClass.lpfnWndProc = WndProc;
 windowClass.cbClsExtra = 0;
 windowClass.cbWndExtra = 0;
 windowClass.hInstance = hInstance;
 windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
 windowClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 windowClass.lpszMenuName = NULL;
 windowClass.lpszClassName = "MyClass";
 windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
 /* Register window class*/
 if (!RegisterClassEx(&windowClass))
 {
  return 0;
 }
 /* Class registerd, so now create window*/
 hwnd = CreateWindowEx(NULL,  //extended style
  "MyClass",   //class name
  "A Real Win App",  //app name
  WS_OVERLAPPEDWINDOW |  //window style
  WS_VISIBLE |
  WS_SYSMENU,
  100,100,   //x/y coords
  400,400,   //width,height
  NULL,    //handle to parent
  NULL,    //handle to menu
  hInstance,   //application instance
  NULL);    //no extra parameter's
 /* Check if window creation failed*/
 if (!hwnd)
  return 0;
 done = false; //initialize loop condition variable
 /* main message loop*/
 while(!done)
 {
  PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE);
  if (msg.message == WM_QUIT) //check for a quit message
  {
   done = true; //if found, quit app
  }
  else
  {
   /* Translate and dispatch to event queue*/
   TranslateMessage(&msg); 
   DispatchMessage(&msg);
  }
 }
 return msg.wParam;
}
Well isn't this a whole mess of code! The first thing that you may have noticed is #define WIN32_LEAN_AND_MEAN. This syntax prevents Visual C++ from linking modules that you aren't going to need in your application.

Moving on we come to the include line #include <windows.h>. This includes all the headers you need in this application. Sometimes you may want to include <windowsx.h>, which will give you a few useful macros to use in Windows development.

The first function we arrive at is the WndProc function. We've discussed this before, so I am just going to highlight two lines I have added here.
HDC hDC;    //device context
Char string[] = "Hello World!"; //display text
These two lines are essential as they declare the device context that we are going to use to output to the window we create, and the string that we will display. As we continue on in the code we arrive at the switch statement. This switch is used to determine the message being passed to the windows procedure. In this particular instance we want to take a closer look at the WM_PAINT block.
case WM_PAINT: 
   hDC = BeginPaint(hwnd,&paintStruct);
   /* Set txt color to blue*/
   SetTextColor(hDC, COLORREF(0x00FF0000));
   /* Display text in middle of window*/
   TextOut(hDC,150,150,string,sizeof(string)-1);
   EndPaint(hwnd, &paintStruct);
   return 0;
   break;
When the window is moved, resized, or is otherwise changed the window needs to be updated. The first noticeable change here is the use of the hDC device context. The win32 function BeginPaint() returns the graphics device context for the hwnd passed to it. You can then use this hDC to set the text color with SetTextColor() and follow up with the TextOut() function to display the text. If you want to know more about these functions, check out MSDN.

Next function up is the WinMain() function. Most of the WinMain() content is pretty straight forward, but were going to review a few parts of it for good measure. The msg variable holds the message received by PeekMessage() from the queue, and will be sent to TranslateMessage() and DispatchMessage(). The variable done is a Boolean value used by your message loop and will not equal true until a WM_QUIT message has been received from the queue to indicate that the application is about to be closed.

For easier understanding we will break the order of each setup task into a list.
1. Window-class setup
2. Window-class registration
3. Window Creation
4. Message loop with event handler
We now have a fully working Windows application! I encourage you to toy around with the code and alter it to your liking. Don't be discouraged by errors or problems, for it is these things that make us better.

Next: Introduction to WGL