Programming with the DIVERSE Graphics Interface to OpenGL(tm)

The following tutorial is derived from Chapter 2 of the dgiGL programming guide, which may be found under the "Documentation" link from this web site.

1. Application Structure

1.1 Sample Application

Building an application with DgiGL is very similar to building an application with other OpenGL interfaces such as GLUT. The following simple application demonstrates the basic programming interface to dgiGL.

cube_rotate.C #include <stdlib.h> #include <stdio.h> #include <math.h> #include "dgiGL.H" int theta; void cb_init(dgiGLDisplayInfo *d) { glClearColor(0.0, 0.0, 0.0, 0.0); glEnable(GL_DEPTH_TEST); } void cb_perFrame(dgiGLDisplayInfo *d) { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(0.0, 1.75, -0.25); glRotatef(theta, 0.0, 0.0, 1.0); glColor3f(1.0, 0.0, 0.0); glBegin(GL_QUADS); glColor3f(0, 0, 0); glVertex3f(-0.25, -0.25, -0.25); glColor3f(1, 0, 0); glVertex3f(0.25, -0.25, -0.25); glColor3f(1, 1, 0); glVertex3f(0.25, 0.25, -0.25); glColor3f(0, 1, 0); glVertex3f(-0.25, 0.25, -0.25); glColor3f(0, 1, 0); glVertex3f(-0.25, 0.25, -0.25); glColor3f(1, 1, 0); glVertex3f(0.25, 0.25, -0.25); glColor3f(1, 1, 1); glVertex3f(0.25, 0.25, 0.25); glColor3f(0, 1, 1); glVertex3f(-0.25, 0.25, 0.25); glColor3f(0, 1, 1); glVertex3f(-0.25, 0.25, 0.25); glColor3f(0, 0, 1); glVertex3f(-0.25, -0.25, 0.25); glColor3f(1, 0, 1); glVertex3f(0.25, -0.25, 0.25); glColor3f(1, 1, 1); glVertex3f(0.25, 0.25, 0.25); glColor3f(0, 0, 0); glVertex3f(-0.25, -0.25, -0.25); glColor3f(1, 0, 0); glVertex3f(0.25, -0.25, -0.25); glColor3f(1, 0, 1); glVertex3f(0.25, -0.25, 0.25); glColor3f(0, 0, 1); glVertex3f(-0.25, -0.25, 0.25); glColor3f(1, 0, 0); glVertex3f(0.25, -0.25, -0.25); glColor3f(1, 0, 1); glVertex3f(0.25, -0.25, 0.25); glColor3f(1, 1, 1); glVertex3f(0.25, 0.25, 0.25); glColor3f(1, 1, 0); glVertex3f(0.25, 0.25, -0.25); glColor3f(0, 0, 0); glVertex3f(-0.25, -0.25, -0.25); glColor3f(0, 0, 1); glVertex3f(-0.25, -0.25, 0.25); glColor3f(0, 1, 1); glVertex3f(-0.25, 0.25, 0.25); glColor3f(0, 1, 0); glVertex3f(-0.25, 0.25, -0.25); glEnd(); glPopMatrix(); } int main(int argc, char **argv) { dgiGL dgiObj; dgiObj.display()->setInitFunc(cb_init); dgiObj.display()->setDisplayFunc(cb_perFrame); dgiObj.config(); dgiCoord c;[0] = 0.0f;[1] = 0.0;[2] = 0.3f; c.hpr[0] = 0.0f; c.hpr[1] = 0.0f; c.hpr[2] = 0.0f; while(1) { dgiObj.sync(); if (theta < 180.0) c.hpr[0] = -21 + 0.25 * theta; else c.hpr[0] = 21 - 0.25 * (theta-180); dgiObj.display()->updateFrusta(c); dgiObj.frame(); theta += (0.33333f) * (0.01667f) * (360.0f); if (theta > 360.0f) theta -= 360.0f; } return EXIT_SUCCESS; }

1.2 Understanding the Sample Application

1.2.1 The dgiGL Object

All drawing in a DIVERSE OpenGL application requires a dgiGL object. The dgiGL object provides two important interfaces for the application programmer. First it acts as a manager for all rendering threads. Some graphics platforms, such as the SGI Onyx series of computers support multiple graphics pipelines, which dgiGL applications take advantage of by creating a separate rendering thread for each graphics pipeline. Three methods are required to control these rendering threads -- dgiGL::config(), dgiGL::sync(), and dgiGL::frame(). The config() method is used to create the rendering threads, the sync() waits until all rendering threads are ready to draw a frame, and the frame() method tells each rendering thread to begin drawing. Because virtual environment and visualization applications often require significant graphics horsepower, the frame() method returns immediately while the rendering threads may take some time to draw a scene.

Figure 1 -- Multiple Rendering Threads

The other important purpose that the dgiGL object serves is as the root of a tree which describes the graphics software and hardware setup for any application. This tree begins with the dgiGLDisplay object, which can be accessed by the dgiGL::display() function. The tree is populated by the both the graphics DSO (see section 2), which describes the graphics hardware, and the application itself, which sets up the initialization and drawing routines.

1.2.2 Callbacks

The sample application sets up two callbacks: cb_init(), the initialization callback, and cb_display() the display callback. The initialization callback is called once per application window that is created, and is used to initialize the OpenGL state. Due to the use of graphics DSOs (see section 2), the number of windows and viewports may not always be constant. The dgiGLDisplayInfo struct which is passed to the initialization function indicates which window is being initialized.

