OPENGL - TUTORIAL - LESSON #2 - DHTML (c) Artur Marques, 17 August 1999

*You are supposed to have read Lesson #1

The simplest Visual C++ OpenGL application...


#1 - Device Contexts and Rendering Contexts

#2 - Current Rendering Context

#3 - Linker settings

#4 - Header files

#5 - Modifying styles, before the creation of the window

#6 - Creating the Rendering Context

#7 - The Rendering Context and making it Active

#8 - Destruction

#9 - Initializing the added member variables

#10 - OpenGL ready!

download the source code + the win32 EXE


#1 - Device Contexts and Rendering Contexts

Those of you who also read the Visual C++ 6 tutorial, will know that there is this thing called the "device context" (DC), that we first used when programming a very simple drawing application, on VCPP6 - lesson #4. Back then, we declared a DC object in order to draw to a dialog box. In fact, Windows Graphics Device Interface (GDI) needs DCs to draw.

OpenGL is not a Microsoft technology, and it was not part of the Windows OS, until Windows 95. There are some instructions that must happen, before we can write OpenGL code on a Windows application. As GDI requires a DC, OpenGL requires a Rendering Context (RC).

With GDI, each command passes the DC as an argument; with OpenGL there is no need to pass the RC, because the commands assume a "current RC". One single window can have several RCs, but only one of them can be the current one, per thread.

This lesson will focus more on OpenGL [after all, this is the OpenGL tutorial...] and less on Windows programming. We will try to build the simplest OpenGL application possible in Windows.


#2 - Current Rendering Context

It should be expectable, from the previous paragraphs, that we need to #1) build a rendering context (RC); #2) make it the current rendering context (CRC). Windows requires that previously to such steps, #0) we must set the window's pixel format.

In practice, how do we do it? Just edit a NEW MFC .exe project, single document, NO database, NO OLE, no printing and print preview, and everything else on their defaults. My suggestion is for you to call this project "002_opengl". Here is a sequence of 5 pictures that show you exactly the options I did: 1 2 3 4 5.


#3 - Linker settings

Once the automatic part of your project is built, you have to make some extra inclusions, so that the OpenGL calls do not generate linker errors. This means that a default project does NOT include the libraries needed to code OpenGL. If you did try OGL statements and even if you did the right #includes, you would still not be able to build a program.

Go to PROJECT / SETTINGS / LINK and add to the "object / library modules" the libraries opengl32.lib, glaux.lib and glu32.lib. Check this picture that shows you exactly what to type.


#4 - Header files

It is now time for the #includes, ie for the inclusion of the header files that describe what is available on the libraries. The C pre-processor handles these includes and it assures that you can compile the project without syntax errors when calling [well formed] OGL statements. Linker errors are another story, told in the previous step.

Using the workspace, choose the file view / header files and edit StdAfx.h.

Make sure you add the following two lines:

#include <gl\gl.h>

#include <gl\glu.h>

These lines should be _exactly_ before the line that reads #ifndef _AFX_NO_AFXCMN_SUPPORT. You should know that the order of the inclusions is very relevant.

Save and close StdAfx.h.


#5 - Modifying styles, before the creation of the window

OpenGL needs the window to have styles WS_CLIPCHILDREN and WS_CLIPSIBLINGS set.

Using the workspace, choose the file view, and edit the file that corresponds to your view class. It should be called something like 002_openglview.cpp, if you are using the same names that I am.

You will need to change the window style and that should be done in method PreCreateWindow, because once the window is born, its style will not changeable. Once you find the method, code it as follows:

BOOL CMy002_openglView::PreCreateWindow(CREATESTRUCT& cs) {

// TODO: Modify the Window class or styles here by modifying the CREATESTRUCT cs

cs.style = cs.style | (WS_CLIPCHILDREN | WS_CLIPSIBLINGS); //apply BOTH the required styles to the current style

return CView::PreCreateWindow(cs);

}; //method ends

Here is what Microsoft says about such styles:

WS_CLIPCHILDREN - excludes the area occupied by child windows when you draw within the parent window. Used when you create the parent window.

WS_CLIPSIBLINGS - clips child windows relative to each other; that is, when a particular child window receives a paint message, the WS_CLIPSIBLINGS style clips all other overlapped child windows out of the region of the child window to be updated. If WS_CLIPSIBLINGS is not given and child windows overlap, when you draw within the client area of a child window, it is possible to draw within the client area of a neighboring child window.


#6 - Creating the Rendering Context

Inclusions are done, libraries are set, styles are applied... time to start creating the RC, and then the CRC (Current Rendering Context).

First, we must define the window's PIXEL FORMAT, which is a description of how the graphics that a window displays are stored in memory: color depth, buffering method and drawing interfaces.

Let's make a method for setting the pixel format. This new function should be edited in the view class file, that is 002_openglview.cpp, if you chose the name I suggested.

Use the Workspace / ClassView to insert the function. Choose the class name from the ClassView; secondary-click on it [normally right-click], then choose "add member function". Here is a picture that shows it all.

The new function should return a boolean (BOOL) and I suggest the name set_window_pixel_format. Check the picture.

BOOL CMy002_openglView::set_window_pixel_format (HDC hDC) {

PIXELFORMATDESCRIPTOR pfd;

pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);

pfd.nVersion = 1;

pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_SUPPORT_GDI | PFD_STEREO_DONTCARE;

pfd.iPixelType = PFD_TYPE_RGBA;

pfd.cColorBits = 32;

pfd.cRedBits = 8;

pfd.cRedShift = 16;

pfd.cGreenBits = 8;

pfd.cGreenShift = 8;

pfd.cBlueBits = 8;

pfd.cBlueShift = 0;

pfd.cAlphaBits = 0;

pfd.cAlphaShift = 0;

pfd.cAccumBits = 64;

pfd.cAccumRedBits = 16;

pfd.cAccumGreenBits = 16;

pfd.cAccumBlueBits = 16;

pfd.cAccumAlphaBits = 0;

pfd.cDepthBits = 32;

pfd.cStencilBits = 8;

pfd.cAuxBuffers = 0;

pfd.iLayerType = PFD_MAIN_PLANE;

pfd.bReserved = 0;

pfd.dwLayerMask = 0;

pfd.dwVisibleMask = 0;

pfd.dwDamageMask = 0;

//ChoosePixelFormat attempts to match an appropriate pixel format [PF] supported by a DC to a given PF spec

m_i_pixel_format = ChoosePixelFormat( hDC, &pfd);

if (m_i_pixel_format==0){

//if ChoosePixelFormat fails...

m_i_pixel_format = 1; //force a default pixel format and then try to set the members of pfd to data obtained via DescribePixelFormat

//DescribePixelFormat obtains information about the PF by m_i_pixel_format of the DC associated with hDC.

//The function sets the members of the PIXELFORMATDESCRIPTOR structure pointed to by pfd with that pixel format data.

if (DescribePixelFormat(hDC, m_i_pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)==0) return FALSE; //if that fails, function returns FALSE

}; //if index was 0

if (SetPixelFormat( hDC, m_i_pixel_format, &pfd)==FALSE) //if ChoosePF or DescribePF worked, set the PF

return FALSE;

return TRUE;

}; //method ends

PIXELFORMATDESCRIPTOR is a big structure, with many fields available for you to set. Explaining all these fields is not that important right now, and you can always get a better explanation of them using the VCPP6 HELP files. The really crucial setting is with the statement pfd.dwFlags = PFD_SUPPORT_OPENGL, above used with other flags [set by the binary OR operator].

Some of the statements here shown are just for security reasons. The SetPixelFormat function is the one you really need to call.

Notice that this method is using a m_i_pixel_format identifier that it does NOT declare, meaning it is a class member variable. Add it as a protected member variable of datatype int. Just use the Workspace / ClassView, then choose your view class, right-click on its name, choose "add member variable", specify type int, name "m_i_pixel_format", and protected access. Check this picture.

The m_i_pixel_format member variable of the view class is a protected entity that sets the pixel format needed for the RC (Rendering Context). We've just finished the very first step needed to code in OpenGL in Windows.


#7 - The Rendering Context and making it Active

Its time to build the RC and then make it the active one (CRC). Remember RC is the OGL rendering context, and CRC is standing for Current Rendering Context.

Lets build a new function for that - create_opengl_rc

Use the Workspace / ClassView and choose "Add Member Function". The function should return a BOOL and it makes sense to be only available for members of its class, ie to be a protected function. Check the picture here. You will need to edit the method's definition and the declaration, so that it has a single parameter - a handle for a Device Context.

You will also need a [protected] member variable that represents the OpenGL Rendering Context. As usual with Visual C++ data types you will need a handle for the OpenGL Rendering Context, and that justifies the datatype HGLRC. Check the picture.

Add the protected member variable "m_opengl_rc" of type HGLRC, to the view class of your program.

Then, code the method as follows:

BOOL CMy002_openglView::create_opengl_rc (HDC hDC) {

m_opengl_rc = wglCreateContext(hDC);

if (m_opengl_rc == NULL) return FALSE;

if (wglMakeCurrent(hDC, m_opengl_rc)==FALSE) return FALSE;

return TRUE;

}; //method ends

This function should be called when the view window is created, so use the CodeWizard to add a function for the WM_CREATE message. That function will be called OnCreate. Edit the default code, so it ends up like this:

int CMy002_openglView::OnCreate(LPCREATESTRUCT lpCreateStruct) {

if (CView::OnCreate (lpCreateStruct) == -1) return -1;

// TODO: Add your specialized creation code here

HWND hWnd = GetSafeHwnd();

HDC hDC = ::GetDC(hWnd);

if (set_window_pixel_format (hDC)==FALSE) return 0;

if (create_opengl_rc (hDC)==FALSE) return 0;

return 0;

}; //method ends


#8 - Destruction

We should also override the function responsable for the WM_DESTROY message, in order to release the CRC and destroy the RC, when the window is destroyed.

//in response to a WM_DESTROY message, release the current RC

void CMy002_openglView::OnDestroy() {

if(wglGetCurrentContext()!=NULL) wglMakeCurrent(NULL, NULL) ; //free CRC

if (m_opengl_rc!=NULL){

wglDeleteContext(m_opengl_rc); //delete RC

m_opengl_rc = NULL;

};

CView::OnDestroy();

};//method ends


#9 - Initializing the added member variables

You should remember that in this lesson's steps #6 and #7, we've added two protected member variables to the view class: m_i_pixel_format and m_opengl_rc. However, we didn't care about their initialization and that sure is one ugly omission.

As any C++ book will tell you, member variables should be initialized in the class constructor, so edit it. In this example, you will be looking for method void CMy002_openglView::CMy002_openglView().

Init the int to 0 (zero) and the handle to NULL.

CMy002_openglView::CMy002_openglView() {

// TODO: add construction code here

m_i_pixel_format=0;

m_opengl_rc=NULL;

};//method ends


#10 - OpenGL ready!

That is it. Build your program. Fix any errors you may have commited while typing the code, or just download my source code and win32 exe. When you run the program, it will look just like any ordinary MFC program, but is OpenGL enabled, and it will respond to any OpenGL primitives you might ask it. The Next lesson will start from this program and show you just that.


Downloads

all the source files [ZIP - 12K]

the debug release win32 executable [ZIP - 70K]