Introduction
This tutorial will demonstrate how to add OpenGL support to an ATL control, and constitutes a cut-down, organized version of the ATL OpenGL demo that comes with Visual C++ in a more printable and easier-to-digest format. In order to work with this tutorial,
you must create an ATL control in your project. What follows below is a step-by-step guide to adding the most basic OpenGL support to an ATL control.
Note: though I prefix function declarations with CMyControl::, I would recommend writing these functions inline
(in the header file) and if you do that, the prefix must be removed.
- Add support for headers <gl/gl.h> and <gl/glu.h>; Add support
for opengl32.lib and glu32.lib libraries.
This can be done by adding the following four lines at the end of your
stdafx.h file:
#include <gl/gl.h>
#include <gl/glu.h>
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
- Add an OpenGL rendering context variable to your control class:
HGLRC m_hRC;
Now make a function to choose the OpenGL pixel format. This function will
take a device context handle as a parameter and would set pixel format specifically
for this device context. The function should look like the following:
BOOL MyControl::SetupPixelFormat(HDC hdc)
{
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
32,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
int pixelformat;
if ((pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0)
{
ATLASSERT(FALSE);
return FALSE;
}
if (SetPixelFormat(hdc, pixelformat, &pfd) == FALSE)
{
ATLASSERT(FALSE);
return FALSE;
}
return TRUE;
}
- Make a function that would create an OpenGL rendering context and initialize
the viewport. Note that after setting the current rendering context, we can
begin calling OpenGL functions such as
glViewport.
void MyControl::CreateContext(HDC hdc, RECT& rc)
{
PIXELFORMATDESCRIPTOR pfd;
if (!SetupPixelFormat(hdc))
return;
int n = GetPixelFormat(hdc);
DescribePixelFormat(hdc, n, sizeof(pfd), &pfd);
m_hRC = wglCreateContext(hdc);
wglMakeCurrent(hdc, m_hRC);
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-width/2, width/2, -height/2, height/2);
glMatrixMode(GL_MODELVIEW);
}
- Add a call to the above function from
OnCreate. To do that,
you first have to create a WM_CREATE handler. Use the Messages
button on the Properties toolbar to add a handler. Then, find the function
and add code similar to the following:
LRESULT CMyControl::OnCreate(UINT , WPARAM ,
LPARAM , BOOL& )
{
HDC hdc = GetDC();
RECT rc;
GetClientRect(&rc);
CreateContext(hdc, rc);
return 0;
}
- In a similar fashion, add code for
OnDestroy (WM_DESTROY)
to clear the rendering context and delete it.
LRESULT CMyControl::OnDestroy(UINT , WPARAM , LPARAM , BOOL& )
{
wglMakeCurrent(NULL, NULL);
if (m_hRC)
{
wglDeleteContext(m_hRC);
m_hRC = NULL;
}
return 0;
}
- Handling
WM_SIZE is also important. Whenever the window resizes,
we must nullify the current OpenGL rendering context and recreate it from
scratch.
LRESULT CMyControl::OnSize(UINT , WPARAM ,
LPARAM , BOOL& )
{
wglMakeCurrent(NULL, NULL);
if (m_hRC)
{
wglDeleteContext(m_hRC);
m_hRC = NULL;
}
HDC hdc = GetDC();
RECT rc;
GetClientRect(&rc);
CreateContext(hdc, rc);
return 0;
}
All done. Remember! You can use glaux library (glaux32.lib, <glaux.h>)
to implement functionality equivalent to the GLUT library (though you probably
won't need the windowing functions). For example, you can use auxDIBImageLoad
to load bitmaps to use as textures. However, I would recommend using more
advanced methods of texturing, for example using PNG files for textures that
have an alpha channel.
- Rendering can be done in
OnDraw. Here is an example:
HRESULT OnDraw(ATL_DRAWINFO& di)
{
HDC hdc = di.hdcDraw;
RECT& rc = *(RECT*)di.prcBounds;
wglMakeCurrent(hdc, m_hRC);
glClearColor(1.0f, 0.0f, 0.0f, 10.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
SIZE qtrSize = {
(rc.right - rc.left) / 2,
(rc.bottom - rc.top) / 2 };
glBegin(GL_QUADS);
glColor3ub(255, 0, 0);
glVertex3s(-qtrSize.cx, -qtrSize.cy, 0);
glColor3ub(0, 255, 0);
glVertex3s(qtrSize.cx, -qtrSize.cy, 0);
glColor3ub(0, 0, 255);
glVertex3s(qtrSize.cx, qtrSize.cy, 0);
glColor3ub(255, 255, 255);
glVertex3s(-qtrSize.cx, qtrSize.cy, 0);
glEnd();
glPopMatrix();
glFinish();
SwapBuffers(wglGetCurrentDC());
return S_OK;
}
Possible Improvements
I believe it is possible to create a mixin class/abstract superclass that will
add OpenGL functionality to an ATL control in a much cleaner fashion than a
bunch of functions/message handlers inside every control class. Writing all
this code for each ATL control (if you have many in your project) is tedious:
it would be better if you could specify that your control inherits from some
CControlOpenGL<YourControl> class. This functionality, as well as
more advanced OpenGL modes and options may make up material for another tutorial.