OpenGL Programming with Windows MFC

This exercise requires Visual Studio 2012 Professional Edition.

Before you begin this exercise, be sure you have installed freeGLUT and GLEW.


Building a Core Profile OpenGL View Class with MFC Framework

When Visual C++ has started, you will see a Start Screen similar to this one:

2. Create a New Project

The following show you how to create a framework for the OpenGL viewing class. You will need to perform the following steps.

At this point, we have created a framework for the OpenGL program we are to develop. Your main window has changed. It now has three main regions.

If your window differs from the one pictured, you can still probably find the same elements. Most likely the Solution Explorer and Property manager will be on the left. The exact layout does not matter much.

In the next section we will use the Class View tab in the Project Management Panel show the project's class hierarchy. There should be five classes (CAboutDlg, CMainFrame, COpenGLApp, COpenGLDoc, and COpenGLView) automatically created for the OpenGL project. One of them, called "COpenGLView", will be particularly interesting to you because the main task of this lab is to add message handling functions into this class to allow OpenGL to set up and render the view at the appropriate times.

At this point, you might want to build and run the application (which of course does nothing yet) just to make sure your the wizard gave you a working project.

It should look something like this:

3. Add Necessary Member Functions to the COpenGLView Class

The MFC Project Wizard creates the minimum number of member functions for each class being generated. We may take a look at them by looking at the Class View:

To examine COpenGLView class: Now, with COpenGLView selected, we can add functions for the following additional messages:

To add functions for these messages, first ensure that you have selected "COpenGLView" in the "Class View", then press Alt + Enter to open the Properties window. It will open on the right of the screen. You will notice that in the Properties window, some properties of "COpenGLView" are listed. The icons along the top allow you to select other property types. We want to see and edit the message mapping list. Follow the example below to see how this is done, then add message handler functions for all the messages listed above.



Click on COpenGLView again to add the next function and repeat as needed.

4. Compile Your Application

It will take a little while to compile and link the program. You may look at the output from the compiling process being displayed in the Output region at the bottom. If the project has compiled successfully, you will see a message like this:
---------------------- Done ----------------------
Build: 1 succeeded, 0 failed, 0 skipped

It is very exciting to run this little window's program if it is your first Windows program.

A new window should be displayed in a few seconds. There are three menus: "File", "Edit", and "Help". These are the minimum options automatically created by your VC++ system. Don't be surprised; the inside window is totally blank, because we haven't written any functions yet.

NOTE: On Alex Clarke's VS2012 Professional install the OnCreate has several lines of garbage code that did not appear in previous versions. If your code does not compile try using this version of OnCreate instead:

int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  Add your specialized creation code here

    return 0;
}

5. Linking OpenGL Libraries

So far what we have created is all standard MFC stuff. It has nothing to do with OpenGL yet. To connect to OpenGL, we have do a few things first. Then we may use functions from OpenGL library.

First, we need to insert the following lines into OpenGLView.h header file:

// Include the OpenGL headers
#include "Angel.h"  //Dr. Angel's code does this for us

To add the above contents, you have to first find the desired header file. This is a good time to learn how to navigate around the files of the project.

Now, you may add the above contents at the very beginning of this header file. Remember to save the document before you close this Editing window.

The next step is to add the OpenGL libraries to the link line:

Last, but not least, add support code from Lab 1 to your project by:

At this point, you might want compile your application again to make sure that everything is right so far.

D. Editing Member Functions

Now that the OpenGL libraries have been added to our project, we are ready add some real OpenGL code. For this lab, you are concerned with the COpenGLView class only. Therefore, you will work with two files:

The code you will add will:

  1. Create and set up a Device Context
  2. Establish a Rendering Context
  3. Handle window events
  4. Draw a scene
  5. Clean up after itself

For now here are the changes you need to make to the two files. The parts you will work with are highlighted in red.

File: OpenGLView.h
// OpenGLView.h : interface of the COpenGLView class


