Full-Screen Rendering
Windowed OpenGL apps are great, but it’s hard to
create an immersive game if your app isn’t in full-screen! One of the
most common developer questions is “How do I do full-screen rendering
with OpenGL?” The truth is, you already
know how to do full-screen rendering with OpenGL—it’s just like
rendering into any other window! The real question is “How do I create a
window that takes up the entire screen and has no borders?”
Even
though this issue isn’t strictly related to OpenGL, it is of enough
interest to a wide number of our readers that we give this topic some
coverage here. Creating a full-screen window is almost as simple as
creating a regular window the size of the screen and starting at (0,0).
We also use a different window style because we have no need for a title
bar or border because none of that is visible. The code in Listing 13.3 does just that.
Listing 1. Setting Up a Full-Screen Window
if(bUseFS)
{
// Prepare for a mode set to the requested resolution
DEVMODE dm;
memset(&dm,0,sizeof(dm));
dm.dmSize=sizeof(dm);
dm.dmPelsWidth = nWidth;
dm.dmPelsHeight = nHeight;
dm.dmBitsPerPel = 32;
dm.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
long error = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (error != DISP_CHANGE_SUCCESSFUL)
{
// Oops, something went wrong, let the user know.
if (MessageBox(NULL, "Could not set fullscreen mode.\n"
"Your video card may not support the requested mode.\n"
"Use windowed mode instead?", g_szAppName,
MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
g_InFullScreen = false;
dwExtStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwWindStyle = WS_OVERLAPPEDWINDOW;
}
else
{
MessageBox(NULL, "Program will exit.",
"ERROR", MB_OK|MB_ICONSTOP);
return false;
}
}
else
{
// Mode set passed, setup the styles for fullscreen
g_InFullScreen = true;
dwExtStyle = WS_EX_APPWINDOW;
dwWindStyle = WS_POPUP;
ShowCursor(FALSE);
}
}
AdjustWindowRectEx(&g_windowRect, dwWindStyle, FALSE, dwExtStyle);
// Create the window again
. . .
2. Double Buffering
The sphere_world_redux sample program requests a double buffered pixel format by specifying WGL_DOUBLE_BUFFER_ARB the in the list of attributes when searching for a pixel format using wglChoosePixelFormatARB.
By this time you have seen many sample programs that are double
buffered. But let’s revisit briefly given that this is relevant to how
we allocate the pixel format and how the program is controlled. When a
double buffered pixel format is used, two surfaces the size of the
window are allocated. One acts as the front buffer and the other as the
back buffer.
Why would you want to do
that? Double buffering allows OpenGL to draw your entire scene to the
back buffer without any intermediate results showing up on the screen.
This can provide a smoother and more visually pleasing experience for
your users.
But how do users see anything if you are always
rendering to a buffer that is not visible? Easy, just tell OpenGL when
you are done drawing and the buffers need to be swapped. This is done
simply by calling SwapBuffers with the device handle of the
window. Once this call is made, the back buffer will be displayed, and
our program will have a new back buffer to work with.
// Do the buffer swap
SwapBuffers(g_hDC);
Eliminating Visual Tearing
If your application is able to draw quickly and call SwapBuffers at a faster rate than the refresh rate of the monitor, an ugly effect called tearing can occur. If your application calls SawBuffers
before the previous frame is finished being scanned out, someone using
your application will see part of one frame and part of the next.
The widely supported extension WGL_EXT_swap_control
comes to the rescue! You can tell OpenGL how many video frames, or
V-Syncs, are allowed to happen at minimum between swap calls. Just use
the following function to set the interval:
Bool wglSwapIntervalEXT(GLint interval);
If you pass in 0 for interval, the calls to SwapBuffers are unrestricted just as they are without this extension. But if you pass 1 for interval, only one SwapBuffers
call is allowed to return for every vertical refresh of the monitor
(every video frame). This is exactly what you want to eliminate tearing!
All of the additional CPU time can be used for other things while your
app waits for the swap to complete.
You can also pass larger intervals to wglSwapIntervalEXT
to wait more frames between swaps, but this can cause considerable
stutter in your applications. Also many drivers may not support larger
intervals than one and will quietly clamp the interval back to 1.