OpenGL Performer Applications

Programmers familiar with the use of scene graphs may prefer to use that data structure rather than writing OpenGL manually. While VR Juggler does not provide a scene graph of its own, its design allows the use of existing scene graph software. In VR Juggler 1.1 and beyond, the supported scene graphs are OpenGL Performer from SGI, OpenSG, and Open Scene Graph. This section explains how to use OpenGL Performer to write VR Juggler applications.

A Performer-based VR Juggler application must derive from vrj::PfApp. Similar to vrj::GlApp presented in the previous section, vrj::PfApp derives from vrj::App. vrj::PfApp extends vrj::App by adding methods that deal with scene graph initialization and access. Figure 5.3, “vrj::PfApp application class” shows how vrj::PfApp fits into the class hierarchy of a Performer-based VR Juggler application.

Figure 5.3. vrj::PfApp application class

vrj::PfApp application class

Two of the methods added to the application interface by vrj::PfApp are initScene() and getScene(). These are called by the Performer Draw Manager to initialize the application scene graph and to get the root of the scene graph respectively. They must be implemented by the application (they are pure virtual methods within vrj::PfApp). Additional methods will be discussed in this section, but in many cases the default implementations of these other methods may be used. A simple tutorial application will be provided to illustrate the concepts presented.

Scene Graph Initialization: vrj::PfApp::initScene()

In an application using OpenGL Performer, the scene graph must be initialized before it can be used. The method vrj::PfApp::initScene() is provided for that purpose. Within this method, the root of the application scene graph should be created, and any required models should be loaded and attached to the root in some way. The exact mechanisms for accomplishing this will vary depending on what the application will do.

During the initialization of OpenGL Performer by VR Juggler, vrj::PfApp::initScene() is invoked after the Performer functions pfInit() and pfConfig() but before vrj::App::apiInit().

Scene Graph Access: vrj::PfApp::getScene()

In order for Performer to render the application scene graph, it must get access to the scene graph root. The method vrj::PfApp::getScene() will be called by the Performer Draw Manager so that it can give the scene graph root node to Performer. Since the job of getScene() is straightforward, its implementation can be very simple. A typical implementation will have a single statement that returns a member variable that holds a pointer to the application scene graph root node.

Note

Make sure that the node returned is not a pfScene object. If it is, then lighting will not work.

Possible Misuses

Do not load any models in this member function. This sort of operation should be done within initScene().

Tutorial: Loading a Model with OpenGL Performer

In this section, we present a tutorial that demonstrates model loading with OpenGL Performer. The tutorial overview is as follows:

  • Description: Simple OpenGL Performer application that loads a model.

  • Objective: Understand how to load a model, add it to a scene graph, and return the root to VR Juggler.

  • Member functions: vrj::PfApp::initScene(), vrj::PfApp::getScene()

  • Directory: $VJ_BASE_DIR/share/samples/Pf/simple/simplePf

  • Files: simplePfApp.h, simplePfApp.cpp

Class Declaration

The following application class is called simplePfApp. It is derived from vrj::PfApp and has custom initScene() and getScene() methods declared. Note that this application uses preForkInit() which will be discussed later. Refer to simplePfApp.h for the implementations of preForkInit() and setModel().

  1 class simplePfApp : public vrj::PfApp
    {
    public:
       simplePfApp();
  5    virtual ~simplePfApp();
    
       virtual void preForkInit();
       virtual void initScene();
       virtual pfGroup* getScene();
 10    void setModel(std::string modelFile);
    
    public:
       std::string    mModelFileName;
    
 15    pfGroup*       mLightGroup;
       pfLightSource* mSun;
       pfGroup*       mRootNode;
       pfNode*        mModelRoot;
    };

The initScene() Member Function

The implementation of initScene() is in simplePfApp.cpp. Within this method, we create the scene graph root node, the lighting node, and load a user-specified model. The implementation follows:

  1 void simplePfApp::initScene ()
    {
       // Allocate all the nodes needed
       mRootNode = new pfGroup;                                (1)
  5 
       // Create the SUN light source
       mLightGroup = new pfGroup;                              (2)
       mSun = new pfLightSource;
       mLightGroup->addChild(mSun);
 10    mSun->setPos(0.3f, 0.0f, 0.3f, 0.0f);
       mSun->setColor(PFLT_DIFFUSE, 1.0f, 1.0f, 1.0f);
       mSun->setColor(PFLT_AMBIENT, 0.3f, 0.3f, 0.3f);
       mSun->setColor(PFLT_SPECULAR, 1.0f, 1.0f, 1.0f);
       mSun->on();
 15 
       // --- LOAD THE MODEL -- //
       mModelRoot = pfdLoadFile(mModelFileName.c_str());       (3)
    
       // -- CONSTRUCT STATIC STRUCTURE OF SCENE GRAPH -- //
 20    mRootNode->addChild(mModelRoot);                        (4)
       mRootNode->addChild(mLightGroup);                       (4)
    }
1

First, the root node is constructed as a pfGroup object.

2

Next, some steps are taken to create a light source for the application.

3

Finally, the model is loaded using pfdLoadFile(), and the model scene graph root node is stored in mModelRoot. (The model loader must be initialized prior to calling pfdLoadFile(). This is done in preForkInit().)

4

Finally, the model and the light source nodes are added as children of the root.

The getScene() Member Function

The Performer Draw Manager will call the application's getScene() method to get the root of the scene graph. The implementation of this method can be found in simplePfApp.h. The code is as follows:

pfGroup* simplePfApp::getScene ()
{
   return mRootNode;
}

The simplicity of this method implementation is not limited to the simple tutorial from which it is taken. All Performer-based VR Juggler applications can take advantage of this idiom where the root node is a member variable returned in getScene().

