SGI Windows NT Toolbox


The source code and Makefile described in this article
exists at toolbox/src/gfx/opengl/tutorials/{simple1,simpleMFC}/
From Developer News, September/October 1998:

OpenGL and Windows NT

By Peter Shafton, Member of Technical Staff

Introduction

You may be wondering why I am writing an article about OpenGL on Windows NT® when so much information about this topic already exists. My reason is twofold: first I want there to be one easy place to go to get answers about porting OpenGL code from IRIX® to Windows NT; second, I needed a starting point from which to explain the OpenGL/Windows NT programs that I will add to a future version of the Developer's Toolbox. I hope this article will answer some of the questions that may arise when moving OpenGL programs from IRIX to Windows NT. I am assuming that the reader is already familiar with OpenGL on IRIX and has at least a basic understanding of Windows NT.

This is the first of a two-part article. In this issue I will touch on OpenGL rendering, OpenGL in standard Windows®, and the integration of OpenGL with the Microsoft® Foundation Classes. In the next issue I will address the connection between Windows palettes and OpenGL.

OpenGL Rendering Overview

WGL is the Windows to OpenGL interface layer on Windows NT; it is functionally equivalent to the GLX layer on IRIX. Since OpenGL is window-system independent, no changes need to occur in the straight OpenGL code when going from IRIX to Windows NT. All of the changes that occur are in the window-system interface code. Let me start by listing the three things that you need to render in OpenGL on the IRIX side. You first need a window or drawable into which you are going to render; second, you need a VisualInfo structure or Fbconfig to tell X how the window is to be configured; and last, you need a graphics context to keep track of the OpenGL state. To render OpenGL on the Windows NT side you need the same three functional pieces. First you need a drawable or HDC (handle to a device context) that supports OpenGL. Second, you need a PixelFormat, which tells how to render to the window. Last, you need an HGLRC (handle to a GL rendering context) or graphics context to keep track of the OpenGL state. The mapping between the two operating systems is shown in Table 1.

IRIX Windows NT
GLXDrawable HDC
XVisualInfo* PixelFormat
GLXContext HGLRC
Table 1: IRIX and Windows NT Handles

While the three major pieces needed to render are fundamentally the same, the way in which they are set up differs a bit. On the IRIX side you need to choose your visual type and therefore your configuration before you create your drawable. On the Windows NT side the DC can be configured after the drawable is created. On both Windows NT and IRIX a drawable can be rendered into by different threads or processes. The main difference is that on Windows NT the DC can be configured once per thread after creation, whereas on IRIX the drawable can only be configured once for all threads at creation time. Aside from these differences, once you have the three pieces, rendering is fundamentally the same.

The order in which the three pieces are created differs slightly between IRIX and Windows NT. On IRIX, creation takes the following path: You start by deciding how you are going to be using the drawable and therefore what visual type you will need. You can use a number of functions to obtain the XVisualInfo structure once you decide what you need. Second, you use XVisualInfo to create an applicable drawable. Next you can use XVisualInfo to obtain a graphics context, and last, you make the context and drawable current. On Windows NT the creation path looks like this: First you create the Drawable either by a call to CreateWindow or CreateBitmap. Second, you get the HDC for the given drawable by calling GetDC. Third, you decide how you want to use the drawable and choose an appropriate PixelFormat. Next you use the HDC to obtain a graphics context, and last, you make the context and HDC current. The flow is illustrated in Table 2.

Table 2: Steps Involved in Creating OpenGL Capable Windows
IRIX Windows NT
Step Function Step Function
1. Choose the appropriate XVisualInfo structure glXChooseVisual 1. Create the window or bitmap to draw into CreateWindow, CreateBitmap
2. Create the window or bitmap to draw into XCreateWindow 2. Get the HDC from the window or bitmap GetDC
3. Obtain a graphics context from the visual glXCreateContext 3. Get and set the appropriate PixelFormat ChoosePixelFormat, SetPixelFormat
4. Make the context and drawable current glXMakeCurrent 4. Obtain a graphics context from the HDC wglCreateContext
5. Make the context and HDC current wglMakeCurrent

