Setting Up OpenGL in an MFC Control

I haven’t seen many articles on the Web integrate functionality other than just setting a basic OpenGL window inside of an existing MFC control. My goal in this tutorial is to set up, step-by-step, how to initialize an OpenGL rendering context inside of an MFC control such as a picture control, as well as basic functionality to draw based on a timer, resizing issues, basic camera functionality, and more. I will go step-by-step in the Microsoft Visual Studio .NET 2003 environment and it will be designed for all levels of understanding. I will add steps for the beginners for certain areas and perhaps steps that the more adept person can skip over and delve into what they’re truly looking for. Anyway, I appreciate any kind of comments on this (good or bad) and will try my best to update it to everyone’s liking. Thanks and enjoy!

Note: This article is a reprint from a tutorial on my Web site, located here. Please look at it for a more picture-oriented step-by-step tutorial.

Part I: Creating the Initial OpenGL Window

Step 1: Creating the Project

You will begin by starting a new MFC Dialog application project. File->New->Project… Select MFC Application from the Templates window and name it “oglMFCDialog”. Where you place it on your hard drive is up to you. Just remember where you saved it.

When the MFC Application Wizard pops up, under Application Type select Dialog based and click the Finish button. Any additional preferences and features can be changed under User Interface Features. For my example, I just selected the bare minimum.

Step 2: Creating the Control

Navigate to the Resource View tab from the Solution Explorer tab, expand the Dialog folder, and double-click on the automatically generated IDD_OGLMFCDIALOG_DIALOG dialog box. You will need to add a control in which the OpenGL rendering context will be placed. A simple picture control will do fine, so select Picture Control from the Toolbox.

Note: If your Toolbox does not show up, simply go to View->Toolbox to bring it up.

Drag and drop or select-drag the control onto your dialog window. Resize accordingly.

You will need to set some properties of the picture control. With the picture control selected, the properties window usually will be located on your bottom right of the environment. Set the following variables:

  • Visible: False
  • ID: IDC_OPENGL

You may be wondering why you set the Visible attribute to False. When you load the OpenGL rendering context inside of any MFC control, you will just use the control’s window rect coordinates to draw the OpenGL stuff. For some odd reason, if the visibility of the picture control is set to True, the control will cover up all the OpenGL you draw inside of the rect.

Step 3: Adding the OpenGL Class

Next, to set up the OpenGL, I opted to keep it nice and neat and add a separate class for it. It’s good practice to keep major components of a project in separate classes, so I will keep the OpenGL and the MFC separate.

To add a class, under the Solution Explorer tab, right-mouse click on the oglMFCDialog project in the tree and go to Add->Add Class… Select Generic C++ Class from the template and click Open. When the C++ Class Wizard pops up, set the following variables:

  • Class name: COpenGLControl
  • Base class: CWnd
  • Access: public
  • Check Virtual destructor

Click the Finish button and you will have your new class added to the project.

Step 4: Adding Project Libraries

Because you will be accessing OpenGL’s rendering functions, you also will need to add some libraries to link into the project. To do this, right-mouse click on the oglMFCDialog project in the tree once again and select Properties.

When the oglMFCDialog Property Pages box pops up, select Linker->Input and set the following variables:

Additional Dependencies: opengl32.lib glu32.lib

Note: Do not put any spaces between the two *.lib files; simply type them just as I have listed above.

Step 5: Setting Up Class Variables

For the duration of the project, some local variables will need to be added to the OpenGLControl.h, both public and private, as well as two #include calls for the OpenGL functions to work. They are as follows:

#include <gl/gl.h>
#include <gl/glu.h>

class COpenGLControl : public CWnd
{
   public:
      /******************/
      /* PUBLIC MEMBERS */
      /******************/
      // Timer
      UINT_PTR m_unpTimer;

   private:
      /*******************/
      /* PRIVATE MEMBERS */
      /*******************/
      // Window information
      CWnd    *hWnd;
      HDC     hdc;
      HGLRC   hrc;
      int     m_nPixelFormat;
      CRect   m_rect;
      CRect   m_oldWindow;
      CRect   m_originalRect;
   .
   .
   .

Important Note: I keep the functions of this project in a specific order. This is rather important because, if some are instantiated before others, you will get a compiler error due to protected member calls. So, I have set them up in the following order:

