Tuesday, March 24, 2009

Alternate OpenGL Rubberband in Vista and XP

For 3D CAD applications it is often useful to use glLogicOp( GL_XOR ) to draw a rubber band line in OpenGL. This is a classic method that allows the line to be drawn over the existing scene by simply inverting the colors along the rubber band line. The benefit of this method is that the scene does not need to be redrawn since the pixels can be inverted twice to restore the image to its original state. This results in tremendous performance gains for large models.

If you search for "opengl rubber band" you will undoubtedly run across the somewhat antiquated OpenGL FAQ. Further searching on this method reveals a few hits that suggest XOR-ing color bits in the front buffer. This is not correct. Writing to the front buffer is not legal in OpenGL and as of Vista, it can result in unpredictable behavior.

An alternate solution is to do rubber band drawing in the back buffer. However, once the back buffer has been swapped, it is no longer guaranteed to be in sync with the front buffer. In order to ensure that the back buffer stays in sync glCopyPixels(...) can be used on most graphics hardware to copy the front buffer to the back buffer before rubber banding. Below is some sample code showing one method to do rubber banding in OpenGL:

CRect wndRect;
GetClientRect( hWnd, wndRect );

//copy front buffer to back buffer...
glReadBuffer( GL_FRONT );
glDrawBuffer( GL_BACK );
glCopyPixels(wndRect.left, wndRect.top, wndRect.Width(), wndRect.Height(), GL_COLOR);

StartGL2DDrawing(); //set up ortho mode and disable lighting
DisableGLTransparency();
glOrtho(double(wndRect.left), double(wndRect.right), double(wndRect.bottom), double(wndRect.top), -1., 1. );
glDisable( GL_DEPTH_TEST );
glColor3f(1.f, 1.f, 1.f);
glPointSize( float(ptSize) );
glLineWidth( 1.f );

glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_XOR);

glBegin( GL_LINES );
glVertex2s( short( m_curStartPos.x ), short( m_curStartPos.y ) );
glVertex2s( short( m_curEndPos.x ), short( m_curEndPos.y ) );
glEnd();

glDisable(GL_COLOR_LOGIC_OP);

// finish - restore state
glEnable( GL_DEPTH_TEST );
EndGL2DDrawing();

SwapBuffers( wglGetCurrentDC() /*pDC->GetSafeHdc()*/ );

// toggle the visibility state
m_bIsOn = !m_bIsOn;
return;

5 comments:

Hugo said...

Unfortunately, on some Vista desktops you can't even *read* from the front buffer.

Jeff Lutzenberger said...

Hi Hugo,

Thanks for letting us know. So if reading the front buffer is not allowed, do you have a better solution?

Hugo said...

No :(
I’ve tried several different approaches (yours, using glCopyPixels() instead of SwapBuffers(), etc.) and all of them failed at some systems.

It seems to me that the only reliable way to do it is trough triple buffering (using an OpenGL extension for off-screen rendering)

Jeff Lutzenberger said...

This is just crazy! It is such a simple and fundamental thing for CAD and yet it is impossible to do now.

I have found that, while glCopyPixels can be slow it seems to be supported on all machines I have tested. Definitely more so than overlay planes. So for now, this is what I am using. If anyone else comes up with a better solution please let me know!

cm said...

Another method:

To draw big scene
draw scene => back buffer
save back buffer to bitmap
then...
-swapbuffers
-restore back buffer from bitmap

To draw rubberband
Draw to back buffer (XOR optional)
swapbuffer
restore back buffer from bitmap.