Let's talk separately about the three main pieces from above. The first piece is the drawable. The drawable is the actual canvas where the OpenGL drawing is to occur. On the IRIX side the drawable can be any OpenGL capable window, an OpenGL capable pixmap, or a Pbuffer (an offscreen rendering area that is hardware accelerated). On Windows NT you can render to any OpenGL capable window or OpenGL capable bitmap. The main difference between IRIX and Windows NT here is that on Windows NT all of the WGL functions refer to the HDC as the drawable as opposed to using the HWND (handle to a window). On IRIX the GLX functions refer to the drawable directly. The HDC is a handle to a device context. Each window in Windows NT can have a number of HDCs. Each thread or process that requests the device context for an HWND gets its own unique HDC. In order to obtain the HDC for a given window there is a function called GetDC. The HDC can be obtained only after the window has been created.

The second piece is the XVisualInfo structure or on Windows NT the PixelFormat. This piece is needed to tell OpenGL and the operating system how the drawable is to be used. Since the drawable is just a canvas, we need this second piece to explain how we are going to use the canvas (for example, RGBA or color index rendering, pixel depth, Z buffer depth, and so on). If we set up a canvas for color index rendering, then making RGBA calls like glColor3f won't make any sense, and likewise if we configure the canvas for RGBA rendering, calls like glIndexi won't make any sense. On IRIX boxes the decision of how to configure the canvas can be made only once, and it needs to be made before the drawable is created. As well on IRIX, any process or thread that uses that drawable must do so with the original configuration. On Windows NT the configuration has to be set after the drawable has been created, and it can only be set once per thread or process. Once the PixelFormat has been set for a given HDC in a given thread, it can't be changed for that thread. This means that other threads or processes can reference the same drawable or window using a different configuration because each thread or process will get its own HDC.

The third piece is the OpenGL graphics context. The graphics context maintains the OpenGL state. On both IRIX and Windows NT the OpenGL graphics context is compatible only with a single configuration, and this is why the configuration must be set before the graphics context is created. Once created, the graphics context can be used with any drawable that has the same configuration as the one with which the graphics context was created. An OpenGL graphics context must be made current for any rendering commands to go through to the graphics pipeline and actually get rendered. Only a single graphics context may be current to a single thread at a time. This means that each thread can have only one current graphics context and a graphics context may not be current to two different threads at the same time.

OpenGL in Standard Windows

Now that we have a basic understanding of the pieces needed to render OpenGL, it is time to look at how those pieces are created and how this fits into the framework of a simple standard windows application. The three main topics that I am going to discuss here are how to create an OpenGL compatible window, what to do once the window has been created, and handling important messages.

For matters of this discussion I am going to consider a simple windows application as one in which there is a single main window that is registered and created in the WinMain function.

How to Create an OpenGL Compatible Window

A simple windows program would have a WinMain function that registers and creates a main window. It is this main window that will act as the canvas. On Windows 95 and 98 you need to set up the window class to use its own DC so that DCs aren't shared. On Windows NT this isn't necessary, but is still a good idea. It is also a good idea to set up the main window to clip its children and sibling windows. This is the only change that is needed before window creation in order to support OpenGL rendering. All of the other changes occur after window creation. Below I have included a simple WinMain function with the changed sections added in bold:

  int APIENTRY WinMain(HINSTANCE _instance, HINSTANCE _prevInst, 
                       LPSTR _cmdLine, int _cmdShow) 
  { 
          MSG msg ; 
          WNDCLASSEX wndClass; 
          char *className = "OpenGL"; 
          char *windowName = "Simple OpenGL Program"; 
  
          winWidth = winHeight = 500; 
  
          wndClass.cbSize        = sizeof (WNDCLASSEX) ; 
          wndClass.style         = CS_HREDRAW | CS_VREDRAW | 
          			 CS_OWNDC; 
          wndClass.lpfnWndProc   = WndProc; 
          wndClass.cbClsExtra    = 0 ; 
          wndClass.cbWndExtra    = 0 ; 
          wndClass.hInstance     = _instance ; 
          wndClass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ; 
          wndClass.hCursor       = LoadCursor (NULL, IDC_ARROW) ; 
          wndClass.hbrBackground = (HBRUSH) 
          			 GetStockObject(WHITE_BRUSH); 
          wndClass.lpszMenuName  = NULL ; 
          wndClass.lpszClassName = className ; 
          wndClass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ; 
  
          RegisterClassEx (&wndClass) ; 
  
          wnd = CreateWindow(className, windowName, 
          		   WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | 
          		   WS_CLIPSIBLINGS, 
          		   100,          // initial x position 
          		   100,          // initial y position 
          		   winWidth,     // winWidth 
          		   winHeight,    // winHeight 
          		   NULL,         // parent window handle 
          		   (HMENU) NULL, // window menu handle 
          		   _instance,    // program instance handle 
          		   NULL) ; 
  
          ShowWindow(wnd, _cmdShow); 
          UpdateWindow(wnd); 
  
          while (GetMessage (&msg, NULL, 0, 0)) 
          { 
          	TranslateMessage (&msg) ; 
          	DispatchMessage (&msg) ; 
          } 
          return msg.wParam ; 
  } 

