OpenGL rendering outside WM_PAINT

Greetings for everyone

Could anyone tell me if and how (code sample would be nice) can I render through OpenGL outside the WM_PAINT message handler(for example in function called by timer)???

Thanks for all answers.

you are talking about OpenGL in an MFC application, right?

This is pretty simple. You just need your own loop that handles i/o events and you draw in the loop. You also need your event handling callback but you can update the OpenGL stuff in that too for resize events.

Here’s some code from an asteroids game I wrote when learning this stuff a long time ago. You can see the entry point & main loop and the event handler.

// Entry point of all Windows programs
int APIENTRY WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg; // Windows message structure
WNDCLASS wc; // Windows class structure
HWND hWnd; // Storeage for window handle
static HDC hDC; // Private GDI Device context
unsigned int seed;
LARGE_INTEGER hitime;
BOOL HighRes;

// Register Window style
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);

// No need for background brush for OpenGL window
wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;
wc.lpszClassName = lpszAppName;

// Register the window class
if(RegisterClass(&wc) == 0)
return FALSE;

// Create the main application window
hWnd = CreateWindow(
lpszAppName,
lpszAppName,

  		// OpenGL requires WS_CLIPCHILDREN and WS_CLIPSIBLINGS
  		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,

  		// Window position and size
  		WIN_ORIGINX, WIN_ORIGINY,
  		WIN_SIZEX, WIN_SIZEY,
  		NULL,
  		NULL,
  		hInstance,
  		NULL);

// If window was not created, quit
if(hWnd == NULL)
return FALSE;

// Display the window
ShowWindow(hWnd,SW_SHOW);
//SetForegroundWindow(hWnd);
//UpdateWindow(hWnd);
// Store the device context
hDC = GetDC(hWnd);

InitializeSound(hWnd);

// seed rand with start time of the program
HighRes = QueryPerformanceFrequency(&hitime);
if(HighRes)
{
QueryPerformanceCounter(&hitime);
seed = hitime.LowPart;
}
else
{
seed = (unsigned int)GetTickCount();
}
srand(seed);

init_numerals();
init_letters();
init_game();
first_title();

// Process application messages until the application closes
while(Running)
{
ClearScreen();

  while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
  {
  	// No need to translate key data, just polling keys at iteration resolution
  	//TranslateMessage(&msg);
  	DispatchMessage(&msg);
  }

  dtime = DeltaTime()/1000.0f;

  // Timers really blow chunks on Windows 95 so run code here
  dtime *= 0.5f; // half time because we're iterating twice

  UpdateFunction();
  UpdateFunction();

  // Call OpenGL drawing code
  RenderScene();

  // Call function to swap the buffers
  SwapBuffers(hDC);

  // Block here, some cards don't block on vertical retrace or screen clear
  // or a bunch of other stuff and it can hose the latency and or timing
  // I sure don't want to miss the opportunity work with the latest timing
  // info during screen clear which I'd otherwise have to miss.
  glFinish();

}

return msg.wParam;
}

// Window procedure, handles all messages for this program
LRESULT CALLBACK WndProc( HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
static HGLRC hRC; // Permenant Rendering context
static HDC hDC; // Private GDI Device context

switch (message)
{
// Window creation, setup for OpenGL
case WM_CREATE:
// Store the device context
hDC = GetDC(hWnd);

  	// Select the pixel format
  	SetDCPixelFormat(hDC);		

  	// Create the rendering context and make it current
  	hRC = wglCreateContext(hDC);
  	wglMakeCurrent(hDC, hRC);

  	// initialises anti-aliasing for line drawing
        gl_AA(1);
  	break;

  // Window is being destroyed, cleanup
  case WM_DESTROY:

  	Running = 0;

  	// cancel audio
  	ShutdownSound();

  	// Deselect the current rendering context and delete it
  	wglMakeCurrent(hDC,NULL);
  	wglDeleteContext(hRC);

  	// Delete the palette
  	if(hPalette != NULL)
  		DeleteObject(hPalette);

  	// Tell the application to terminate after the window
  	// is gone.
  	PostQuitMessage(0);
  	break;

  // Window is resized.
  case WM_SIZE:
  	// Call our function which modifies the clipping
  	// volume and viewport
  	ChangeSize(LOWORD(lParam), HIWORD(lParam));
  	break;

  // Thrust increase
  //case WM_CHAR:
  case WM_KEYDOWN:
  	if(!(lParam & (0x01 << 30)) ) // check for new press
  	{
  		if(wParam == 'K')
  		  Thrusting = 1;
  		if(wParam == 'D')
  		  Turning -= 1;
  		if(wParam == 'A')
  		  Turning += 1;
  		if(wParam == 'L')
  		  Firing = 1;
  		if(wParam == ' ')
  		  Hyperspace = 1;
  		if(wParam == 'P')
  		  NewGame = 1;
  		if(wParam == 'Q')
  		{
  		  if(AntiAlias)
  			  gl_AA(0);
  		  else
  			  gl_AA(1);
  		}
  		if(wParam == 'H')
  		{
  			if(GameOver)
  				GameOver = 2;
  		}
  	}
  	break;

  case WM_KEYUP:
  	if(wParam == 'K')
  	  Thrusting = 0;
  	if(wParam == 'A')
  	  Turning -= 1;
  	if(wParam == 'D')
  	  Turning += 1;
  	break;
  // Windows is telling the application that it may modify
  // the system palette.  This message in essance asks the 
  // application for a new palette.
  case WM_QUERYNEWPALETTE:
  	// If the palette was created.
  	if(hPalette)
  	{
  		int nRet;

  		// Selects the palette into the current device context
  		SelectPalette(hDC, hPalette, FALSE);

  		// Map entries from the currently selected palette to
  		// the system palette.  The return value is the number 
  		// of palette entries modified.
  		nRet = RealizePalette(hDC);

  		// Repaint, forces remap of palette in current window
  		InvalidateRect(hWnd,NULL,FALSE);

  		return nRet;
  	}
  	break;

    default:   // Passes it on if unproccessed
        return (DefWindowProc(hWnd, message, wParam, lParam));

}

return (0L);

}