The display callback is called once per frame for every viewport that is defined by the graphics DSOs that are loaded. Again, see section 2 for more information about DSOs. In this callback should be all of a programs graphics code. In order to properly operate, the code in this function must be threadsafe. Code that is not threadsafe may be protected with a pthread_mutex_t, which will slow down rendering performance.

No buffer swapping is performed at the end of the display function because all buffer swapping is handled by the dgiGL library.

1.2.3 Coordinate Systems

DIVERSE defines the following coordinate system: +x is to the right, +y is toward the front, and +z is toward the ceiling. All drawing takes place inside the cube bounded by (1, 1, 1) and (-1, -1, -1). Your code may use the WorldMatrix code inside of the dgiGLDisplay object to transform this cube, or you may set up transformations of your own in the display callback.

1.2.4 Output

The output of this application depends on what kind of graphics hardware it is run on. The following two diagrams give sample output using a single window, single viewport graphics DSO, and a single window, multiple viewport DSO.

Figure 2: Sample Application Output, DSO

Figure 3: Sample Application Output, multiple viewport DSO

2. DSOs

Because of the varied nature of the hardware used to create virtual environments, a DSO-based "plugin" system is used to handle all graphics hardware interaction in dgiGL programs. You probably noticed that in the above sample program, no projection matrix is ever defined. This is because that work is done in the graphics DSO and the dgiGL library. In this way, the application may be run on a desktop with a simple perspective projection, or in the VT-CAVE(tm) with full immersion with no recompiling of the code.

DSOs are simple to write; just use the dgiGL API to define the graphics hardware and desired output format. The following code is a working graphics DSO which defines one window containing six viewports. The viewports contain the scene as it would look in all six directions from the head

testDSO.C -- A sample graphics DSO #include <dtk.h> #include "dgiGL.H" #define HOSTNAME ":0.0" class testDSO : public dgiGLAugment { public: testDSO(dgiGL *d); int preConfig(void); private: dgiGLDisplay *dsp; }; testDSO::testDSO(dgiGL *d) : dgiGLAugment("A Test DSO.") { validate(); } static float screens[6][3] = { {0.0, 90.0, 0.0}, {180.0, 0.0, 0.0}, {90.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {-90.0, 0.0, 0.0}, {0.0, -90.0, 0.0} }; int testDSO::preConfig(void) { dgiGLWindow *wp; int v; dsp->pipes(1); dsp->pipe(0)->SetX11DisplayName(HOSTNAME); dsp->pipe(0)->windows(1); wp = dsp->pipe(0)->window(0); wp->SetX11Geometry(50*p, 50*p, 400, 300); wp->viewports(6); wp->viewport(0)->SetViewportGeometry(0.50, 0.66, 0.25, 0.33); wp->viewport(1)->SetViewportGeometry(0.00, 0.33, 0.25, 0.33); wp->viewport(2)->SetViewportGeometry(0.25, 0.33, 0.25, 0.33); wp->viewport(3)->SetViewportGeometry(0.50, 0.33, 0.25, 0.33); wp->viewport(4)->SetViewportGeometry(0.75, 0.33, 0.25, 0.33); wp->viewport(5)->SetViewportGeometry(0.50, 0.00, 0.25, 0.33); } wp = dsp->pipe(0)->window(0); wp->SetX11WindowName("dgiGL Symmetric Test Window"); for (v=0; v<6; v++) { wp->viewport(v)->SetFOV(120.0); wp->viewport(v)->SetSymmetric(); dgiVec3 foo; foo[0] = screens[v][0]; foo[1] = screens[v][1]; foo[2] = screens[v][2]; wp->viewport(v)->SetHPR(foo); } return DGIGL_REMOVE_OBJECT; } extern "C" void *dtkDSO_loader(void *arg) { return new testDSO((dgiGL*) arg); }

This DSO defines one pipeline which contains one window, which contains six viewports. The viewports are laid out within the window with the dgiGLViewport::SetViewportGeometry() method, where (0, 0) represents the lower left corner of the window, and (1, 1) represents the upper right corner of the window.

3. Compiling the Sample Application

3.1 Required software

Before you can compile a dgiGL program, you must install the software on which dgiGL depends. You will need OpenGL and DTK 1.0.2. If you are working on an SGI platform, you already have OpenGL. If you are working on a linux platform, the the Mesa3D package should work fine. DTK 1.0.2 can be downloaded from this link: dtk-1.0.2.tar.gz.

3.2 Building dgiGL

Because dgiGL is new and under development, it may seem difficult to build. Hopefully the following instructions will help:

3.3 Building a dgiGL Application

Sample applications need to link against -ldgiGL and -ldtk in order to properly build using dgiGL. Examples of how to compile and link sample applications can be found in dgiGL/library/Makefile.

CC -n32 -g -c -o cube_rotate.o cube_rotate.C -I/usr/local/dtk/include \ -I/usr/local/dgiGL/include CC -n32 -g -o cube_rotate cube_rotate.o -L/usr/local/dtk/lib32 \ -L/usr/local/dgiGL/lib -lGL -ldgiGL -ldtk -lm

will compile the above sample application on an n32 Irix system with dtk installed in /usr/local/dtk and dgiGL installed in /usr/local/dgiGL