Once the window is created there are five main messages that need to be handled in an OpenGL program (actually there are quite a few others, but five is the minimum). The following messages need to be handled: WM_CREATE, WM_PAINT, WM_SIZE, WM_ERASEBKGND, and WM_DESTROY. I will explain each of these messages and what needs to be done once they arrive.

What to Do Once the Window Has Been Created

The first is the WM_CREATE message. This message tells us that the window has been created, so this is the first opportunity to get the HDC and configure the drawable the way we want it. I usually set up an Init function to handle all of this. The Init function has to complete the following four tasks in order: get the HDC, choose the PixelFormat, set the PixelFormat, and create the OpenGL graphics context. The following sample Init function shows each of the four tasks in bold:

  void Init() 
  { 
	PIXELFORMATDESCRIPTOR   pfd; 
	int     pixelFormat; 

	dc = GetDC(wnd); 

	pfd.nSize =             sizeof(PIXELFORMATDESCRIPTOR); 
	pfd.nVersion =          1; 
	pfd.dwFlags =           PFD_DRAW_TO_WINDOW | 
				PFD_SUPPORT_OPENGL | 
				PFD_DOUBLEBUFFER; 
	pfd.iPixelType =        PFD_TYPE_RGBA; 
	pfd.cColorBits =        24; 
	pfd.cRedBits =          0; 
	pfd.cRedShift =         0; 
	pfd.cGreenBits =        0; 
	pfd.cGreenShift =       0; 
	pfd.cBlueBits =         0; 
	pfd.cBlueShift =        0; 
	pfd.cAlphaBits =        0; 
	pfd.cAlphaShift =       0; 
	pfd.cAccumBits =        0; 
	pfd.cAccumRedBits =     0; 
	pfd.cAccumGreenBits =   0; 
	pfd.cAccumBlueBits =    0; 
	pfd.cAccumAlphaBits =   0; 
	pfd.cDepthBits =        0; 
	pfd.cStencilBits =      0; 
	pfd.cAuxBuffers =       0; 
	pfd.iLayerType =        PFD_MAIN_PLANE; 
	pfd.bReserved =         0; 
	pfd.dwLayerMask =       0; 
	pfd.dwVisibleMask =     0; 
	pfd.dwDamageMask =      0; 

	pixelFormat = ChoosePixelFormat(dc, &pfd); 

	DescribePixelFormat(dc, pixelFormat, 
			   sizeof(PIXELFORMATDESCRIPTOR), 
			   &pfd); 

	if (pfd.dwFlags & PFD_NEED_PALETTE || 
	    pfd.iPixelType == PFD_TYPE_COLORINDEX ) 
		BuildPalette( &pfd ); 

	if ( pfd.dwFlags & PFD_DOUBLEBUFFER ) 
		doubleBuffered = TRUE: 
	else 
		doubleBuffered = FALSE; 

	if(SetPixelFormat(dc, pixelFormat, &pfd) == FALSE) 
		exit(1); 

	rc = wglCreateContext(dc); 

	wglMakeCurrent( dc, rc ); 

	SetupScene(); 

	wglMakeCurrent( NULL, NULL ); 
  } 

