OpenGL on OS/2 - The Utility Libraries

From EDM2
Jump to: navigation, search

Written by Perry Newhook

The Utility Libraries

Welcome back to a new installment of OpenGL and OS/2. I hope everyone liked last months' column (well you waited long enough for it). If anyone has managed to come up with interesting bump mappings, send them in and I'll publish them here.

This month, because many of you do not have VisualAge to compile the samples, I'll show you how to create OpenGL programs using the other windowing toolkits.

Visit the Bookstore

Now available in the bookstore are two great titles that are a must have for every serious OpenGL programmer. They are "OpenGL Programming Guide: The Official Guide to Learning OpenGL", and "OpenGL Reference Manual: The Official Reference Manual for OpenGL". The first describes OpenGL and the different effects that you can do with it, while the second book is a reference for all of the OpenGL commands. If you are short on cash, you can get a great deal by getting the 1.0 version of these books. The newer 1.1 versions, however, describe the new OpenGL v1.1 commands, expand into more detail on some of the descriptions and examples, and are a little better organized.

A Quick Review

First, I will go back and show the basic rendering method using VisualAge. This method has been used in all of the previous examples so it should be very familiar. Here is the skeleton of what we were using. I have left out the class definitions and have just shown the relevant functions.

  void GLPaintHandler::initOpenGL()
  {
     int attribList[] = { PGL_RGBA, PGL_RED_SIZE, 2, PGL_GREEN_SIZE, 2,
        PGL_BLUE_SIZE, 2, PGL_ALPHA_SIZE, 1, PGL_DOUBLEBUFFER, 0 };

     // get a handle to anchor block
     HAB hab =  IThread::current().anchorBlock();

     // create a visual config
     PVISUALCONFIG pVisualConfig = pglChooseConfig( hab, attribList );

     // create a direct OpenGL context
     hgc = pglCreateContext( hab, pVisualConfig, NULL, true );

     // and make the context we got the current one
     Boolean val = pglMakeCurrent( hab, hgc, window.handle() );

     // enable depth buffer
     glEnable( GL_DEPTH_TEST );

     glClearColor( 0.0, 0.0, 0.0, 1.0 );
     glDrawBuffer( GL_FRONT_AND_BACK );
     glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT  );
     glDrawBuffer( GL_BACK );

     glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
     glShadeModel( GL_FLAT );

     // free the visual config created above
     delete [] pVisualConfig;
  }

  // openGL settings
  void GLPaintHandler::setWindowSize( ISize size )
  {
     // setup the 3D perspective window
     glMatrixMode( GL_PROJECTION );
     glLoadIdentity();
     gluPerspective( FOVY, (float)window.size().width() /
                    (float)window.size().height(),
                    MINVIEWDISTANCE, MAXVIEWDISTANCE );
     glMatrixMode( GL_MODELVIEW );
     // make a viewport the acual size of the window
     glViewport( 0, 0, size.width(), size.height() );
  }

  Boolean GLPaintHandler::paintWindow( IPaintEvent &event )
  {
     // clear the buffer
     glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

     // no rotations
     glLoadIdentity();

     // draw something here ...

     // swap the back draw buffer to the front
     pglSwapBuffers( IThread::current().anchorBlock(), window.handle() );

     // return true to indicate no additional processing needed
     return( true );
  }

  // constructor
  GLWindow::GLWindow()
  : IFrameWindow( "OpenGL Demonstration 11 - visualAge", 0x1000,
  IFrameWindow::defaultStyle()
           | IFrameWindow::noMoveWithOwner | IWindow::synchPaint ),
     canvas( 0x8008, this, this ),
     paintHandler( canvas ),
     resizeHandler( this )
  {
     // setup
     setClient( &canvas );
     setDestroyOnClose( true );

     // draw on screen
     sizeTo( ISize( 400, 400 ));
     setFocus();
     show();

     // initialize openGL
     paintHandler.initOpenGL();

     // set the size of the 3D window
     paintHandler.setWindowSize( canvas.size() );

     // attach the handlers
     paintHandler.handleEventsFor( &canvas );
     resizeHandler.handleEventsFor( &canvas );
  }