[This message has been edited by dorbie (edited 12-30-2003).]

If you really need to use MFC, for an interractive application basically anything can go off and trigger a draw, the most elegant way to do this is call invalidate region on your own window and let the event handlers take care of it, they should automatically concatenate. This is from picking example where I adjust the draw mode in the callback for picking before triggering the draw but any handler could do the same thing, for example a mouse move:

void CTest1View::OnLButtonDown(UINT nFlags, CPoint point)
{
m_DoPick = 1; // pick grid in the OnDraw function
refreshDisplay();
break;
}
void CTest1View::refreshDisplay(void)
{
CRect rectWindow(0, 0, m_winx, m_winy);
InvalidateRect(rectWindow);
}

void CTest1View::OnDraw(CDC* pDC)
{
SetupViewingXform();
if(m_DoPick)
PickGrid(0);

You use an itimer but in my experience this gives bad results and is inefficient for most apps.

Well, actually I want to run the whole engine internal loop (with OpenGL rendering) in the callback function for multimedia timer - timeSetEvent().
I process there all needed things and then something like that appears:

  • glClear()
  • matrix setup
  • render map
  • glFinish()
  • SwapBuffers()
    But everything what I get is gray screen, even when I use InvalidateRect() and then ValidateRect() in WM_PAINT. (Is is possible to do this in that way?). When I move whole loop to the WM_PAINT handler everything works fine.

PS. This is mainly intended for simple Win32 API window, but may be also ported to the MFC later…

Thanks Dorbie.
I tested your code and it works fine for me. Obviously it gives similar (if not the same) effect compare to using multimedia timer. And it looks more clean and just better.

I am satisfied with yor help.

But I have two other questions related with this:

  1. Does the processing and rendering code from the loop (after PeekMessage() inner loop) will run at similar speed like the one from timer callback function?
  2. Can I (and how) plug into the message pump (like in your code) in MFC based application? Is there some function I should overload?

PS. Happy New Year for everyone, especially OpenGL programmers!

  1. It runs better, an interrupt timer tends to have a much more unreliable framerate thanks to scheduling issues AFAIK you go out to lunch after you service the timer and must rely on being woken up and that causes a lot of jitter in the frame rate. Ron Fosners book on OpenGL I think used an itimer in it’s example code and I abandoned that after trying it (it’s been a while since I mucked with an interrupt timer driven draw).

  2. These are standard methods of the MFC classes like CView and ofcourse any methods you care to add. So yes you overload the event handler methods like OnLButtonDown and OnDraw and they are automatically invoked. If memory serves there’s actually some horrible macro to register some of these functions to avoid the virtual function overhead but the class wizards do most of this under the covers if you use them. The class wizards create callbacks automatically and you can add your own. Yuck, you made me look, yup here’s my example code where I have the wizard created methods and some other message handling I’ve defined myself to handle communication from a modeless dialog.



  from the C++ file


IMPLEMENT_DYNCREATE(CTest1View, CView)

BEGIN_MESSAGE_MAP(CTest1View, CView)
ON_MESSAGE(WM_HIDE, OnHide)
ON_MESSAGE(WM_TOGALPHA, OnAlpha)
ON_MESSAGE(WM_TOGBUFFER, OnBuffer)
ON_MESSAGE(WM_PICKSIZE, OnPicksize)
ON_MESSAGE(WM_INTERACTION, OnInteraction)
//{{AFX_MSG_MAP(CTest1View)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_WM_ERASEBKGND()
ON_WM_SIZE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_RBUTTONDOWN()
ON_WM_RBUTTONUP()
ON_WM_RBUTTONDBLCLK()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()



  and from the class header file


// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CTest1View)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL

// Generated message map functions
protected:
//{{AFX_MSG(CTest1View)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

The InvalidateRect call in the event handler ultimately results in a call to OnDraw via the framework. This way you have all your rendering happen in the same place and it’s triggered elegantly by user input via the event handler. Note with some animating app in MFC you’d need an interrupt driven approach to keep generating the draw events in the absence of user input (my app didn’t need it and many other apps (like 3D tools and cad don’t), unless you posted an invalidate rect at the end of your ondraw (hmm… never tried that), that would be ugly.

[This message has been edited by dorbie (edited 12-31-2003).]

@dorbie:

ha ! cool ! obviously you are also not using Begin/EndPaint() structure goofiness !!! and the code works.
a few weeks ago there was a guy in another thread, who told me that without this construction it won’t be able to get anything on the screen because no more messages are processed…

I didn’t really know it’d be useful to anyone. I wrote the MFC stuff years ago as a picking tutorial and presented it at a joint SGI 3DLabs seminar, the code never made it onto the CDROM (to my mild annoyance). I kinda assumed that it was how things were done in MFC, I probably got the technique from Kruglinski’s “Inside Visual C++” (without the OpenGL of course), but I’m not sure. Adding it in an interactive 3D app on mousemoves etc is obviously a trivial application of the intended use of InvalidateRect.

[This message has been edited by dorbie (edited 01-01-2004).]