The first task is to get the HDC. The HDC can be obtained from the HWND or handle to the main window once that window has been created by calling GetDC. It is worthwhile to keep a copy of the HDC around since it is used for creating a new context, making the context current, and swapping buffers.

The second task is to choose the PixelFormat. Since the PixelFormat is just an int that references a format from the list of available formats, it is really more similar to a Visual Id than it is to a VisualInfo structure. You can use the ChoosePixelFormat function to get a PixelFormat that fulfills your needs, or you can go through the list of available formats and manually choose the one that suits you. All you really need is the int in order to set the PixelFormat on the HDC. Note that the format-choosing algorithm of ChoosePixelFormat isn't quite the same as the algorithm of glXChooseVisual. By this I mean that glXChooseVisual assumes the values that you pass in are a minimum requirement and therefore returns NULL if nothing matches. ChoosePixelFormat, on the other hand, assumes the values you pass in are more like a suggestion and it will return the closest match that it can find even if it doesn't meet your requirement (you might even get back a ColorIndex PixelFormat when you requested RGBA).

The third task is to set the pixel format on the HDC. Setting the PixelFormat on the HDC is what causes the HDC to take on a certain configuration. This can be done only once per thread and can't be changed once it is set. You use the SetPixelFormat function to accomplish this, passing in the int of the PixelFormat that you intend to use.

The last task that needs to be accomplished in the Init function is to create an OpenGL graphics context. This is needed to keep track of the OpenGL state and to allow rendering calls to go to the graphics pipe. You obtain the graphics context by calling wglCreateContext and passing in the HDC. The graphics context returned can be used to render to any HDC that was configured with the same PixelFormat. It is important that the PixelFormat be set on the HDC before the call to wglCreateContext, otherwise OpenGL won't know how the context is to be configured when it is created. In case you are wondering about the call to BuildPalette, I will explain this in the follow-up article on Windows Palettes and OpenGL.

Handling Important Messages

At this point we have the three main pieces that I spoke about in "OpenGL Rendering Overview," so you are probably wondering why we need to handle any more windows messages. Why can't we just start rendering now? Well, in reality we can, but there are a few more things that we should do so that things will continue to run smoothly after our first rendering pass. This is where the other four messages come in: WM_PAINT, WM_SIZE, WM_ERASEBKGND, and WM_DESTROY. Now that we have the graphics context, we will need to make it current before calling any OpenGL functions.

When the WM_PAINT message comes in, it means that Windows wants you to redraw the contents of your window. I have found that in the Windows NT world it is best to do what Windows wants when it wants you to, so when the WM_PAINT message comes in I usually choose to call my main draw function. You will notice a call to ValidateRect at the end of my draw function. This is needed to tell Windows that I have drawn to the window and any exposed areas have been addressed. A simple draw function follows.

  void Draw() 
  { 
	wglMakeCurrent(dc, rc); 

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 

	glMatrixMode( GL_MODELVIEW ); 

	glPushMatrix(); 
	glTranslatef( 0.0, 0.0, -250.0 ); 

	glColor4f( 1.0, 0.0, 0.0, 1.0 ); 
	glBegin( GL_QUADS ); 
	glVertex2f( -20.0f, -20.0f ); 
	glVertex2f( -20.0f,  20.0f ); 
	glVertex2f( 20.0f, 20.0f ); 
	glVertex2f( 20.0f, -20.0f ); 
	glEnd(); 

	glPopMatrix(); 

	if ( doubleBuffered ) 
		SwapBuffers(dc); 
	else 
		glFlush(); 

	wglMakeCurrent( NULL, NULL ); 

	ValidateRect(wnd, NULL); 
  } 

The WM_SIZE message is just a message telling you that the window size has changed. The WM_SIZE message is a good time to reset your OpenGL viewport to the new window size. The call to InvalidateRect in the function below tells Windows that the window has changed and forces it to send a WM_PAINT message to the window. A sample resize function follows:

  void Resize() 
  { 
	wglMakeCurrent(dc, rc); 
	glViewport(0,0,winWidth, winHeight); 
	InvalidateRect(wnd, NULL, FALSE); 

	wglMakeCurrent( NULL, NULL ); 
  } 