For those not familiar with C++, functions are grouped into structures called classes. Classes are simply a way of organizing functions of similar nature. You can tell what class a function is in by looking at the name preceeding the :: before the function name. All of our OpenGL calls have been grouped together in the GLPaintHandler class. Since those functions consist of 'C' based OpenGL calls, they can be converted from C++ to C by simply removing the class identifier 'GLPaintHandler::'. Functions and data inside classes are grouped into three areas. 'public:' means any other class can access the functions or data freely, 'protected:' means that only inherited classes can access those functions and/or data (I won't get into that), and 'private:' means that only other functions within the same class can access the functions or data. Just think of a class as a structure that can contain functions, and is divisible into different areas for organization and protection.

The GLWindow::GLWindow() function is a special function called a constructor that gets called when the GLWindow class is created. In our constructor we create a window (the IFrameWindow call), do some sizing, call initOpenGL, and attach the handlers to the window. The handlers are asynchronous classes called when something happens to the application: the paint handler is called when the window requires painting, and the resize hander is called when the window gets resized. Kind of logical huh? The resize handler calls a function inside the paint handler called setWindowSize(), and the repaint action on the paint handler calls the function paintWindow(). Inside of paintWindow is where we place the OpenGL draw commands.

void main()
{
   // create the main window
   GLWindow *glWindow = new GLWindow;

   // main messaging processing loop
   IThread::current().processMsgs();
}

The above code is for the main() function that gets called when the application is stated. All it does in our case is create the GLWindow class (which in turn calls the GLWindow constructor called above), and then enters an infinite loop that processes window messages.

As you remember from previous columns, and as you can see from the code above, we use the pgl*() calls to draw onto the window itself. Remember that OpenGL itself cannot actually render to the screen, but requires some sort of windowing interface. In OS/2 it is pgl, and in Windows it is wgl. Three optional toolkits that work with OpenGL have the interface compiled into them so you don't have to worry about it. They are called GLUT (GL UTility), AUX (AUXiliary), and TK (Nano) toolkits. Using these toolkits pretty well guarantees that you can simply recompile your code on any platform you desire without changing the source. The only caveat is that these toolkits are slower than if you coded using pgl or an equivalent.

The GLUT Toolkit

The GLUT toolkit is an optional component that you can download. If you have the sample GLUT applications, you will see that one of the best OpenGL samples, Atlantis, is a GLUT application.

The sequence for starting up a GLUT application is as follows:

  void main()
  {
     glutInitWindowSize( 400, 400 );
     glutInit( &argc, argv );
     glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
     glutCreateWindow( "OpenGL Demonstration 11 - glut" );

     // setup stuff in here

     glutMainLoop();
  }

The sequence shown here is very similar to the VisualAge opening sequence. The following shows the equivalent functions in both GLUT and pgl.

glutInitWindowSize() -> sizeTo()
glutInit(), glutInitDisplayMode() -> functions inside initOpenGL()
glutCreateWindow() -> IFrameWindow() constructor, show()
glutMainLoop() -> IThread::current().processMsgs();

One thing that you may notice above is that we initialize GL (through glutInit() and glutInitDisplayMode() ) before we actually create the window glutCreateWindow(). This seemingly violates our rule stated in previous columns that we have to show the window before we create the GL context (the equivalent of plgCreateContext() is embedded in glutInit() ). The rule actually is that we cannot create the context with a zero size window. Normally a window size request does not take effect until after the window is created. If you try to create the GL context before the window is shown the first time, the context will fail because the window has zero size before the initial showing. In GLUT however, you'll notice that we have an extra call before glutInit() called glutInitWindowSize(). As you might suspect, this simply sets the size that the window is going to be so that the context can be created successfully. The window is actually created later with a glutCreateWindow() call.

The code so far simply sets everything up and displays the window. We still have to create the functions that draw onto the screen and handle window resizing, equivalent to the paintHandler() and resizeHandler() functions in the VisualAge code above. There are a number of functions that accomplish this, and they follow the pattern glut*Func(). The two that match what we are looking for are glutDisplayFunc() and glutReshapeFunc(). The parameter that you pass to these functions is the address of the function that you wish called. For example, if I wanted the function paintWindow() called when glut decided that the window needs repainting, I would use the call glutDisplayFunc( paintWindow );

Similarly the reshape function would be:

glutReshapeFunc( setWindowSize );

Our paintWindow() function becomes:

void APIENTRY paintWindow( void )
{
   // clear the buffer
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

   // no rotations
   glLoadIdentity();

   // draw something here ...

   // swap the back draw buffer to the front
   glutSwapBuffers();
}

And the reshape function is:

void APIENTRY setWindowSize(int width, int height)
{
   // setup the 3D perspective window
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   gluPerspective( FOVY, (float)width / (float)height,
                   MINVIEWDISTANCE, MAXVIEWDISTANCE );
   glMatrixMode( GL_MODELVIEW );
   // make a viewport the acual size of the window
   glViewport( 0, 0, width, height );
}

There are all kinds of callback functions for most anything you want to do. For those unfamiliar with the way callback functions work, the callback function takes as an argument a name of a function. This function will be called when the action corresponding to the callback happens. For most of these callbacks, you can figure out what they do by their name:

glutDisplayFunc()
glutReshapeFunc()
glutKeyboardFunc()
glutMouseFunc()
glutMotionFunc()
glutPassiveMotionFunc()
glutEntryFunc()
glutVisibilityFunc()
glutIdleFunc()
glutTimerFunc()
glutMenuStateFunc()
glutSpecialFunc()
glutSpaceballMotionFunc()
glutSpaceballRotateFunc()
glutSpaceballButtonFunc()
glutButtonBoxFunc()
glutDialsFunc()
glutTabletMotionFunc()
glutTabletButtonFunc()

As you can see, there is an extensive list of functions that do a variety of effects and connect to a number of devices such as mice and spaceballs. There are also a number of pre-built models that you can use:

glutWireSphere()
glutSolidSphere()
glutWireCone()
glutSolidCone()
glutWireCube()
glutSolidCube()
glutWireTorus()
glutSolidTorus()
glutWireDodecahedron()
glutSolidDodecahedron()
glutWireTeapot()
glutSolidTeapot()
glutWireOctahedron()
glutSolidOctahedron()
glutWireTetrahedron()
glutSolidTetrahedron()
glutWireIcosahedron()
glutSolidIcosahedron()

More information on the GLUT toolkit can be obtained from SGI's online reference

The Auxilliary Toolkit

The Auxilliary Toolkit (or AUX) is similar to GLUT in that it allows you to compile OpenGL apps with a portable toolkit instead of with a windowing extension. The AUX library is smaller than the GLUT library and as such has less functions. AUX is really only meant for quick prototyping because of this lack of more advanced functions.

An AUX application would start up as follows:

void main( )
{
    auxInitDisplayMode( AUX_DOUBLE | AUX_RGB | AUX_DEPTH );
    auxInitPosition( 10, 10, 400, 400 );
    auxInitWindow( "OpenGL Demonstration 11 - aux" );

    // setup stuff in here

    auxMainLoop( paintWindow );
}

One difference in the AUX toolkit from the GLUT toolkit is that the display routine is integrated into the main loop routine. The only other function we need to add for our example is the one for the reshape function. The reshape function is (as you can probably guess by now):

auxReshapeFunc( setWindowSize );

The setWindowSize() function itself can remain exactly as it was. The paintWindow() function needs to only change the line that swaps buffers to the window; glutSwapBuffers() gets changed to auxSwapBuffers().

The AUX toolkit has callback functions similar to the GLUT toolkit although there are fewer of them. The AUX callbacks are as follows:

auxMainLoop()
auxExposeFunc()
auxReshapeFunc()
auxIdleFunc()
auxKeyFunc()
auxKeyDownFunc()
auxMouseFunc()
auxMouseDownFunc()
auxMouseUpFunc()
auxMouseMoveFunc()

In addition to the callback functions, there are also a number of prebuilt functions. You may remember that we used some of these models in previous examples.

auxWireSphere()
auxSolidSphere()
auxWireCube()
auxSolidCube()
auxWireBox()
auxSolidBox()
auxWireTorus()
auxSolidTorus()
auxWireCylinder()
auxSolidCylinder()
auxWireIcosahedron()
auxSolidIcosahedron()
auxWireOctahedron()
auxSolidOctahedron()
auxWireTetrahedron()
auxSolidTetrahedron()
auxWireDodecahedron()
auxSolidDodecahedron()
auxWireCone()
auxSolidCone()
auxWireTeapot()
auxSolidTeapot()

The Nano Window Toolkit

Just when you thought you had the naming conventions down, (GLUT functions start with glut*() and AUXilliary functions start with aux*()), along comes the Nano toolkit that starts with tk*(). Although not as well known as the other toolkits, it has all of the capability and functions of the previous two.

Similar to before, the main routine becomes:

void main( )
{
    tkInitDisplayMode( TK_DOUBLE | TK_RGB | TK_DEPTH );
    tkInitPosition( 10, 10, 400, 400 );
    tkInitWindow( "OpenGL Demonstration 11 - tk" );

    // setup stuff in here

    tkExec();
}

For this toolkit, the paint routine is setup with the callback:

tkDisplayFunc( paintWindow );

The actual bufferswap function inside the paint function now becomes tkSwapBuffers();

The resize function is setup with

tkReshapeFunc( setWindowSize );

The callback functions allowable in the Nano toolkit are

tkExposeFunc()
tkReshapeFunc()
tkDisplayFunc()
tkKeyDownFunc()
tkMouseDownFunc()
tkMouseUpFunc()
tkMouseMoveFunc()
tkIdleFunc()

Like the other toolkits, this one also has a number of pre-built objects that you can use.

tkWireSphere()
tkSolidSphere()
tkWireCube()
tkSolidCube()
tkWireBox()
tkSolidBox()
tkWireTorus()
tkSolidTorus()
tkWireCylinder()
tkSolidCylinder()
tkWireCone()
tkSolidCone()

PGL Toolkit

The toolkits mentioned above are great for prototyping or for simple applications, but when it comes to a full application you need to use OS/2's native windowing functions. This means using the pgl functions directly, and using either the VisualAge libraries or the OS/2 toolkit. Since all of the previous examples in the previous months have been with VisualAge, and we have reviewed the VisualAge calls at the beginning of the article, I will now give you a sample OpenGL application skeleton using only the OS/2 toolkit.

In PM, the main window is setup as follows:

  void main( void )
  {
     HAB   hab = NULLHANDLE;
     HMQ   hmq = NULLHANDLE;
     HWND  hwndFrame = NULLHANDLE;
     HWND hwndClient;
     ULONG flCreate = 0UL;
     QMSG  qmsg;
     int retVal = 1;

     do
     {
        /* Initialize PM and create a message queue of default size */
        if( ( hab = WinInitialize( 0UL ) ) == NULLHANDLE )
           break;

        if( ( hmq = WinCreateMsgQueue( hab, 0UL ) ) == NULLHANDLE )
           break;

        /* Register client window class */
        if( !WinRegisterClass( hab,  "PGL_Demo", ClientWindowProc,
                                           CS_SIZEREDRAW, 0L ) )
            break;

        /* Create the windows  */
        flCreate = FCF_STANDARD & ~(FCF_ICON | FCF_MENU | FCF_ACCELTABLE);

        hwndFrame = WinCreateStdWindow( HWND_DESKTOP, WS_VISIBLE,  &flCreate,
                      "PGL_Demo",  "OpenGL Demonstration 11 - pm" ,
                      CS_SIZEREDRAW, 0L, 0x1000, &hwndClient );

        if( hwndFrame == NULLHANDLE )
           break;

        /* window created, initialize opengl */
        initOpenGL();

        /* Message loop */
        while( WinGetMsg( hab, &qmsg, 0L, 0L, 0L ) )
           WinDispatchMsg( hab, &qmsg );

        retVal = 0;
     } while( FALSE );

     /* cleanup */
     if ( hwndFrame != NULLHANDLE )
        WinDestroyWindow( hwndFrame );

     /* Destroy the message queue and release the anchor block */
     if ( hmq != NULLHANDLE )
        WinDestroyMsgQueue( hmq );

     if ( hab != NULLHANDLE )
        WinTerminate( hab );

     return( retVal );
  }

  /* message handling function */
  MRESULT EXPENTRY ClientWindowProc( HWND hwnd, ULONG msg,
                                    MPARAM mp1, MPARAM mp2 )
  {
     HPS   hps = NULLHANDLE;

     switch( msg )
     {
        case WM_PAINT:
           /* paint routine */
           hps = WinBeginPaint( hwnd, NULLHANDLE, NULL );

           /* call our opengl paint function  */
           paintWindow();

           WinEndPaint( hps );
           break;

        case WM_SIZE:
           /* tell new window resize to opengl */
           setWindowSize(  SHORT1FROMMP( mp2 ),  SHORT2FROMMP( mp2 ) );
           break;
      }
      return( NULL );
  }

The paintWindow() and setWindowSize() are identical to functions stated previously, except we are using pglSwapBuffers() to copy the buffer from the back buffer to the screen. The initOpenGL() call that we are calling in the main() routine, is identical to the initOpenGL() function listed in the VisualAge C++ listing.

As you can see, the longest program by far is the PM version with pgl. Most of this complexity is because of the PM initialization code and the messaging function. The toolkits are the smallest which make them great for prototyping; their only drawback being a lack of flexibility, and a decrease in performance. As you can probably tell by now, I prefer using the VisualAge C++ user interface libraries. You get all of the speed of the PM version, with an object-oriented environment. The only real drawback with VisualAge is the large size of the executable.

For completeness, you will find attached some code that displays a sphere in a window. This application is not complex, the purpose being to show the differences and similarities between each of the toolkits. I tried to keep the structure between the five pieces of code as similar as possible to help you see where the equivalent pieces of code are.

Conclusions

That's it for this month. I hope this addresses some of the concerns that I've received on how to use each of the toolkits. There is a lot more to to each of the toolkits that I didn't go into, such as how to run timers, create menus and such, but there is only so much I can describe here in this column. GLUT is well described in the reference given above, and pgl is used extensively in this column.

Next month, I'll be describing one of the great mathematical mysteries of OpenGL - Non-Uniform Rational B-Splines, or NURBS for short. After that, we'll start developing a real application together, one that I think you'll have a lot of fun with.