  1. Constructor and destructor
  2. Manually added functions
  3. Automatically added “afx_msg” functions
  4. The DECLARE_MESSAGE_MAP() call

Step 6: Adding the oglCreate Function

A new function will need to be added to both the OpenGLControl.h and OpenGLControl.cpp files. The function will be responsible for setting up some basic window variables and function calls important to the MFC; I named this function oglCreate. The portion of code below to be added to the header file can be placed under a new public routine section.

OpenGLControl.h:

void oglCreate(CRect rect, CWnd *parent);

OpenGLControl.cpp:

void COpenGLControl::oglCreate(CRect rect, CWnd *parent)
{
   CString className = AfxRegisterWndClass(CS_HREDRAW |
      CS_VREDRAW | CS_OWNDC, NULL,
      (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);

   CreateEx(0, className, "OpenGL", WS_CHILD | WS_VISIBLE |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);

   // Set initial variables' values
   m_oldWindow    = rect;
   m_originalRect = rect;

   hWnd = parent;
}

Step 7: Adding the OnPaint Function

A message class function will need to be added next. How this differs from the previous function is rather simple. MFC calls certain messages (with the WM_ prefix) when specific events happen within the program. Some of the main ones are OnPaint, OnSize, OnCreate, and so forth. To add one of these through Visual Studio, there is a Messages button located in the Properties dockable window with an icon next to the lightning bolt icon. If your cursor is inside the proper *.cpp or *.h file (in your case, for example, OpenGLControl.cpp), this is where you need to add these kinds of messages.

Having said that, first you will need to create an OnPaint message by locating the WM_PAINT message under the Properties->Messages window. Then, select <Add> OnPaint from the dropdown list. You will notice that the message function has been added to both the OpenGLControl.h and OpenGLControl.cpp. But, instead of like a regular user-added function, it will have the prefix “afx_msg” and will also have a call placed in the message map at the top of the *.cpp file. I recommend not changing anything added automatically by Visual Studio unless you know what you’re changing.

Inside the OnPaint function, only a little bit of the automatically generated code will need to be changed. You will be rendering (or “painting”) the OpenGL window unlike the MFC windows’ rendering, in that you will be doing it through a timer. This is in case the need to clamp to a certain frame rate arises later on in a project you do. To render the window control through a timer, the OnPaint cannot be called like it originally is; therefore, adding a simple line of code and commenting out (or deleting) the old calls is needed. Following is the code that will need to be added into this OnPaint function in the OpenGLControl.cpp file:

OpenGLControl.cpp:
void COpenGLControl::OnPaint()
{
   //CPaintDC dc(this);    // device context for painting
   ValidateRect(NULL);
}

Step 8: Adding the OnCreate Function

Next, add another message class like you did in the previous step, but this time select the WM_CREATE message and from the dropdown, select <Add> OnCreate. Again, the new class will be added to both the *.h and *.cpp files.

As far as the code calls for this, you will only need to add one line of code and that’s a call to your next manually added function, the oglInitialize function.

Note: Of course, this new function hasn’t been added yet, so if you try to run it you will get a compiler error.

OpenGLControl.cpp:

int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CWnd::OnCreate(lpCreateStruct) == -1)
      return -1;

   oglInitialize();

   return 0;
}

Step 9: Adding the oglInitialize Function

As mentioned in the previous step, the next manually added function, the oglInitialize function, is called when your OpenGL class is created (by means of the OnCreate message). The oglInitialize function will be responsible for setting up all of the information OpenGL needs to render. These include the pixel format, rendering context, and also a clear color to make sure that it’s swapping the buffers correctly.

Note: The last line of code in the *.cpp portion below calls an OnDraw function, which has not yet been created. Therefore, another compiler error will occur if you try running this.

OpenGLControl.h:

void oglInitialize(void);

OpenGLControl.cpp:

void COpenGLControl::oglInitialize(void)
{
   // Initial Setup:
   //
   static PIXELFORMATDESCRIPTOR pfd =
   {
      sizeof(PIXELFORMATDESCRIPTOR),
      1,
      PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
      PFD_TYPE_RGBA,
      32,    // bit depth
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      16,    // z-buffer depth
      0, 0, 0, 0, 0, 0, 0,
   };

   // Get device context only once.
   hdc = GetDC()->m_hDC;

   // Pixel format.
   m_nPixelFormat = ChoosePixelFormat(hdc, &pfd);
   SetPixelFormat(hdc, m_nPixelFormat, &pfd);

   // Create the OpenGL Rendering Context.
   hrc = wglCreateContext(hdc);
   wglMakeCurrent(hdc, hrc);

   // Basic Setup:
   //
   // Set color to use when clearing the background.
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   glClearDepth(1.0f);

   // Turn on backface culling
   glFrontFace(GL_CCW);
   glCullFace(GL_BACK);

   // Turn on depth testing
   glEnable(GL_DEPTH_TEST);
   glDepthFunc(GL_LEQUAL);

   // Send draw request
   OnDraw(NULL);
}

Step 10: Adding the OnDraw Function

Next, the OnDraw function mentioned in the previous step’s oglInitialize function needs to be added. This will have to act as a message function, but will need to be added manually. And as you might gather already, the “afx_msg” prefix will need to be added to the function’s declaration. You will notice in the *.cpp portion that you don’t actually call any procedures yet, just a commented TODO. This will be mentioned later on in the tutorial, for camera controls and such.

Note: If you try running this now, it will compile correctly, but unfortunately still nothing is being drawn into the control. This is due to the change you made in OnPaint.

OpenGLControl.h:

afx_msg void OnDraw(CDC *pDC);

OpenGLControl.cpp:

void COpenGLControl::OnDraw(CDC *pDC)
{
   // TODO: Camera controls.
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read