The WM_ERASEBKGND message is a special message that Windows sends when it wants you to clear the background of your window before it sends a WM_PAINT message. If you don't handle the WM_ERASEBKGND message, Windows will handle it for you, and this can cause quite a bit of unnecessary flickering. You need to return a nonzero value to this message; otherwise, Windows will assume that the window hasn't been erased and will mark it as still needing to be erased.

I added the WM_DESTROY message to the list since it is sent when the window is being destroyed and is the best place to do any cleanup that needs to be done. Generally I use this opportunity to delete the OpenGL graphics context and release the HDC back to the system. A simple cleanup function follows:

  void Quit() 
  { 
	wglMakeCurrent(NULL, NULL); 
	wglDeleteContext(rc); 
	ReleaseDC(wnd, dc); 
	PostQuitMessage(0); 
  } 

OpenGL Integrated with MFC

Integrating OpenGL with MFC isn't all that different from getting OpenGL to work in a standard Windows application. All the same pieces are needed in the same order; it is just accessing those pieces within the MFC framework that is a bit different. I won't spend a lot of time explaining MFC, but a base overview would probably be useful. In this section I will cover setting up Visual Studio for OpenGL, handling the CView creation, and dealing with messages.

Setting up Visual Studio for OpenGL

MFC supplies an application framework with four main parts that allows application writers to skip most of the skeleton code for an application and only add the code that is specific to their program. The four parts are the CWinApp (main application class), CMainFrame (outer window frame including menus), CView (main work area), and CDocument (data behind the application). Windows calls this the Document/View architecture; there are quite a few articles in the MSDN about this topic, so I won't add to the confusion here. The basic concept is that the CMainFrame acts as the container for any and all CViews within the application. The CView acts like a window into the data (for instance, you can view the data as a bar chart, a pie chart, or a list of numbers). The CDocument is where all the actual data is stored. There is nothing in MFC that requires you to follow this framework, but Microsoft makes it somewhat painful if you don't. You can actually have a CView that contains all of the data and not use the CDocument at all. For our purposes we will try to use the framework as it was intended.

Since the CView is the window into the data and it contains our canvas, it is the most logical place for us to set up and store all of our necessary rendering pieces. We should set up our drawable, configure it, and create our graphics context all within the confines of the CView class. The CView class is also going to be responsible for handling all of our relevant messages. The CView will only rely upon the CDocument to draw the data when it is ready to update the drawable; otherwise, it should handle almost everything else.

For the sake of this article I am assuming that you know enough about Visual C++ to at least create a Single Document Interface (SDI) MFC application. It is just a matter of clicking the correct buttons in the application wizard. Once you have a base SDI MFC application then we can start modifying it to support OpenGL.

The first step is to include the OpenGL header files so that we have prototypes for the OpenGL functions. I have found that the StdAfx.h file that the appwizard creates for you is the best place to add these includes. We start by opening the StdAfx.h file and including the following two headers near the end of the file: <gl/gl.h> and <gl/glu.h>. Once this is done we need to add the OpenGL and GLU libraries to the link line so that we pick up the OpenGL functions when we compile. On Windows NT the default OpenGL library is called opengl32.lib and the GLU library is called glu32.lib. We need to add these to the link line by doing the following: Click on the Project pull-down and select the Settings button. When the dialog appears go to the Link tab and add opengl32.lib and glu32.lib to the Object/Library Modules field and then click OK. That's it; we are now ready to start adding code.

Handling the CView Creation

The changes we make will follow the same flow as for the standard Windows application. We will start with the window creation. Just like for the standard application, we need to make sure the window is created with its own DC and that it clips its siblings and children. Since this needs to occur before the window is created, it needs to be handled in a convenient function called PreCreateWindow. The appwizard automatically adds this method to the CView class for you, so you should be able to find it in your *View.cpp file. We can make the needed changes by modifying the CREATESTRUCT that is passed in before it gets passed along to the base class PreCreateWindow function. Your function will end up looking something like the following:

  BOOL CSimpleMFCView::PreCreateWindow 
  (CREATESTRUCT& cs) 
  { 
    // TODO: Modify the Window class or styles 
    here by modifying 
    //  the CREATESTRUCT cs 

    cs.style |= ( WS_CLIPCHILDREN | 
    WS_CLIPSIBLINGS | CS_OWNDC ); 

    return CView::PreCreateWindow(cs); 
  }

