The first program demonstrated here will show you the minimum
requirements for setting up a Windows program to display OpenGL
graphics. As GDI needs a Device Context (DC) to draw images, OpenGL
requires a Rendering Context (RC). Unlike GDI, in which each GDI
command requires that a DC is passed into it, OpenGL uses the
concept of a current RC. Once a rendering context has been made
current in a thread, all OpenGL calls in that thread will use
the same current rendering context. While multiple rendering contexts
may be used to draw in a single window, only one rendering context
may be current at any time in a single thread.
The goal of this sample is to create and make current an OpenGL
rendering context. There are three steps to creating and making
current a rendering context:
- Set the window's pixel format.
- Create the rendering context.
- Make the rendering context current.
Take the following steps to create the project:
- Create a new Project Workspace of type "MFC AppWizard (exe)".
Select the directory you where you want the project directory
to be created, and type "GLSample1" as the project name.
Click "Create" to enter the AppWizard. Following is
a list of the steps in the AppWizard and the parameters you should
enter in each of them. Any parameters not listed are optional.
- Single Document Interface
- Database support: None
- OLE support: None
- Docking Toolbar: OFF (optional)
Initial Status Bar: OFF (optional)
Printing an Print Preview: OFF (Printing OpenGL images is accomplished
by creating an RC using a printer DC. If you would like to experiment
with this later, without rebuilding everything, go ahead and turn
this option on).
Context-Sensitive Help: OFF (optional)
3D Controls: ON (optional)
- Generate Source File Comments: Yes
Use the MFC library as a shared DLL.
- Keep everything at the default.
Press Finish
Check the "New Project Information" dialog to make sure
everything is as it should be and press OK. The new project will
be created in the subdirectory "GLSample1".
Visualizing MFC |
One of the most frusterating things you notice when you are first using OpenGL with MFC is the "Where am I?" feeling
you get. MFC has hundreds of classes, and the AppWizard adds several more. A good way to get around this feeling is to use a class hierarchy visualization
tool like CodeVizor. With CodeVizor you can drag the source code for MFC into
the CodeVizor tool and in about 30 seconds have a beautiful, clickable (and printable!) class hierarchy chart. You can even color classes individually or in groups so that they stand out!
Get CodeVizor and see how much easier undestanding class hierarchies becomes!
CodeVizor Web Site
|
First we will include all necessary OpenGL files and libraries
in this project. Select "Build-Settings" from the menu.
Click on the "Link" tab (or press Ctrl-Tab to move there).
Select the "General" category (it should already be
selected by default), and enter the following into the Object/Library
Modules edit box: "opengl32.lib glu32.lib glaux.lib".
Press OK. Now open the file "stdafx.h". Insert the following
lines into the file:
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <gl\gl.h>
#include <gl\glu.h>
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC support for Windows 95 Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
OpenGL requires the window to have styles WS_CLIPCHILDREN
and WS_CLIPSIBLINGS set. Edit OnPreCreate so that
it looks like this:
BOOL CGLSample1View::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
return CView::PreCreateWindow(cs);
}
The first set to creating a rendering context is to define the
window's pixel format. The pixel format describes how the graphics
that the window displays are represented in memory. Parameters
controlled by the pixel format include color depth, buffering
method, and supported drawing interfaces. We will look at some
of these below. First create a new protected member function in
the CGLSample1View class called "BOOL SetWindowPixelFormat(HDC
hDC)" (my preferred method of doing this is right clicking
on the class name in the Project Workspace and selecting "Add
Function..." from the resulting pop-up menu. You may also
do it manually if you wish) and edit the function so that it looks
like this:
BOOL CGLSample1View::SetWindowPixelFormat(HDC hDC)
{
PIXELFORMATDESCRIPTOR pixelDesc;
pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixelDesc.nVersion = 1;
pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW |
PFD_DRAW_TO_BITMAP |
PFD_SUPPORT_OPENGL |
PFD_SUPPORT_GDI |
PFD_STEREO_DONTCARE;
pixelDesc.iPixelType = PFD_TYPE_RGBA;
pixelDesc.cColorBits = 32;
pixelDesc.cRedBits = 8;
pixelDesc.cRedShift = 16;
pixelDesc.cGreenBits = 8;
pixelDesc.cGreenShift = 8;
pixelDesc.cBlueBits = 8;
pixelDesc.cBlueShift = 0;
pixelDesc.cAlphaBits = 0;
pixelDesc.cAlphaShift = 0;
pixelDesc.cAccumBits = 64;
pixelDesc.cAccumRedBits = 16;
pixelDesc.cAccumGreenBits = 16;
pixelDesc.cAccumBlueBits = 16;
pixelDesc.cAccumAlphaBits = 0;
pixelDesc.cDepthBits = 32;
pixelDesc.cStencilBits = 8;
pixelDesc.cAuxBuffers = 0;
pixelDesc.iLayerType = PFD_MAIN_PLANE;
pixelDesc.bReserved = 0;
pixelDesc.dwLayerMask = 0;
pixelDesc.dwVisibleMask = 0;
pixelDesc.dwDamageMask = 0;
m_GLPixelIndex = ChoosePixelFormat( hDC, &pixelDesc);
if (m_GLPixelIndex==0) // Let's choose a default index.
{
m_GLPixelIndex = 1;
if (DescribePixelFormat(hDC, m_GLPixelIndex,
sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc)==0)
{
return FALSE;
}
}
if (SetPixelFormat( hDC, m_GLPixelIndex, &pixelDesc)==FALSE)
{
return FALSE;
}
return TRUE;
}
Now add the following member variable to the CGLSample1View class
(again, I like to use the right mouse button on the class name
and select "Add Variable..."):
int m_GLPixelIndex; // protected
Finally, in the ClassWizard, add the function OnCreate in response
to a WM_CREATE message and edit it to look like this:
int CGLSample1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
HWND hWnd = GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);
if (SetWindowPixelFormat(hDC)==FALSE)
return 0;
return 0;
}
Compile the program and fix any syntax errors. You may run the
program if you wish but at the moment, it will look like a generic
MFC shell. Try playing with the pixel format descriptor. You may
want to try passing other indices into DescribePixelFormat
to see what pixel formats are available. I'll spend some time
now explaining what the code does and precautions you should take
in the future.
PIXELFORMATDESCRIPTOR contains all of the information defining
a pixel format. I'll explain some of the important points here,
but for a complete description look in the VC++ online help.
- dwFlags Defines
the devices and interfaces with which the pixel format is compatible.
Not all of these flags are implemented in the generic release
of OpenGL. Refer to the documentation for more information. dwFlags
can accept the following flags:
PFD_DRAW_TO_WINDOW -- Enables drawing to a window or device surface.
PFD_DRAW_TO_BITMAP -- Enables drawing to a bitmap in memory.
PFD_SUPPORT_GDI -- Enables GDI calls. Note: This option is not valid if PFD_DOUBLEBUFFER is specified.
PFD_SUPPORT_OPENGL -- Enables OpenGL calls.
PFD_GENERIC_FORMAT -- Specifies if this pixel format is supported by the Windows GDI library or by a vendor hardware device driver.
PFD_NEED_PALETTE -- Tells if the buffer requires a palette. This tutorial assumes color will be done with 24 or 32 bits and will not cover palettes.
PFD_NEED_SYSTEM_PALETTE -- This flag indicates if the buffer requires the reserved system palette as part of its palette. As stated above, this tutorial will not cover palettes.
PFD_DOUBLEBUFFER -- Indicates that double-buffering is used. Note that GDI cannot be used with windows that are double buffered.
PFD_STEREO -- Indicates that left and right buffers are maintained for stereo images.
- iPixelType Defines
the method used to display colors. PFD_TYPE_RGBA
means each set of bits represents a Red, Green, and Blue value,
while PFD_TYPE_COLORINDEX means that each set
of bits is an index into a color lookup table. All of the examples
in this program will use PFD_TYPE_RGBA.
- cColorBits Defines
the number of bits used to define a color. For RGBA it is the
number of bits used to represent the red, green, and blue components
of the color ( but not the alpha). For indexed colors, it is the
number of colors in the table.
- cRedBits, cGreenBits, cBlueBits,
cAlphaBits The number of bits used to represent
the respective components.
- cRedShift, cGreenShift, cBlueShift,
cAlphaShift The number of bits each componet is
offset from the beginning of the color.
Once we initialize our structure, we try to find the system pixel
format that is closest to the one we want. We do this by calling:
m_hGLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
ChoosePixelFormat takes an hDC and a PIXELFORMATDESCRIPTOR*,
and returns an index used to reference that pixel format, or 0
if the function fails. If the function fails, we just set the
index to 1 and get the pixel format description using DescribePixelFormat.
There are a limited number of pixel formats, and the system defines
what their properties are. If you ask for pixel format properties
that are not supported, ChoosePixelFormat will return an integer
to the format that is closest to the one you requested. Once we
have a valid pixel format index and the corresponding description
we can call SetPixelFormat. A window's pixel format may
be set only once.
Now that the pixel format is set, all we have to do is create
the rendering context and make it current. Start by adding a new
protected member function to the CGLSample1View class called "BOOL
CreateViewGLContext(HDC hDC)" and edit it so that it looks
like this:
BOOL CGLSample1View::CreateViewGLContext(HDC hDC)
{
m_hGLContext = wglCreateContext(hDC);
if (m_hGLContext == NULL)
{
return FALSE;
}
if (wglMakeCurrent(hDC, m_hGLContext)==FALSE)
{
return FALSE;
}
return TRUE;
}
Add the following member variable to the CGLSample1View class:
HGLRC m_hGLContext; // protected
Edit OnCreate to call the new function:
int CGLSample1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
HWND hWnd = GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);
if (SetWindowPixelFormat(hDC)==FALSE)
return 0;
if (CreateViewGLContext(hDC)==FALSE)
return 0;
return 0;
}
Add the function OnDestroy in response to a WM_DESTROY message
and edit it to look like this:
void CGLSample1View::OnDestroy()
{
if(wglGetCurrentContext()!=NULL)
{
// make the rendering context not current
wglMakeCurrent(NULL, NULL) ;
}
if (m_hGLContext!=NULL)
{
wglDeleteContext(m_hGLContext);
m_hGLContext = NULL;
}
// Now the associated DC can be released.
CView::OnDestroy();
}
And lastly, edit the CGLSample1View class constructor to look
like this:
CGLTutor1View::CGLTutor1View()
{
m_hGLContext = NULL;
m_GLPixelIndex = 0;
}
Once again compile the program and fix any syntax errors. When
you run the program it will still look like a generic MFC program,
but it is now enabled for OpenGL drawing. You may have noticed
that we created one rendering context at the beginning of the
program and used it the entire time. This goes against most GDI
programs where DCs are created only when drawing is required and
freed immediately afterwards. This is a valid option with RCs
as well, however creating an RC can be quite processor intensive.
Because we are trying to achieve high performance graphics, the
code only creates the RC once and uses it the entire time.
CreateViewGLContext creates and makes current a rendering context.
wglCreateContext returns a handle to an RC. The pixel format for
the device associated with the DC you pass into this function
must be set before you call CreateViewGLContext. wglMakeCurrent
sets the RC as the current context. The DC passed into this function
does not need to be the same DC you used to create the context,
but it must have the same device and pixel format. If another
rendering context is current when you call wglMakeCurrent, the
function simply flushes the old RC and replaces it with the new
one. You may call wglMakeCurrent(NULL, NULL) to make no rendering
context current.
Because OnDestroy releases the window's RC, we need to delete
the rendering context there. But before we delete the RC, we need
to make sure it is not current. We use wglGetCurrentContext to
see if there is a current rendering context. If there is, we remove
it by calling wglMakeCurrent(NULL, NULL). Next we call wglDeleteContext
to delete out RC. It is now safe to allow the view class to release
the DC. Note that since the RC was current to our thread we could
have just called wglDeleteContext without first making it not
current. Don't get into the habit of doing this. If you ever start
using multi-threaded applications that laziness is going to bite
you.
Congratulations on your first OpenGL program, even if it doesn't
do much! If you already know OpenGL on another platform read the
tips below and go write the next killer graphics applications.
If you don't know OpenGL keep reading. I'll give you a tour of
some of its functions.
OpenGL Tips:
- Set the viewport and matrix modes in response to a WM_SIZE
message.
- Do all of your drawing in response to a WM_PAINT message.
- Creating a rendering context can take up a lot of CPU time.
Only create it once and use it for the life of your program.
- Try encapsulating your drawing commands in the document class.
That way you can use the same document in different views.
-
Download Example Source
Source code for GLSample1
Place the file in the parent directory for the project and type "pkunzip -d GLSamp1.zip".
Then choose File - OpenWorkspace and open the glsample1.mak file unzipped into the GLSample1 directory.
You must have a version on PKZip that supports long file names.
Click here to get the latest version of PKZip.