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.
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.
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().
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.
Make sure that the node returned is not
a pfScene object. If it is, then lighting
will not work.
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
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 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;
5
// Create the SUN light source
mLightGroup = new pfGroup;
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());
// -- CONSTRUCT STATIC STRUCTURE OF SCENE GRAPH -- //
20 mRootNode->addChild(mModelRoot);
mRootNode->addChild(mLightGroup);
}![]() | First, the root node is constructed as a
|
![]() | Next, some steps are taken to create a light source for the application. |
![]() | Finally, the model is loaded using
|
![]() | Finally, the model and the light source nodes are added as children of the root. |
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().
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.
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().
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()).
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.
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.
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.
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;
}