// You should have already added this line.
#include "Angel.h"
...... class COpenGLView : public CView { protected: // create from serialization only ..... // Attributes public: ...... // Operations public: // Overrides public: ...... // Implementation public: ...... protected:
   // You will add the following stuff!!!

   virtual BOOL GetOldStyleRenderingContext( void );
   virtual BOOL SetupPixelFormat( void );


private:
   //OpenGL Setup
   BOOL GetRenderingContext();
   //Rendering Context and Device Context Pointers
   HGLRC     m_hRC;
   CDC*      m_pDC;

   //Error Handling
   void SetError( int e );
   static const char* const _ErrorStrings[];
   const char* m_ErrorString;

// Generated message map functions protected: DECLARE_MESSAGE_MAP() public: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnSize(UINT nType, int cx, int cy); }; ......
OpenGLView.cpp

// OpenGLView.cpp : implementation of the COpenGLView class

#include "stdafx.h"

// SHARED_HANDLERS can be defined in an ATL project implementing preview, thumbnail
// and search filter handlers and allows sharing of document code with that project.
#ifndef SHARED_HANDLERS
#include "OpenGL.h"
#endif

#include "OpenGLDoc.h"
#include "OpenGLView.h"


// Add code from Prototypes and Global Variables Section
// of lab 1 notes
...... // COpenGLView IMPLEMENT_DYNCREATE(COpenGLView, CView) BEGIN_MESSAGE_MAP(COpenGLView, CView) ...... END_MESSAGE_MAP()
// You will add stuff here!!!!
const char* const COpenGLView::_ErrorStrings[]= {
    {"No Error"},                     // 0
    {"Unable to get a DC"},           // 1
    {"ChoosePixelFormat failed"},     // 2
    {"SelectPixelFormat failed"},     // 3
    {"wglCreateContext failed"},      // 4
    {"wglMakeCurrent failed"},        // 5
    {"wglDeleteContext failed"},      // 6
    {"SwapBuffers failed"},           // 7
};
// COpenGLView construction/destruction COpenGLView::COpenGLView()
   // You will add the following line !!!
: m_hRC(0), m_pDC(0), m_ErrorString(_ErrorStrings[0]) // Call constructors
{ ...... } ...... BOOL COpenGLView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs
    // You will add stuff here !!!
// An OpenGL window must be created with the following flags and must not
// include CS_PARENTDC for the class style.
cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
return CView::PreCreateWindow(cs); } // COpenGLView drawing void COpenGLView::OnDraw(CDC* pDC) { COpenGLDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here
    // Find the OpenGL drawing function provided in the Lab 1 notes and call it here


    //Swap buffers to show result
    if ( FALSE == ::SwapBuffers( m_pDC->GetSafeHdc() ) )
    {
        SetError(7);
    }
} ...... // COpenGLView message handlers int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here
    GetRenderingContext();
    if (!GetRenderingContext())
    {
       //Something went wrong with getting the rendering context.
       //Create a popup with the error message, then quit.
       AfxMessageBox(CString(m_ErrorString));
       return -1;
    }

    // We now have a rendering context, so we can set the initial drawing state.
    // Find the initialize OpenGL function provided in the Lab 1 notes and call it here

return 0; } void COpenGLView::OnDestroy() { CView::OnDestroy(); // TODO: Add your message handler code here
    if ( FALSE == ::wglDeleteContext( m_hRC ) )
{
SetError(6);
}

if ( m_pDC )
{
delete m_pDC;
}
} void COpenGLView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here
    // A resize event occured; cx and cy are the window's new width and height.
    // Find the OpenGL change size function given in the Lab 1 notes and call it here

}
/////////////////////////////////////////////////////////////////////////////
// GL Rendering Context Creation Functions
//
// Since we are using Windows native windowing, we need to set up our own
// OpenGL rendering context. These functions do it to the main view area.
// It is possible to do it to a smaller sub view. If you are curious, you can
// find tutorials on how to do that on the net.
//

// Error reporting utility
void COpenGLView::SetError( int e )
{
   // if there was no previous error,
   // then save this one
   if ( _ErrorStrings[0] == m_ErrorString )
   {
      m_ErrorString = _ErrorStrings[e];
   }
}

BOOL COpenGLView::GetRenderingContext()
{
   // Can we put this in the constructor?
   m_pDC = new CClientDC(this);

   if ( NULL == m_pDC ) // failure to get DC
   {
      SetError(1);
      return FALSE;
   }


   if (!GetOldStyleRenderingContext())
   {
      return TRUE;
   }

   //Get access to modern OpenGL functionality from this old style context.
   glewExperimental = GL_TRUE;
   if (GLEW_OK != glewInit())
   {
      AfxMessageBox(_T("GLEW could not be initialized!"));
      return FALSE;
   }

   //Get a new style pixel format
   if (!SetupPixelFormat())
   {
      return FALSE;
   }

   //Setup request for OpenGL 3.2 Core Profile
   int attribs[] =
   {
      WGL_CONTEXT_MAJOR_VERSION_ARB,   3,
      WGL_CONTEXT_MINOR_VERSION_ARB,   2,
      WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 
      //WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
      0, 0  //End
   };

   if(wglewIsSupported("WGL_ARB_create_context") == 1)
   {
      //If this driver supports new style rendering contexts, create one
      HGLRC oldContext = m_hRC;
      if ( 0 == (m_hRC = m_hRC = wglCreateContextAttribsARB(m_pDC->GetSafeHdc(),0, attribs) ) )
      {
         SetError(4);
         return FALSE;
      }

      if(!wglMakeCurrent(NULL,NULL) )
         wglDeleteContext(oldContext);
      if ( FALSE == wglMakeCurrent( m_pDC->GetSafeHdc(), m_hRC ) )
      {
         SetError(5);
         return FALSE;
      }     
   }
   else
   {  
      //Otherwise use the old style rendering context we created earlier.
      AfxMessageBox(_T("GL 3.2 Context not possible. Using old style context. (GL 2.1 and before)"));
   }

   return TRUE;
}

BOOL COpenGLView::GetOldStyleRenderingContext()
{
   //A generic pixel format descriptor. This will be replaced with a more
   //specific and modern one later, so don't worry about it too much.
   static PIXELFORMATDESCRIPTOR pfd =
   {
      sizeof(PIXELFORMATDESCRIPTOR),
      1,
      PFD_DRAW_TO_WINDOW |            // support window
      PFD_SUPPORT_OPENGL |            // support OpenGL
      PFD_DOUBLEBUFFER,               // double buffered
      PFD_TYPE_RGBA,                  // RGBA type
      32,                             // 32-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
      24,                        // 24-bit z-buffer
      0,                              // no stencil buffer
      0,                              // no auxiliary buffer
      PFD_MAIN_PLANE,                 // main layer
      0,                              // reserved
      0, 0, 0                         // layer masks ignored
   };

   // Get the id number for the best match supported by the hardware device context
   // to what is described in pfd
   int pixelFormat = ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd);

   //If there's no match, report an error
   if ( 0 == pixelFormat )
   {
      SetError(2);
      return FALSE;
   }

   //If there is an acceptable match, set it as the current 
   if ( FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd) )
   {
      SetError(3);
      return FALSE;
   }

   //Create a context with this pixel format
   if ( 0 == (m_hRC = wglCreateContext( m_pDC->GetSafeHdc() ) ) )
   {
      SetError(4);
      return FALSE;
   }

   //Make it current. Now we're ready to get extended features.
   if ( FALSE == wglMakeCurrent( m_pDC->GetSafeHdc(), m_hRC ) )
   {
      SetError(5);
      return FALSE;
   }
   return TRUE;
}

BOOL COpenGLView::SetupPixelFormat()
{
   //This is a modern pixel format attribute list.
   //It has an extensible structure. Just add in more argument pairs 
   //befroe the null to request more features.
   const int attribList[] =
   {
      WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
      WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
      WGL_ACCELERATION_ARB,   WGL_FULL_ACCELERATION_ARB,
      WGL_DOUBLE_BUFFER_ARB,  GL_TRUE,
      WGL_PIXEL_TYPE_ARB,     WGL_TYPE_RGBA_ARB,
      WGL_COLOR_BITS_ARB,     32,
      WGL_DEPTH_BITS_ARB,     24,
      WGL_STENCIL_BITS_ARB,   8,
      0, 0  //End
   };


   unsigned int numFormats;
   int pixelFormat;
   PIXELFORMATDESCRIPTOR pfd;

   //Select a pixel format number
   wglChoosePixelFormatARB(m_pDC->GetSafeHdc(), attribList, NULL, 1, &pixelFormat, &numFormats);

   //Optional: Get the pixel format's description. We must provide a 
   //description to SetPixelFormat(), but its contents mean little.
   //According to MSDN: 
   //  The system's metafile component uses this structure to record the logical
   //  pixel format specification. The structure has no other effect upon the
   //  behavior of the SetPixelFormat function.
   //DescribePixelFormat(m_pDC->GetSafeHdc(), pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

   //Set it as the current 
   if ( FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd) )
   {
      SetError(3);
      return FALSE;
   }

   return TRUE;
}

// Add the definitions for the OpenGL initialize, resize, and draw functions from the lab notes!

Once you have modified the above two files, you can compile and run the program.

If you got errors, you might have forgotten to add the OpenGL code from the lab notes to the OpenGLView.h and OpenGLView.cpp! You will also need the correct shaders, or the program will appear not to start. If you get a warning that says "GL 3.2 Context not possible", you have a graphics card that doesn't support OpenGL 3.2 Core Profile and you will have to either update your drivers, or try again with a newer card. I suggest any nVidia or AMD card produced in the last couple years.

E. Clean Your Project Before You Submit It

To clean up your project for submission delete all Debug folders in the project and delete all sdf files. They are rather large (in some cases > 30 MB) and the .exe file in the Debug folder will be rejected by many spam filtering systems. Besides, you will get them back the next time you compile your program.