Other vrj::PfApp Methods

Besides the two methods discussed so far, there are several other methods in vrj::PfApp that extend the basic vrj::App interface. Each is discussed in this section.

preForkInit()

Prototype: public void preForkInit();

This member function allows the user application to do any processing that needs to happen before Performer forks its processes but after pfInit() is called. In other words, it is invoked after pfInit() but before pfConfig().

appChanFunc()

Prototype: public void appChanFunc(pfChannel* chan);

This method is called every frame in the application process for each active channel. It is called immediately before rendering (pfFrame()).

configPWin()

Prototype: public void configPWin(pfPipeWindow* pWin);

This method is used to initialize a pipe window. It is called as soon as the pipe window is opened.

getFrameBufferAttrs()

Prototype: public std::vector<int> getFrameBufferAttrs();

This method returns the needed parameters for the Performer frame buffer. Stereo, double buffering, depth buffering, and RGBA are all requested by default.

drawChan()

Prototype: public void drawChan(pfChannel* chan,
                     void* chandata);

This is the method called in the channel draw function to do the actual rendering. For most programs, the default behavior of this function is correct. It makes the following calls:

chan->clear();
pfDraw();

Advanced users may want to override this behavior for complex rendering effects such as overlays or multi-pass rendering. (See the OpenGL Performer manual pages about overriding the draw traversal function.) This function is the draw traversal function but with the projections set correctly for the given displays and eye. Prior to the invocation of this method, chan is ready to draw.

preDrawChan()

Prototype: public void preDrawChan(pfChannel* chan,
                        void* chandata);

This is the function called by the default drawChan() member function before clearing the channel and drawing the next frame (pfFrame()).

postDrawChan()

Prototype: public void postDrawChan(pfChannel* chan,
                         void* chandata);

This is the function called by the default drawChan() member function after clearing the channel and drawing the next frame (pfFrame()).

pfExit(): To Call or Not to Call

The Performer function pfExit() poses a problem for VR Juggler applications, and some background information will help ensure that readers understand the consequences of using pfExit() (or not). The main issue with pfExit() as it relates to VR Juggler is that calling pfExit() has the side effect of calling the system function exit(), which means that it should be (or has to be) the very last function call of a program. Prior to VR Juggler 2.0.1, the VR Juggler Performer Draw Manager was written to call pfExit() from within the method vrj::PfDrawManager::closeAPI(). Before VR Juggler 2.0 Beta 3, however, this method of the vrj::PfDrawManager interface had never been called—the result of the kernel shutdown process being incomplete. With the more complete kernel shutdown process in VR Juggler 2.0 Beta 3 and 2.0.0, authors of Performer-based VR Juggler applications saw their applications exiting prematurely after invoking the kernel shutdown. More specifically, any code that was intended to be executed after vrj::PfDrawManager::closeAPI() would not be executed. Such code includes vrj::App::exit() or an override thereof; an application object destructor; and anything else to be done after vrj::Kernel::waitForKernelStop() returned.

To remedy this problem, vrj::PfDrawManager::closeAPI() in VR Juggler 2.0.1 and newer does not call pfExit(). Rather, it is the responsibility of the application programmer to call pfExit() if s/he so desires. Failing to call pfExit() could result in resource leaks from Performer, but calling pfExit() has been known to cause application crashes (irrespective of whether VR Juggler is used). In general, users should call pfExit() at the end of their main() function, but if doing so causes the application to crash on exit, then not calling pfExit() is probably the better option.

The important thing to remember is that the application object destructor needs to be called before pfExit() is called. Refer to Example 5.2, “Using pfExit() with a Heap-Allocated Application Object” for an example of how pfExit() would be used with a VR Juggler application object allocated on the heap. For a stack-allocated application object, see Example 5.3, “Using pfExit() with a Stack-Allocated Application Object”.

Example 5.2. Using pfExit() with a Heap-Allocated Application Object

  1 int main(int argc, char* argv[])
    {
       vrj::Kernel* kernel = vrj::Kernel::instance();
    
  5    // Allocate the application object on the heap.  Its
       // destructor will be called manually before calling
       // pfExit().
       simplePfApp* application = new simplePfApp();
    
 10    // Load config files.
       for ( int i = 2; i < argc; ++i )
       {
          kernel->loadConfigFile(argv[i]);
       }
 15 
       kernel->start();
    
       // Configure the application.
       application->setModel(argv[1]);
 20    kernel->setApplication(application);
    
       // Wait for the kernel to shut down.
       kernel->waitForKernelStop();
    
 25    // Final application clean-up.
       delete application;
    
       // Clean up Performer.
       // Calls system exit() function and therefore never returns.
 30    pfExit();
    
       return 0;
    }

Example 5.3. Using pfExit() with a Stack-Allocated Application Object

  1 int main(int argc, char* argv[])
    {
       // Nested scope for stack-allocated data.
       {
  5       vrj::Kernel* kernel = vrj::Kernel::instance();
    
          // Load config files.
          for ( int i = 2; i < argc; ++i )
          {
 10          kernel->loadConfigFile(argv[i]);
          }
    
          kernel->start();
    
 15       // Allocate the application object on the stack.  Its
          // destructor will be called automatically at the end
          // of this nested scope.
          simplePfApp application;
    
 20       // Configure the application and give it to the kernel.
          application.setModel(argv[1]);
          kernel->setApplication(&application);
    
          // Wait for the kernel to shut down.
 25       kernel->waitForKernelStop();
       }
    
       // Clean up Performer.
       // Calls system exit() function and therefore never returns.
 30    pfExit();
    
       return 0;
    }