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.