This is an interesting question I've been pondering over (and experimenting with) in the course of my resolution changing code. Received wisdom appears to be that when changing resolution you should take the following steps (simplified list):
- Delete all OpenGL objects.
- Destroy your current OpenGL context and make the current context NULL.
- Destroy your window.
- Recreate your window.
- Set a new pixel format.
- Create a new context and make it current.
- Reload all of your OpenGL objects (textures/display lists/etc).
It turns out that in reality this is not actually the case! Unfortunately, the documentation is of absolutely no help whatsoever in telling you this, all the information you'll get from MSDN will reveal is that ChoosePixelFormat and SetPixelFormat refer to the pixel format of a device context, but what it fails to give you is any practical real-world-use information (most MSDN documentation is like this, but that's another story...) OpenGL tutorials or documentation are just as bad, as they generally only deal with first-time startup.
If you're the cautious type you'll likely do the above anyway, and be happy to leave it at that. But just think: you could be creating a load of unnecessary work for yourself. There are plenty of GLUT examples where the window size changes but a Pixel Format switch is not required, and how is that really any different from a change of resolution? Likewise with when the scr_viewsize cvar changes.
So I decided to throw out caution and just change the window size (and resolution if required), without bothering to destroy and recreate anything. And guess what - it worked. Perfectly. First time. Everything survived a ChangeDisplaySettings call with no issues or corruptions. I've tested the following mode changes and everything just works perfectly fine:
- Change from one Windowed mode to another.
- Change from Fullscreen to Windowed.
- Change from Windowed to Fullscreen.
- Change from one Fullscreen mode to another.
- Change from 16 bit to 32 bit.
- Change from 32 bit to 16 bit.
Until I see some conclusive evidence that destroy/recreate is an absolute requirement
because of 1... 2... 3... (in other words I want to see concrete definite reasons) I'm going to leave things be. I want a definite yes or no answer on this, not some vague description of what a function does. I have a feeling that this "accepted way of doing things" attitude may have been relevant in the days of 4 MB 3DFX cards, where you had limited memory and a Pixel Format switch may have been required as a higher resolution mode may have needed less bits in the depth or color buffers, but in 2008 when even an Intel Integrated card from over 2 years ago can easily support 1280 x 768 fullscreen with 32 bit colour and 32 bit depth, this is just silly.
So the answer to my initial question seems to be: SetPixelFormat is only required when you need to change the bit-depth of the color or depth buffers. Never any other time.
For the record, here's the procedure I use for switching resolution.
- Determine if ChangeDisplaySettings is required by comparing the new mode with the current mode, and call it if required.
- Call SetWindowLong to change the window style (title bar & borders vs. none).
- Call AdjustWindowRectEx to set the client area to the desired window size (adjusting for title bar & borders, or lack thereof).
- Call MoveWindow to position the window to the origin (not required, but hey!)
- Call CenterWindow to center the window (required as the size will have changed).
- Call InvalidateRect (NULL, NULL, TRUE); to force an immediate repaint of the desktop (required to refresh desktop areas that the window may have previously covered).
- Call IN_ActivateMouse and IN_HideMouse to bring the mouse settings into sync with the new mode (if it's a windowed mode, the next GL_EndRendering will finish fixing things up).
- Store the new mode settings back to the current mode settings, and do some other bookkeeping stuff.
Postscript: one other thing strikes me here. If we're running in a full-screen desktop-resolution mode, there doesn't seem to be much to stop us from using the desktop as our OpenGL window (by passing a HWND parameter of NULL to the relevant functions). I'd like to experiment with this sometime, but probably outside of the context of MHQuake as it would complicate things a bit too much. I've also got a feeling that regular Windows desktop repaints might mess things up a bit.
Post-postscript: If one was feeling a bit cautious but still wanted to try this method out, once could do a ChoosePixelFormat on the desktop DC (using GetDC (NULL);), then do SetPixelFormat on the window DC. This is based on the assumption that whatever pixel format is used for the desktop will also be valid for any window we care to create. I'll probably end up going down this route myself.