Native Win32 API OpenGL Tutorial - Part 2





5.00/5 (2 votes)
In this second part, we will create an OpenGL MDI application
The source and files are in Visual Studio 2013 format.
Introduction
In this second article, we will expand the simple OpenGL application from Part 1 into a standard MDI interface application. Here, each MDICHILD
window will contain the fuctionality of the single application from part 1.
Background
In part one, we had a single render context which belonged to the only application window. What we will now do is move much of the functionality away from the application window and onto the MDICHILD
window. So we will have multiple render contexts operating at once and the organization we undertook in lesson 1 becomes the key. In later lessons, we will add threads and the drawing complexity will go up another level.
The key part to understand this lesson is understanding the MDI Application and behaviour. MDI Applications are based around an invisible or transparent Window
class that exists in the area that is drawn in a normal application window. This special Window
class is called an MDIClient
and although invisible, it is involved in controlling the behaviours of the MDIChildren
windows that are inserted into it.
The MDIClient
is responsible for all those MDI special things like the minimize and maximize behaviours, tiling and cascading and a wealth of other unique features. They are worth reading some articles on if you want to use this sort of application. For our MDI application, we will make our OpenGL windows and insert them into the MDIClient
and allow the standard behaviours to work.
Using the Code
Our pseudo code from lesson 1 remains unchanged except it is moved from the application window and onto the MDIChild
window and we add a cleanup step when closing. Thus, each MDIChild
runs the sequence:
1.) Initialize OpenGL for window (Called Once)
2.) Scale the OpenGL viewPort (Initial Call)
repeat
3.) Draw the scene
4.) Transfer the scene to screen
until window closes
5.) Window closing cleanup OpenGL memory and stuff used
** Note Item 2) the scale process also gets called if the window changes size
We use the same structure to hold our data as per Lesson 1, only this time each MDIChild
creates a structure and it is attached to each MDI child. The initialization of step 1 returns a render context specific to the MDIChild
and thus each MDIChild
has its own render context.
The MDIChild
handler becomes the site of all the OpenGL calls. The create of the MDI child will cause creation of a new render context which will be stored in its own database structure on the window itself. Each MDIChild
will therefore have its own data structure and messages which create operations on the windows becomes unique to the data structure attached to each MDIChild
. So each MDI child can be doing different things without having to track any complexity for the OpenGL system itself.
What seemed like a slightly complex data holding arrangement in lesson 1 makes data management obvious and simple in our MDI. There are other faster ways to attach data to Windows but they are more complex than SetProp
/GetProp
but this is the easiest for our target audience of beginners. Later, as we move into games and faster render situations, we will discuss other methods to deal with this.
For this application, the MDIChild
shown below is where all the OpenGL is called and controlled from and it is worth looking at the process and equating it back up to the pseudocode above.
static LRESULT CALLBACK OpenGLMDIChildHandler (HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg){
case WM_CREATE: { // WM_CREATE MESSAGE
GLDATABASE* db = (GLDATABASE*) malloc(sizeof(GLDATABASE)); // Allocate structure
db->Rc = InitGL(Wnd); // Initialize OpenGL & get render context
db->glTexture = 0; // Zero the texture
db->xrot = 0.0f; // Zero x rotation
db->yrot = 0.0f; // Zero y rotation
SetProp (Wnd, DATABASE_PROPERTY, (HANDLE) db); // Data structure hold as property
ReSizeGLScene (Wnd); // Rescale the OpenGL window
}
break;
case WM_DESTROY: { // WM_DESTROY MESSAGE
wglMakeCurrent(NULL, NULL); // Make the rendering context not current
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);// Get data struct
if (db != 0) {
if (db->Rc != 0) wglDeleteContext(db->Rc); // If valid delete context
if (db->glTexture != 0)
glDeleteTextures(1, &db->glTexture); // If valid delete the texture
free(db); // Release the data structure memory
}
}
break;
case WM_PAINT: { // WM_PAINT MESSAGE
PAINTSTRUCT Ps;
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);// Get data struct
BeginPaint (Wnd, &Ps); // Begin paint
DrawGLScene(db, Ps.hdc); // Draw the OpenGL scene
SwapBuffers(Ps.hdc); // Swap buffers
EndPaint(Wnd, &Ps); // End paint
return 0;
}
break;
case WM_TIMER: { // WM_TIMER MESSAGE
GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY);// Get data struct
db->xrot += 1.0f; // Inc x rotation
db->yrot += 1.0f; // Inc y rotation
InvalidateRect(Wnd, 0, TRUE); // Redraw now so invalidate us
}
break;
case WM_WINDOWPOSCHANGED: // WM_WINDOWPOSCHANGED
// Check if window size has changed .. window move doesnt change aspect ratio
if ((lParam == 0) || ((((PWINDOWPOS) lParam)->flags & SWP_NOSIZE) == 0)){
ReSizeGLScene(Wnd); // Rescale the GL window
InvalidateRect(Wnd, 0, TRUE); // We need a redraw now so invalidate us
}
break;
case WM_ERASEBKGND: // WM_ERASEBKGND MESSAGE
return (FALSE);
}
return DefMDIChildProc(Wnd, Msg, wParam, lParam); // Unprocessed messages to DefMDIChildProc
}
History
- Version 1.00 - Initial release
- Version 1.10 - Small bug changing wrong textures on MDIChild fixed, MDI DragDrop functionality added.