At this point we have done the exact same task as modifying the WinMain function in the standard Windows application, and it is necessary to handle the first window message, WM_CREATE. As a matter of fact, we can cut and paste the code logic from our Init function into our WM_CREATE message handler with only a minor change. Since the CView class keeps a copy of the HWND for us in a data member called m_hWnd, we don't need to store this value in a wnd pointer; otherwise, the code is identical.

The first step is to create the message handler for our WM_CREATE message. Use the Class Wizard to add an OnCreate method to the CView class. This method will handle all of the OpenGL initialization, including getting a handle to the DC, creating the graphics context, and setting up the OpenGL state.

Go to the OnCreate function of your view class and enter the Init code. When you are finished the function should look like this:

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

	// TODO: Add your specialized creation code here 

	PIXELFORMATDESCRIPTOR   pfd; 
	int     pixelFormat; 

	m_hDC = ::GetDC(m_hWnd); 

	pfd.nSize =             sizeof(PIXELFORMATDESCRIPTOR); 
	pfd.nVersion =          1; 
	pfd.dwFlags =           PFD_DRAW_TO_WINDOW | 
				PFD_SUPPORT_OPENGL | 
				PFD_DOUBLEBUFFER; 
	pfd.iPixelType =        PFD_TYPE_RGBA; 
	pfd.cColorBits =        24; 
	pfd.cRedBits =          0; 
	pfd.cRedShift =         0; 
	pfd.cGreenBits =        0; 
	pfd.cGreenShift =       0; 
	pfd.cBlueBits =         0; 
	pfd.cBlueShift =        0; 
	pfd.cAlphaBits =        0; 
	pfd.cAlphaShift =       0; 
	pfd.cAccumBits =        0; 
	pfd.cAccumRedBits =     0; 
	pfd.cAccumGreenBits =   0; 
	pfd.cAccumBlueBits =    0; 
	pfd.cAccumAlphaBits =   0; 
	pfd.cDepthBits =        0; 
	pfd.cStencilBits =      0; 
	pfd.cAuxBuffers =       0; 
	pfd.iLayerType =        PFD_MAIN_PLANE; 
	pfd.bReserved =         0; 
	pfd.dwLayerMask =       0; 
	pfd.dwVisibleMask =     0; 
	pfd.dwDamageMask =      0; 

	pixelFormat = ChoosePixelFormat(m_hDC, &pfd); 

	DescribePixelFormat(m_hDC, pixelFormat, 
			    sizeof(PIXELFORMATDESCRIPTOR), 
			    &pfd); 

	if (pfd.dwFlags & PFD_NEED_PALETTE || 
	    pfd.iPixelType == PFD_TYPE_COLORINDEX ) 
		BuildPalette( &pfd ); 

	if ( pfd.dwFlags & PFD_DOUBLEBUFFER ) 
		doubleBuffered = TRUE; 
	else 
		doubleBuffered = FALSE; 

	if(SetPixelFormat(m_hDC, pixelFormat, &pfd) == FALSE) 
		 exit(1); 

	m_hRC = wglCreateContext(m_hDC); 
	wglMakeCurrent( m_hDC, m_hRC ); 
	SetupScene(); 
	wglMakeCurrent( NULL, NULL ); 
	return 0; 
  } 

We are going to store the graphics context (m_hRC) and the device context (m_hDC) on the view so that we can access them when we need to. Also note that we need to make sure we call the global ::GetDC function because there is also a GetDC method on the CView class that would return a CDC, a DC class, instead of the HDC that we want.

Dealing with Messages

