dcsimg
December 8, 2016
Hot Topics:

Printing OpenGL in MFC

  • August 8, 1998
  • By John Wagner
  • Send Email »
  • More Articles »

I have seen a number of OpenGL view classes in various magazines. NONE of them addressed printing. Sigh.

So, suppose you have an OpenGL view class with member data storing the HGLRC and CDC (which are required for an OpenGL-enabled window):


		


class COpenGLView : public CView {

	...

protected:

	CDC *m_pDC;

	HGLRC m_hRC;

	...

};

In your OnCreate override, you will create these:


int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct) {

	if (CView::OnCreate (lpCreateStruct) == -1) return (-1);

	m_pDC = new CClientDC (this);

	if (m_pDC == NULL) return (-1);

	//  Set the DC's pixel format...

	if (!SetDCPixelFormat (m_pDC)) return (-1);

	m_hRC = wglCreateContext (m_pDC->GetSafeHdc ());

	if (!wglMakeCurrent (m_pDC->GetSafeHdc (), m_hRC)) return (-1);

	glClearColor (1.0f, 1.0f, 1.0f, 1.0f);

	glClearDepth (1.0);

	return (0);

}

Notice that I call wglMakeCurrent () here. Some authors do it at the beginning of OnDraw (). That is probably because their code didn't work without it! Bear in mind that wglMakeCurrent () makes a specific window instance the current OpenGL window. In an MDI, with two documents open, you need to make sure that each view instance makes its own window current before drawing into it. Therefore, calling wglMakeCurrent () in OnCreate () is not enough -- otherwise after the second view is created, all subsequent OpenGL calls in the first view will get rendered in the second!

Many authors call wglMakeCurrent () at the beginning of the view's OnDraw() method -- resist the temptation! Here is (roughly) my COpenGLView::OnPrint():



void COpenGLView::OnPrint(CDC* pDC, CPrintInfo* pInfo) {

	//  Let's not worry for now about print preview...

	if (pInfo->m_bPreview) return;  //  or print a message to preview

	window...

		

		UINT CurPage = pInfo->m_nCurPage;

	GLsizei HRes = pDC->GetDeviceCaps(HORZRES);

	GLsizei VRes = pDC->GetDeviceCaps(VERTRES);

	GLsizei HPixelsPerInch = pDC->GetDeviceCaps(LOGPIXELSX);

	GLsizei VPixelsPerInch = pDC->GetDeviceCaps(LOGPIXELSY);

	pDC->SetMapMode (MM_TEXT);

	// m_LMargin, etc are margins, in inches...

	GLint l = (GLint) (m_LMargin*HPixelsPerInch);

	GLint r = (GLint) (m_RMargin*HPixelsPerInch);

	GLint t = (GLint) (m_TMargin*VPixelsPerInch);

	GLint b = (GLint) (m_BMargin*VPixelsPerInch);

	//  Image width and height...

	GLsizei w = HRes - l - r;

	GLsizei h = VRes - t - b;

	//  Probably don't need this...

	int SavedDC = pDC->SaveDC ();

	if (CurPage == 1) {

		//  Save the current OpenGL settings...

		HDC   hDCOld   = wglGetCurrentDC ();

		HGLRC hGLRCOld = wglGetCurrentContext ();

		//  Make the printer the current

		//  OpenGL rendering device...

		HGLRC hGLRCPrinter = wglCreateContext (pDC->GetSafeHdc ());

		BOOL bRet = wglMakeCurrent (pDC->GetSafeHdc (), hGLRCPrinter);

		

		ASSERT (bRet);

		glClearColor (1.0f, 1.0f, 1.0f, 1.0f);

		glClearDepth (1.0);

		//  This makes a square image...

		if (w < h) h = w;

		if (h < w) w = h;

		//  This centers the images...

		l = (HRes - w)/2;

		b = (VRes - h)/2;

		//

		glViewport(l, b, w, h);

		OnDraw (pDC);

		//  Go back to the saved OpenGL settings...

		wglMakeCurrent (hDCOld, hGLRCOld);

		wglDeleteContext(hGLRCPrinter);

	}

	//  Probably don't need this...

	if (SavedDC != 0) pDC->RestoreDC (SavedDC);

}

