Wes Bethel R3vis Corporation 4 September 2000
OpenRM Scene Graph is an implementation of a scene graph API that provides services such as retained mode storage and rendering, which allows graphics developers to create highly portable and efficient interactive graphics applications. CAVELib is an API that provides the framework for event management and application multiprocessing, and is targeted at Virtual Reality (VR) applications. This white paper describes technical issues pertaining to the use of these cooperative technologies in a single application, and is intended for use by software developers.
1. Introduction 1.1 What is OpenRM? 1.2 What is CAVELib? 1.3 High level view of how these technologies fit together. 2. Technical Details 2.1 High Level Architecture of an CAVElib+OpenRM Application. 2.2 Summary of Graphics Issues. 2.2.1 OpenGL Context and Stack Management 2.2.2 Stereo and Multipass Rendering. 2.2.3 Developer's Checklist 3. Examples 3.1 Sample Graphics Initialization Function 3.2 Sample Draw Function 4. Future Work 4.1 Multiprocessing 5. Summary
OpenRM Scene Graph is a software library that implements a scene graph retained storage model, and which provides rendering, visualization and event management services, and which is extensible both at the source code and runtime levels. OpenRM is distributed under the GNU Lesser General Public License (LGPL), and is a derivative of RM Scene Graph (tm), a commercial product from R3vis Corporation (www.r3vis.com). OpenRM and the scene graph model it implements can be thought of as a mediating layer between applications and OpenGL (www.opengl.org), where OpenRM adds value to the graphics API.
Simply put, a scene graph is just a convenient way for graphics applications to organize data for fast rendering. Many developers prefer to code applications to a scene graph API rather than directly to a graphics platform, like OpenGL, since the scene graph API often encapsulates details of the platform as well as provides services and features not present in the underlying platform.
OpenRM adds value to the underlying graphics platform by providing functionality that is not present, or difficult to access using OpenGL. At the primitive level, things like text and volumetrics are first class primitives in OpenRM, but are not present at all in OpenGL. Specification of material properties, lights, lighting environment, textures and other rendering or scene properties is simplified with the OpenRM API. Framebuffer control, including per-frame clear of the color and depth buffers using a solid color or value, or with an application-defined image, is provided by the scene graph model. Multipass rendering is implemented by using a set of scene graph traversal flags that enable or disable render-time traversal based upon attributes such as 3D and opaque, 3D and transparent, 2D, left channel or right channel. Picking is implemented as a first-class frame-based operation, and can be used to discover which object, or objects, cover a particular (x,y) screen location.
Extensibility is achieved through either run-time additions, through the use of a rich set of callbacks, or by developers' direct access to the source code itself. One of the OpenRM demonstration programs illustrates view frustum culling with the use of a node "pre-traversal" callback, checking to see if an object's bounding box lies within the view volume, and if not, trivially rejecting the object from rendering, thereby lightening the rendering load on the application, resulting in improved performance. Other node-level callbacks include a switch callback, which can be used to implement distance-based model switching, or level-of-detail control (LOD), or post-traversal callbacks. All callbacks, as well as user-defined primitive draw routines, have access to the full rendering state, including model, view, and projection matrices, along with their inverses, as well as current material and lighting properties.
In addition to the scene graph modeling and rendering services, OpenRM includes two additional components that supply visualization and event management services. The visualization tools create renderable geometry in the form of scene graph nodes directly from either y=f(x), z=f(x,y), or w=f(x,y,z) data (the RMV library). Event management, services, including two user interface models for interactive transformation, implement mappings of mouse button and motion events to single-node transformations, and support mapping of application-defined callbacks to specific events. A "flight stick" camera/viewer motion model in the RMAUX library can be used for "terrain flyovers."
Through a procedural interface, OpenRM allows developers to create highly portable and efficient graphics and visualization applications. Since OpenRM uses OpenGL as the underlying graphics API, OpenRM can be used on any platform that supports OpenGL. OpenRM has been deployed and tested on many Unix, Linux and Win32 platforms, including Windows 95/98/NT/W2K.
This information is copied from VRCO's website:
The CAVELib is an Application Programmers Interface (API) that provides general support for building virtual environments for Spatially Immersive Displays and head-mounted displays including desk-type devices, cubic displays, multi-piped curved displays, and some dome styled displays. The CAVELib is not an application, it's a building block used to create applications for a variety of virtual environments.
The CAVELib configures the display device, synchronizes processes, draws stereoscopic views, creates a viewer-centered perspective and provides basic networking between remote Virtual Environments. The CAVELib allows a single program to be available on a wide variety of virtual display devices without rewriting or recompiling. The CAVELib uses a resource configuration file that can be modified to change display and input devices, making the programs written on the CAVELib portable to a wide-variety of display devices.
The CAVELib is an API of functions that can be used by programmers to create cross-platform, robust programs for virtual display devices or desktops. The CAVELib is not the product of choice for the non-programmers and end-users simply wanting to interact with a virtual environment. For those customers there are a variety of applications available including the VRCO's VRScape[tm] model viewer.
From a high level view, CAVELib provides a framework for organizing a VR application, including VR device event management and propogation to multiple drawing processes. To complement this framework, OpenRM provides scene graph and rendering services. It is important to note that CAVELib by itself does not provide any rendering services whatsoever - it creates and manages the OpenGL rendering context, and computes the model and view matrices for applications so that when user draw code is invoked (the draw callback), developers need only be concerned with dispatching OpenGL draw calls as quickly as possible from their own object database.
With this in mind, we approach the task of application development using CAVELib and OpenRM from the perspective that high level application architecture follows from CAVELib, and that OpenRM is used as a service within this application framework for scene graph construction and rendering. Using this approach, we find that CAVELib and OpenRM are highly complementary technologies.
The balance of this document serves to identify specific technical issues that require attention when creating a CAVELib application that uses OpenRM for scene graph construction and rendering, and to identify areas of future work.
Creating a CAVELib application that uses OpenRM's scene graph and rendering services is a straightforward task, but requires careful attention to a few specific areas. By and large, these areas are all related to CAVELib's management of the OpenGL rendering context: OpenRM can be used to create a complete, standalone graphics application, so those steps necessary to initialize windows and OpenGL context are effectively replaced with CAVELib equivalents. Issues related to multiprocessing and distributed applications are discussed in Section 4, Future Work.
Referring to the CAVELib user's manual (www.vrco.com/CAVE_USER), we see that inside the application's "main" are calls that initialize the CAVELib, and that establish callbacks to application code that perform one-time initialization, per-frame drawing and per-frame computation, and event loop management.
The following code snippet shows a baseline set of calls that will initialize the CAVELib, and register application initialization and draw callbacks with CAVELib.
int
main(int argc,
char *argv[])
{
CAVEConfigure(&argc, argv, NULL);
CAVEInit();
CAVEInitApplication(appInitGL, 0); /* register gfx init func with CAVE.
this callback invoked only once. */
CAVEDisplay(appDrawFunc, 0); /* register draw function with CAVE.
invoked once per frame. */
CAVEFrameFunction(mySpinFunc, 0); /* register per frame processing callback.
also invoked once per frame */
while (!CAVEgetbutton(CAVE_ESCKEY)) /* loop until user presses ESC key */
usleep(1000); /* sleep 1000 microseconds (1msec) */
CAVEExit();
}
From an OpenRM and CAVELib applications development perspective, there are three key functions registered with CAVELib in this code snippet. First, CAVEInitApplication registers a callback used to perform application-specific initialization. This callback is invoked once per application run. Second, CAVEDisplay registers the application "draw" callback with CAVELib. This function is invoked once per frame, and its purpose is to actually draw the scene. Finally, CAVEFrameFunction registers a per-frame compute routine with CAVELib. Inside that routine, the application will perform per-frame processing, such as simulation/computation or model updates. For more details on these callbacks from the CAVELib perspective, please refer to the CAVELib Users Manual. The other CAVELib calls are also significant and required in OpenRM and CAVELib applications, but have no impact on the subject of this article.
CAVELib is a framework for VR applications development. It gathers VR input device events, computes view transformations, invokes user-supplied draw code, performs framebuffer management (swapbuffers) and process synchronization. In this article, we wish to focus upon the application-supplied draw code, as this is the service provided by OpenRM, as well as the fundamental CAVELib assumptions regarding OpenGL context management policy.
Upon entry to the user-supplied draw code, the OpenGL context has been pre-loaded with values by CAVELib that reflect the viewing transformation. In other words, user draw code is invoked once per viewpoint, per display surface. Stereo views require two renderings, one for each eye of a binocular view. In OpenGL paralance, the ModelView and Projection matrices are loaded by CAVELib for each view, and the user draw code simply dispatches OpenGL draw calls. Implicit in this statement is that the user draw code must also perform some type of scene management, such as traversing a geometric database of primitives.
OpenRM in a CAVELib application serves to alleviate the burden of scene management and rendering code. By default, OpenRM is predisposed to perform all OpenGL rendering context management: modeling transformations are explicit in the scene graph nodes; cameras, both 2D and 3D, define views within the scene graph, including the option for orthographic or perspective projection. As of OpenRM version 1.3.0 (September 2000), developers may exercise "fine-grained" control over how the OpenGL context is manipulated - these additions are central to "OpenRM and CAVELib compatibility." Example code is provided below in the section describing a Graphics Initialization Function.
Multipass rendering refers to the process of performing multiple rendering passes through the scene graph in order to create a single image. In stereo rendering, for example, the scene is rendered first from the left-eye view, then from the right-eye view, then the final composite image presented to the user. Anaglyph stereo is a presentation format where the left eye view is rendered in one color, such as shades of red, and the right eye view is rendered in shades of a different color, such as cyan. The final image, containing both left- and right-eye views, has both images represented in full color. The viewer is required to wear glasses with different colored lenses in front of each eye, and the lenses perform filtering based upon light wavelength. Multibuffered stereo, in contrast, is when the left-eye view is rendered into the left buffer, the right-eye view is rendered into the right buffer, and the graphics hardware itself displays first the left buffer, then the right buffer, but the alternating of displays happens quickly, faster than detectable by the human eye. Again, the viewer must wear special glasses that perform filtering to present only the left-eye image to the left eye, and the right-eye image to the right eye.
In a standalone OpenRM application, a stereo or monoscopic format is specified by assigning the appropriate attribute to an RMpipe object using one of RM_MONO_CHANNEL, RM_MBUF_STEREO_CHANNEL, RM_REDBLUE_STEREO_CHANNEL or RM_BLUERED_STEREO_CHANNEL. Internal to OpenRM, framebuffer manipulation and multiple rendering passes through the scene graph are performed in order to produce the desired output format. In CAVELib, the user specifies a stereo format through a CAVELib config file (e.g., a user-customizable .caverc file that supplements/overrides default settings in the site-wide configuration file), and CAVELib will invoke user draw code multiple times for stereo views, and will perform all framebuffer manipulation (swapbuffers) outside the user draw code.
OpenRM performs an additional type of multipass rendering beyond what is required for stereo views. By default, OpenRM makes three rendering passes through the scene graph per frame. During the first pass, only 3D and opaque objects are drawn. In the second pass, only 3D and transparent objects are drawn, and alpha blending is enabled. During the final pass, only 2D objects are drawn, and the depth test is disabled (so all 2D objects appear "on top of" the 3D scene). Regardless of the stereo or monoscopic format selected in an OpenRM application, all three of these passes will be performed, unless explicitly disabled (see rmPipeSetRenderPassEnable). During a render traversal of the scene graph, the traversal masks of RMnodes are compared with those of the current rendering pass. Developers can "schedule" RMnodes for traversal (and rendering) by manipulating these traversal masks (see rmNodeSetTraversalMaskChannel, rmNodeSetTraversalMaskOpacity and rmNodeSetTraversalMaskDims). In an OpenRM/CAVELib application, this type of multipass rendering will be performed, but may be overridden by the developer.
The following list summarizes the specific areas where OpenRM's default behavior must be adjusted for use as a rendering service and scene graph management infrastructure for use in CAVELib applications. Please refer to the sample graphics initialization function (below) for sample code.
The following sample code shows how OpenRM is initialized from a CAVELib application. In this initialization function, after OpenRM has been initialized, we perform two alterations to OpenRM's "default settings." First, we set the OpenRM matrix stack management policy. This change is needed to honor the viewing transformation computed and assigned by the CAVELib. The second is to set the OpenRM rendering context management policy. The only change needed is to disable buffer-swapping within OpenRM, as CAVELib provides that service.
void
appInitGL(void)
{
RMenum status;
int myWidth, myHeight, myOriginX, myOriginY;
rmInit(); /* initialize OpenRM */
myPipe = rmPipeNew(); /* create an OpenRM pipe */
/* for OpenRM + CAVELib apps, always use an RM_MONO_CHANNEL. see
the OpenRM + CAVELib White Paper for caveats. */
rmPipeSetChannelFormat(myPipe, RM_MONO_CHANNEL);
/* turn off OpenRM initialization of the OpenGL matrix stack. */
rmPipeSetInitMatrixStackMode(myPipe, RM_FALSE);
/* turn off swapbuffers - CAVElib handles that. */
rmPipeSetSwapbuffersFunc(myPipe, NULL);
/* obtain the OpenGL context from CAVElib, assign that to the pipe */
rmxPipeSetContext(myPipe, CAVEGLXContext());
rmxPipeSetDisplay(myPipe, CAVEXDisplay());
/* obtain and assign window geometry */
CAVEGetWindowGeometry(&myOriginX, &myOriginY, &myWidth, &myHeight);
rmPipeSetWindow(myPipe, CAVEXWindow(), myWidth, myHeight);
rmPipeMakeCurrent(myPipe);
/* build the model which will later be rendered during the per-frame
CAVELib draw callback. */
buildInitialSceneGraph();
}
Once per frame, CAVELib invokes an application-supplied draw callback. In the OpenRM+CAVELib application, the following code snippet represents the draw callback registered in the sample main() (above). All this callback does is invoke the general purpose OpenRM frame drawing method.
void
appDrawFunc(void)
{
rmFrame();
}
While it is straightforward to create an application that uses CAVELib and OpenRM, there are several specific technical areas that require future development to fully leverage the capabilities of both systems. These include multiprocessing applications, multipass rendering, and intersection computation and collision detection.
As of the time of this writing (July 2000), a CAVELib application, once fully initialized, consists of multiple processes created by the fork() system call. While the exact demography of processes is in part a function of site-specific configuration, we can make the following generalizations about CAVELib processes:
This is the only process that returns from the CAVEInit() call (the point at which the application goes parallel). The "per-frame computation callback" is invoked from within this process. This routine is intended to be used to perform per-frame computations, such as updates to the model.
There exists one draw process "per screen." All rendering must occur within this process, and as quickly as possible.
In many cases, what applications will want is to render from a single scene graph from several views. In this scenario, there will exist a single copy of the scene graph which is traversed by multiple drawing or rendering threads or processes. This approach is attractive because it makes most effective use of memory.
At this time (July 2000), all of OpenRM's internal data structures are created with the malloc() system call, so the scene graph itself is allocated off the heap from within a single process. These data structures do not exist in a shared memory format, so are not available to other processes. This limitation prohibits the use of a single scene graph as the source for multiple rendering processes using the fork() model of multiprocessing. Alternatively, a threads-based model for multiprocessing would allow for multiple rendering threads to access this single scene graph.
The most expiditious workaround at this time is for multipipe CAVE applications to create and render from multiple copies of the scene graph. To implement this, the graphics initialization function is called after CAVELib goes parallel (after CAVEInit()). The graphics initialization function then initializes OpenRM, as in the code example above. During the per-frame draw callback, each process will invoke the OpenRM frame-based rendering function as in the code example above. The application can use CAVELib tools to distribute data amongst processes, which can perform any updates to local copies of the scene graph. In this approach, no special synchronization constructs are required, as each of the many separate CAVELib processes is manipulating its own copy of the scene graph.
Implemention of the alternative, where a single scene graph is shared amongst several execution threads, will be the subject of future work on the part of both OpenRM and CAVELib, and will follow one of two approaches (or possibly both).
In one approach, CAVELib will transition to a threads-based multiprocessing model, so that explicit shared memory is not required in order for multiple threads to have access to dynamically allocated memory. With a threads model, additional work is required on the OpenRM side to ensure that scene graph traversal for rendering is completely thread-safe. At this time, it is not completely thread safe, for certain OpenGL context-specific data is stored as part of the scene graph. The data which is stored in the scene graph includes OpenGL display list indices and texture object identifiers. This is a known "feature" of OpenRM, and is on the list of future work for the OpenRM project. In addition to thread-safety issues in OpenRM, some application-side work will be required to ensure orderly access when writing to the scene graph. Application-level control will be needed to ensure that multiple application threads do not attempt to simultaneously write into the same memory location, and that simultaneous writes to and reads from a single memory location are prohibited.
CAVELib and OpenRM are complementary technologies: CAVELib provides a framework for developing VR applications for deployment on multiple display surface, spatially immersive environments; OpenRM is a scene graph API used for creating cross-platform, high-performance graphics applications. The benefits of using both technologies to develop applications include the processing framework and access to VR devices provided by CAVELib, and the rendering and scene graph management services of OpenRM. The process of combining these two technologies is straightforward. At present (July 2000), the only significant limitation of OpenRM/CAVELib applications is that the OpenRM scene graph is malloc'ed off the heap, so use in multiple display surface applications requires multiple copies of the scene graph, and explicit application-level propogation of scene data. Future growth plans for CAVELib include movement to a threaded (shared memory) model, so this limitation will no longer apply in shared memory environments.
CAVELib is a registered trademark of the University of Illinois, and is the CAVELib software is commercially licensed by VRCO (www.vrco.com).
|
This page last modified
Friday, 26-Dec-2003 17:46:06 PST
Web problem or question? Send email to webmazen at r3vis.com |