After modifying the OnCreate function we need to deal with the actual drawing of the scene. In the standard Windows application we handled the redraw whenever we saw the WM_PAINT message. When dealing with MFC the application wizard automatically creates an OnDraw method that is called whenever a redraw is needed. It is in the OnDraw method that we will add the code from our draw function. We can cut and paste the logic from the standard Windows section, replacing the HDC and HGLRC with the appropriate member variables. When we are done the function should look like the following:

  void CSimpleMFCView::OnDraw(CDC* pDC) 
  { 
	CSimpleMFCDoc* pDoc = GetDocument(); 
	ASSERT_VALID(pDoc); 

	// TODO: add draw code for native data here 
	wglMakeCurrent(m_hDC, m_hRC); 

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 

	glMatrixMode( GL_MODELVIEW ); 

	glPushMatrix(); 
	glTranslatef( 0.0, 0.0, -250.0 ); 

	glColor4f( 1.0, 0.0, 0.0, 1.0 ); 
	glBegin( GL_QUADS ); 
	glVertex2f( -20.0f, -20.0f ); 
	glVertex2f( -20.0f,  20.0f ); 
	glVertex2f( 20.0f, 20.0f ); 
	glVertex2f( 20.0f, -20.0f ); 
	glEnd(); 

	glPopMatrix(); 

	if (doubleBuffered ) 
	    SwapBuffers(m_hDC); 
	else 
	    glFlush(); 

	wglMakeCurrent( NULL, NULL ); 

	ValidateRect(NULL); 
  } 

For simplicity's sake I added the drawing logic to the CView class, but in most programs I add the logic to the CDocument class. I would start by adding a public method called draw to my CSimpleMFCDoc class and then call this method from the CView class. In this case the OnDraw method would look like this:

  void CSimpleMFCView::OnDraw(CDC* pDC) 
  { 
	CSimpleMFCDoc* pDoc = GetDocument(); 
	ASSERT_VALID(pDoc); 

	// TODO: add draw code for native data here 
	wglMakeCurrent(m_hDC, m_hRC); 

	pDoc->Draw(); 

	if (doubleBuffered ) 
	    SwapBuffers(m_hDC); 
	else 
	    glFlush(); 

	wglMakeCurrent( NULL, NULL ); 

	ValidateRect(NULL); 
	// Do not call CView::OnPaint() for painting messages 
  } 

The draw logic from the CView class could then be moved into the CDocument class. This would allow the document, which contains all of the data, to decide how it should be drawn.

We now need to add message handlers for three other messages just like we did for the WM_CREATE message. We need handlers for the WM_SIZE, WM_ERASEBKGND, and WM_DESTROY messages. Use the Class Wizard to create methods named Onsize, OnEraseBkgnd, and OnDestroy. We are going to add the code logic from the standard Windows functions. When we are done the functions should look like the following:

OnSize (refer also to void Resize code sample)

  void CSimpleMFCView::OnSize(UINT nType, int cx, int cy) 
  { 
	CView::OnSize(nType, cx, cy); 

	// TODO: Add your message handler code here 
	wglMakeCurrent( m_hDC, m_hRC ); 
	glViewport( 0, 0, cx, cy); 

	wglMakeCurrent( NULL, NULL ); 
	InvalidateRect(NULL); 
  } 

OnEraseBkgnd (refer also to WM_ERASEBKGND description)

  void CSimpleMFCView::OnEraseBkgnd(CDC* pDC) 
  { 
	// TODO: Add your message handler code here and/or call default 

	return 1; 
  } 

OnDestroy (refer also to void Quit code sample)

  void CSimpleMFCView::OnDestroy() 
  { 
	CView::OnDestroy(); 

	// TODO: Add your message handler code here 
	wglMakeCurrent( NULL, NULL ); 
	wglDeleteContext( m_hRC ); 
	::ReleaseDC( m_hWnd, m_hDC ); 
  } 

With this you should now be able to get a simple MFC program with OpenGL working properly.

This article covered OpenGL rendering, OpenGL in standard Windows, and the integration of OpenGL with the Microsoft Foundation Classes. Look for a discussion of Windows palettes and OpenGL in the next issue of Developer News.

feedback


Copyright © 1998, Silicon Graphics, Inc.