Notice that OnPrint () calls OnDraw (). If in OnDraw () you were to call wglMakeCurrent (), you would no longer be printing...you would be redrawing the view! It should also be noted that setting and restoring the current OpenGL device can be done in OnBeginPrinting () and OnEndPrinting (); for simplicity, I have put all the code here.

So, now printing OpenGL is simple. Let's go back to drawing...

In order to make sure your drawing goes to the correct window, you need to call wglMakeCurrent from SOMEWHERE OTHER THAN OnDraw (). But where? The candidates are OnActivateView () and OnUpdate ().

Some of the time, calling it in an override of OnActivateView () might be enough. However, there is one scenario where this will not be enough: MDI apps when you have created a second CMDIChildWnd frame via the New Window item in the Window menu. This creates additional views into the same document. Now you can have the situation where the active view does something which causes the document to call UpdateAllViews (), which calls the inactive view's OnUpdate () method, which will draw into the wrong window!

Alternatively, you can override OnUpdate (), and call wglMakeCurrent () there. You might be tempted to do this:



void COpenGLView::OnUpdate (CView* pSender, LPARAM lHint, CObject* pHint) {

	wglMakeCurrent (m_pDC->GetSafeHdc (), m_hRC);

	CView::OnUpdate (pSender, lHint, pHint);

}

Unfortunately, this won't work all the time. CView::OnUpdate () simply invalidates the view's window, which generates a WM_PAINT message. This WM_PAINT is queued up, and handled at some later time...in particular, possibly after calling another view's OnUpdate () method (that is, after another window has been made the current OpenGL rendering device). There is only one conclusion: you must call OnDraw () directly from OnUpdate () (and don't call the base class CView::OnUpdate ()). There are, of course, performance issues, but I generally ignore them.

Therefore, I usually override both methods: OnActivateView () and OnUpdate (), and I call wglMakeCurrent () in both. However, in OnUpdate (), I save and restore the current settings at the beginning and end of the method (just like in OnPrint ()). This is not necessary in OnActivateView ().



void COpenGLView::OnActivateView(BOOL bActivate,

								 CView* pActivateView, CView* pDeactiveView) {

	CView::OnActivateView(bActivate, pActivateView, pDeactiveView);

	if (bActivate) {

		wglMakeCurrent (m_pDC->GetSafeHdc (), m_hRC);

	}

}

void COpenGLView::OnUpdate (CView* pSender, LPARAM lHint, CObject* pHint) {

	//  Save the current OpenGL settings...

	HDC   hDCOld   = wglGetCurrentDC ();

	HGLRC hGLRCOld = wglGetCurrentContext ();

	//  Make this view the current OpenGL rendering context...

	BOOL bRet = wglMakeCurrent (m_pDC, m_hRC);

	//  Draw!!!

	OnDraw (m_pDC);

	//  Go back to the saved OpenGL settings...

	wglMakeCurrent (hDCOld, hGLRCOld);

	wglDeleteContext(hGLRCPrinter);

}

The reason to return to the previous settings in OnUpdate () is fairly subtle. Suppose you didn't, and the last view to be redrawn was not the active view. Suppose you then handled a mouse event in the active view, and you called one of the OpenGL calls that translated the mouse point into an (x,y,z) coordinate. It would translate it based upon the current OpenGL settings (ie. viewport, rotations, scalings, etc). If the two views were not identical, you would end up with improper translations. Since I use the mouse to translate, scale, etc, this is very important! In any case, by setting the current context in OnActivate (), and restoring the previous settings at the end of OnUpdate (), the active view is the current OpenGL context when the next (mouse or other) event occurs.

Now for print preview: I dunno! I tried to do it, but it did not work. I basically could not get the CDC configured to handle OpenGL. I don't know WHAT I was doing wrong. If anyone can get it working correctly, please, PLEASE let me know. It bugs the snot out of me!

In summary, I have tried to not only show the main printing solution, but have also tried to explain in detail some of the subtle problems that show up. Hopefully, the discussion will give you a better understanding of the issues involved, so that if you run into other OpenGL problems, you will be better equipped to deal with them.

John Wagner
Institute of Theoretical Dynamics
UC Davis






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel