How to add OpenGL support to ATL controls






4.57/5 (7 votes)
An article describing the step-by-step process of adding basic OpenGL support to an ATL control
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:
// OpenGL rendering context 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:// Set OpenGL pixel format for given DC BOOL MyControl::SetupPixelFormat(HDC hdc) { static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number PFD_DRAW_TO_WINDOW | // support window PFD_SUPPORT_OPENGL | // support OpenGL PFD_DOUBLEBUFFER, // double buffered PFD_TYPE_RGBA, // RGBA type 24, // 24-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 32, // 32-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored }; 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
.// Create rendering context given device context and control bounds 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); // OpenGL code starts here - below is an example 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 aWM_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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { 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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { 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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // when resized, recreate the device context 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(); // compute dimensions of a quarter of the control 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.