+ All Categories
Home > Documents > Equalizer Programming Guide - GitHub...

Equalizer Programming Guide - GitHub...

Date post: 09-Mar-2020
Category:
Upload: others
View: 5 times
Download: 0 times
Share this document with a friend
38
Equalizer Programming Guide http://www.equalizergraphics.com/documents/Developer/ProgrammingGuide.pdf Eyescale Software GmbH Version 1.0 for Equalizer 0.4 October 29, 2007
Transcript
Page 1: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Equalizer Programming Guidehttp://www.equalizergraphics.com/documents/Developer/ProgrammingGuide.pdf

Eyescale Software GmbH

Version 1.0 for Equalizer 0.4

October 29, 2007

Page 2: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Equalizer 0.4 Programming Guide

Contributors

Written by Stefan Eilemann.Engineering contributions by Maxim Makhinya and Jonas Bosch.

Copyright

©2007 Eyescale Software GmbH. All rights reserved. No permission is granted tocopy, distribute, or create derivative works from the contents of this electronic docu-mentation in any manner, in whole or in part, without the prior written permissionof Eyescale Software GmbH.

Trademarks and Attributions

OpenGL is a registered trademark, OpenGL Multipipe is a trademark of SiliconGraphics, Inc. Linux is a registered trademark of Linus Torvalds. Mac OS is atrademark of Apple Inc. CAVELib is a registered trademark of the Universityof Illinois. The CAVE is a registered trademark of the Board of Trustees of theUniversity of Illinois at Chicago. Qt is a registered trademark of Trolltech. Allother trademarks and copyrights herein are the property of their respective owners.

Feedback

If you have comments about the content, accuracy or comprehensibility of thisprogramming guide, please contact [email protected].

Previous Page

The images on the front page show: a terrain rendering application on a six-nodedisplay wall [top left], the eqPly polygonal renderer in a three-sided CAVE [middle],two volume rendering results from eVolve1 [bottom left and middle] and a six-nodesort-last database decomposition with parallel direct-send recomposition2 [bottomright].

1Data sets courtesy of General Electric, USA and AVS, USA2Data set courtesy of Stanford University Computer Graphics Laboratory

Page 3: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Contents

1 Introduction 1

2 Getting Started 12.1 Installing Equalizer and running eqPly . . . . . . . . . . . . . . . . . 12.2 Equalizer Processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

2.2.1 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.2 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.2.3 Render Clients . . . . . . . . . . . . . . . . . . . . . . . . . . 2

3 Hello, World! 2

4 The Programming Interface 34.1 Task Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34.2 Execution Modeal and Thread Safety . . . . . . . . . . . . . . . . . . 34.3 Config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

4.3.1 Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54.3.2 Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54.3.3 Window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64.3.4 Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

4.4 Compounds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64.4.1 Compound Channels . . . . . . . . . . . . . . . . . . . . . . . 64.4.2 Frustum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64.4.3 Compound Classification . . . . . . . . . . . . . . . . . . . . 74.4.4 Decomposition - Attributes . . . . . . . . . . . . . . . . . . . 74.4.5 Recomposition - Frames . . . . . . . . . . . . . . . . . . . . . 7

5 The eqPly polygonal renderer 75.1 The main Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85.2 Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

5.2.1 Main Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95.2.2 Render Clients . . . . . . . . . . . . . . . . . . . . . . . . . . 11

5.3 Distributed Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . 125.3.1 InitData - a Static Distributed Object . . . . . . . . . . . . . 125.3.2 FrameData - a Versioned Distributed Object . . . . . . . . . 13

5.4 Config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135.4.1 Initialization and Exit . . . . . . . . . . . . . . . . . . . . . . 135.4.2 Frame Control . . . . . . . . . . . . . . . . . . . . . . . . . . 155.4.3 Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 15

5.5 Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165.5.1 Frame Control . . . . . . . . . . . . . . . . . . . . . . . . . . 17

5.6 Pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195.6.1 Initialization and Exit . . . . . . . . . . . . . . . . . . . . . . 195.6.2 Window System . . . . . . . . . . . . . . . . . . . . . . . . . 195.6.3 Carbon/AGL Thread Safety . . . . . . . . . . . . . . . . . . . 205.6.4 Frame Control . . . . . . . . . . . . . . . . . . . . . . . . . . 20

5.7 Window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205.7.1 Initialization and Exit . . . . . . . . . . . . . . . . . . . . . . 205.7.2 Object Manager . . . . . . . . . . . . . . . . . . . . . . . . . 22

5.8 Channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235.8.1 Initialization and Exit . . . . . . . . . . . . . . . . . . . . . . 235.8.2 Rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Page 4: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

6 Advanced Features 276.1 Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

6.1.1 Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286.1.2 Initialization and Exit . . . . . . . . . . . . . . . . . . . . . . 286.1.3 Message Pump . . . . . . . . . . . . . . . . . . . . . . . . . . 286.1.4 Event Data Flow . . . . . . . . . . . . . . . . . . . . . . . . . 296.1.5 Custom Events in eqPixelBench . . . . . . . . . . . . . . . . 29

6.2 Image Compositing for Scalable Rendering . . . . . . . . . . . . . . . 306.2.1 Parallel Direct Send Compositing . . . . . . . . . . . . . . . . 316.2.2 Frame, Frame Data and Images . . . . . . . . . . . . . . . . . 316.2.3 Custom Assembly in eVolve . . . . . . . . . . . . . . . . . . . 32

6.3 Head Tracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Rev Date Changes1.0 Oct 28, 2007 Initial Version for Equalizer 0.4

Page 5: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

1 Introduction

Equalizer provides a framework for the development of parallel OpenGL applica-tions. Equalizer-based applications can run from a single shared-memory systemwith one or multiple graphics cards up to large-scale graphics clusters. This Pro-gramming Guide introduces the programming interface, often using the EqualizereqPly example as a guideline.

Equalizer is the next step in the evolution of generic parallel programming in-terfaces for OpenGL-based visualization applications. Existing solutions, such asOpenGL Multipipe SDK, Cavelib and VRJuggler, implement a subset of conceptssimilar to Equalizer. In other areas, e.g., tracking device support, they providemore functionality.

In order to adapt an application for Equalizer, the programmer structures thesource code so that the OpenGL rendering can be executed in parallel, poten-tially using multiple processes for cluster-based execution. Equalizer provides thedomain-specific parallel rendering know-how and abstracts configuration, thread-ing, synchronization, windowing and event handling. It is a ‘GLUT on steroids’,providing parallel and distributed execution, scalable rendering features and fullycustomizable event handling.

If you have any question regarding Equalizer programming, this programmingguide, or other specific problems you encountered, please direct them to the eq-devmailing list3.

2 Getting Started

2.1 Installing Equalizer and running eqPly

Equalizer can be installed by downloading the distribution4 and compiling thesource code. After installing Equalizer, please take a look at the Quickstart Guide5

to get familiar with the capabilities of the eqPly example.Compiling Equalizer is as simple as running make on Linux or building the Equal-

izer Visual Studio 2005 solution on Windows. On Mac OS X 10.4 (Tiger), some pre-requisites have to be installed before running make, as explained in README.Darwin.Mac OS X 10.5 Leopard does have all the prerequisites installed by default.

2.2 Equalizer Processes

The Equalizer architecture is based on a client-server model. The client library ex-poses all functionality discussed in this document to the programmer, and providescommunication between the different Equalizer processes.

2.2.1 Server

Each Equalizer server is responsible for managing one visualization system, i.e., ashared memory system or graphics cluster. It controls and launches the application’srendering clients. Currently, Equalizer only supports one application per server, butit will provide concurrent and efficient multi-application support in future.

3see http://www.equalizergraphics.com/lists.html4http://www.equalizergraphics.com/downloads.html5http://www.equalizergraphics.com/documents/EqualizerGuide.html

1

Page 6: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

2.2.2 Application

The application connects to an Equalizer server and receives a configuration. Fur-thermore, the application also provides its render client, which will be controlled bythe server. The application reacts on events, updates its database and controls therendering.

2.2.3 Render Clients

libeq.soApplication

libeq.soApplication

Render Client

libeq.soApplication

Render Client

libeq.soApplication

Render Client

provides controls

drives libeq.so

Equalizer Server

Figure 1: Equalizer Processes

The render client implements the rendering partof an application. Its execution is passive, it hasno main loop and is completely driven by Equal-izer, based on the rendering tasks received fromthe server. The tasks are executed by callingthe appropriate task methods (see Section 4.1)in the correct thread and context. The appli-cation either implements the task methods withapplication-specific code or uses the default meth-ods provided by Equalizer.

The application can also be a rendering client,in which case it can also contribute to the render-ing. If it does not implement any render client-related code, it is reduced to be theapplication’s ‘master’ process without any OpenGL windows and rendering code.

The rendering client can be the same executable as the application, as it is thecase with all provided examples. When it is started as a render client, the Equalizerinitialization routine does not return and takes over the control by calling the renderclient task methods. Complex applications usually implement a separate, light-weight rendering client.

3 Hello, World!

Figure 2: Hello, World!

The eqHello example is aminimal application to il-lustrate the basic princi-ple of an Equalizer appli-cation: The application de-veloper has to implementthe rendering method Chan-nel::frameDraw, similar to theglutDisplayFunc in GLUT ap-plications. It can be run as astand-alone application fromthe command line.

The eqHello redraw func-tion renders six rotating, col-ored quads around the origin.The frameDraw method pro-vided by the eq::Channel canbe used as a convience function to setup the frustum and other OpenGL state.After setting up some additional lighting parameter, eqHello rotates the scene andrender the quads using immediate mode:

void Channel : : frameDraw ( const u in t 32 t sp in ){

// se tup OpenGL Sta te

2

Page 7: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

eq : : Channel : : frameDraw ( sp in ) ;

const f loat l i gh tPos [ ] = { 0 .0 f , 0 . 0 f , 1 . 0 f , 0 . 0 f } ;g l L i gh t f v ( GL LIGHT0 , GL POSITION, l i gh tPos ) ;

const f loat l ightAmbient [ ] = { 0 .2 f , 0 . 2 f , 0 . 2 f , 1 . 0 f } ;g l L i gh t f v ( GL LIGHT0 , GL AMBIENT, l ightAmbient ) ;

// ro t a t e scene around the o r i g i ng lRota t e f ( static cast< f loat >( sp in ) * 0 .5 f , 1 . 0 f , 0 . 5 f , 0 .25 f ) ;

// render s i x axis−a l i gned co lored quads around the o r i g i n[ . . . ]

}

The eqHello main function sets up the communication with the server, initializesand drives the rendering. The details of this setup are explained in Section 5.

4 The Programming Interface

Working on the parallelization of customer applications in order to scale the displaysize and performance for 3D rendering. Design and development of scalable, parallelgraphics applications, 3D graphics software and hardware consulting.

Equalizer uses a C++ programming interface. The API is minimally invasive,so Equalizer imposes only a minimal, natural execution framework upon the appli-cation. It does not provide a scene graph, or interfere in any other way with theapplication’s rendering code. The restructuring work required for Equalizer is theminimal refactoring needed to parallelize the application for rendering.

Methods called by the application have the form verb[Noun], whereas methodscalled by Equalizer (‘Task Methods’) have the form nounVerb. For example, theapplication calls Config::startFrame to render a new frame, which causes –amongother things– Node::frameStart to be called in all active render clients.

4.1 Task Methods

The application inherits from Equalizer classes and overrides virtual functions to im-plement certain functionality, e.g., the application’s OpenGL rendering in eq::Chan-nel::frameDraw. These task methods are similar in concept to C function callbacks.The eqPly section will discuss the most important task methods. A full list can befound on the website6.

4.2 Execution Modeal and Thread Safety

Using threading correctly in OpenGL-based applications is easy with Equalizer.Equalizer creates one rendering thread for each graphics card. All task methodsfor a pipe, and therefore all OpenGL commands, are executed from this thread.This threading model is the OpenGL ‘threading model’, which maintains a currentcontext for each thread. If structured correctly, the application rarely has to takecare of thread synchonization or protection of shared data.

The main thread is responsible for maintaining the application logic. It reactson user events, updates the data model and requests new frames to be rendered. Itdrives the whole application, as shown in Figure 3.

The rendering threads concurrently render the application’s database. The data-base should be accessed in a read-only fashion during rendering to avoid threading

6see http://www.equalizergraphics.com/documents/design/taskMethods.html

3

Page 8: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

problems. This is normally the case, for example all modern scene graphs useread-only render traversals.

Application

init send tasks

trigger new frame

wait for frame finish

handle events

update database

exit

Server Render Clients

init

sendrender tasks

executerender tasks

sync frame finish

send tasks exit

idle processing

Figure 3: Simplified execution model

All rendering threads inthe configuration run asyn-chronously to the applica-tion’s main thread. Depend-ing on the configuration’s la-tency, they can fall n framesbehind the last frame finishedby the application thread. Alatency of one frame is usuallynot perceived by the user, butcan increase rendering perfor-mance substantially.

Rendering threads on a sin-gle node are by default syn-chronized. When a frameis finished, all local render-ing threads are done drawing.Therefore the application cansafely modify the data be-tween the end of a frame andthe beginning of a new frame.Furthermore, only one instance of the application data has to be maintained withina process since all rendering threads are guaranteed to draw the same frame.

This per-node frame synchronization does not inhibit latency across renderingnodes. Furthermore, advanced rendering software which multi-buffers the dynamicparts of the database can disable the per-node frame synchronization, as explainedin Section 5.5.1. Some scene graphs do implement multi-buffered data.

4.3 Config

The eq::Config represents the current configuration of the application. The con-figuration is the session in which all render clients are registered. A configurationconsists of the description of the rendering resources and the usage description forthese resources.

The rendering resources are represented in a hierarchical tree structure whichcorresponds to the physical and logical resources found in a 3D rendering environ-ment.

The resource usage is configured using a compound tree, which is a hierarchi-cal representation of the rendering decomposition and recomposition across theresources. It is explained in Section 4.4.

Figure 4 shows an example configuration for a four-side CAVE, running on twomachines (nodes) using three graphics cards (pipes) with one window each to renderto the four output channels connected to the projectors for each of the walls. Thecompound description is only used by the server to compute the rendering tasks.The application is not aware of compounds, and does not need to concern itselfwith the parallel rendering logics of a configuration.

For testing and development purposes it is possible to use multiple instancesfor one resource, e.g. to run multiple render client nodes on one computer. Fordeployment, one node and pipe should be used for each computer and graphicscard, respectively.

4

Page 9: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Resource UsageConfigResources

compoundeye [ LEFT RIGHT ]

channel "left"wall { ... }swapbarrier{}

channel "front"wall { ... }swapbarrier{}

channel "floor"wall { ... }swapbarrier{}

channel "right"wall { ... }swapbarrier{}Channel

name "left"viewport {...}

Windowviewport {...}

Pipe

Node Node

Windowviewport {...}

Pipe

Channelname "floor"

Windowviewport {...}

Pipe

Channelname "front"

Channelname "right"viewport {...}

Figure 4: An example configuration

4.3.1 Node

The eq::Node class is the representation of a single computer in a cluster. Oneoperating system process of the render client will be used for each node. Eachconfiguration might also use an application node, in which case the applicationprocess is also used for rendering. All node-specific task methods are executed fromthe main application thread.

4.3.2 Pipe

The eq::Pipe class is the abstraction of a graphics card (GPU). In the current imple-mentation it is also one operating system thread. Non-threaded pipes are supportedfor integrating with thread-unsafe libraries, but have various performance caveats.All pipe, window and channel task methods are executed from the pipe’s thread, orin the case of non-threaded pipes from the main application thread7.

Further versions of Equalizer might introduce threaded windows, where all window-related task methods are executed in a separate operating system thread.

7see http://www.equalizergraphics.com/documents/design/nonthreaded.html

5

Page 10: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

4.3.3 Window

The eq::Window class holds a drawable and an OpenGL context. The drawable canbe an on-screen window or an off-screen8 PBuffer or framebuffer object (FBO). Thewindow holds window-system-specific handles to the drawable and context, e.g., anX11 window XID and GLXContext for the glX window system.

4.3.4 Channel

The eq::Channel class is the abstraction of an OpenGL viewport within its parentwindow. It is the entity executing the actual rendering. The channel’s viewportmight be overwritten when it is rendering for another channel during scalable ren-dering.

4.4 Compounds

Usage of the rendering resources is configured using a compound tree. Althoughthe API does not currently expose compounds, the basic design behind compoundsis explained here for a better understanding of the Equalizer architecture. Fur-ther information on the configuration of compounds can be found on the Equalizerwebsite9.

4.4.1 Compound Channels

Each compound has a channel, which is used by the compound to execute therendering tasks. One channel might be used by multiple compounds. Unusedchannels are not instantiated during initialization. The rendering tasks for thechannels are computed by the server and send to the appropriate render clients.

4.4.2 Frustum

projection

wall

distance

position

bottom-rightbottom-left

top-left

HPRFOV

Figure 5: Wall and projection parameters

Compounds have a frus-tum description to define thephysical layout of the displayenvironment.

The frustum description isinherited by the children,therefore the frustum is typi-cally defined on the top-mostcompound.

The frustrum can be speci-fied as a wall or projection de-scription.

A wall is completely de-fined by the bottom-left,bottom-right and top-left co-ordinates relative to the ori-gin.

The projection is definedby the position and head-pitch-roll orientation of the projector, as well as the hori-zontal and vertical field-of-view and distance of the projection wall.

Figure 5 illustrates the wall and projection frustum parameters.

8Off-screen drawables are not implemented yet, but can be created by the application and usedwith Equalizer

9see http://www.equalizergraphics.com/documents/design/compounds.html

6

Page 11: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

4.4.3 Compound Classification

The channels of the leaf compounds in the compound tree are designated as sourcechannels. The top-most channel in the tree is the destination channel. Only sourcechannels execute rendering task. All channels in a compound tree work for thedestination channel. The destination channel defines the 2D pixel viewport renderedby all leaf compounds. The destination channel and pixel viewport can not beoverridden by child compounds.

4.4.4 Decomposition - Attributes

Compounds have attributes which configure the decomposition of the destination’schannel viewport, frustum and database. A viewport decomposes the destinationchannel and frustum in screen space. A range tells the application to render a partof its database, and an eye rendering pass can selectively render different stereopasses. Setting one or multiple attributes causes the parent’s view to be decomposedaccordingly. Attributes are cumulative, that is, intermediate compound attributesaffect and therefore decompose the rendering of all their children.

4.4.5 Recomposition - Frames

Compounds use output and input frames to configure the recomposition of theresulting pixel data from the source channels. An output frame connects to an inputframe of the same name. The selected frame buffer data is transported from theoutput channel to the input channel. The assembly routine of the input channelwill block on the availability of the output frame. This composition process isextensively described in Section 6.2.

5 The eqPly polygonal renderer

In this section the source code of eqPly is explained in detail, and relevant designdecisions, caveats and other remarks are raised.

The eqPly example is shipped with the Equalizer distribution and serves as areference implementation of an Equalizer-based application of medium complexity.It focuses on the example usage of core Equalizer features, not on rendering featuresor visual quality.

configInitframeDraw

Channel

connectServerdisconnectServer

Client

chooseConfigreleaseConfig

Server

initexitstartFramefinishFramesendEventhandleEventshandleEvent

ConfigconfigInitconfigExitframeStartframeFinish

NodeconfigInitconfigExitframeStartframeFinish

PipeconfigInitconfigExitframeStartframeFinishmakeCurrentswapBuffers

WindowconfigInitconfigExitframeStartframeFinishframeClearframeDrawframeReadbackframeAssemble

Channel

*

1*

1

*1

*1*

1

*1

runApplication

initexitstartFramehandleEvent

Config

configInitconfigExit

Node configInitconfigExitframeStart

Pipe

configInitconfigExit

Window

commitsyncgetVersion

setInstanceDatagetInstanceDataapplyInstanceData

setDeltaDatapackunpack

Object

getInstanceDataapplyInstanceData

InitData

FrameData

namespace eq

namespace eqPly

namespace eqNet

Figure 6: UML diagram of significant Equalizer and eqPly classes and task methods.

All classes in the example are in the eqPly namespace to avoid type name ambi-guities, in particular for the Window class which is frequently used as a type in the

7

Page 12: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

global namespace by windowing systems. Figure 6 shows how the most importantEqualizer classes are used through inheritance by the eqPly example.

The eqPly classes fall into two categories: Subclasses of the rendering entitiesintroduced in Section 4.3, and two other classes for distributing data. The functionand typical usage for each of the rendering entities is discussed in this section.

The distributed data classes are helper classes based on eqNet::Object. Theyillustrate the typical usage of distributed objects for static as well as dynamic,frame-specific data.

5.1 The main Function

The main function starts off with parsing the command line into the LocalInitDatadata structure. A part of it, the base class InitData, will be distributed to all renderclient nodes. The command line parsing is done by the LocalInitData class, whichis discussed in Section 5.3.1:int main ( int argc , char** argv ){

// 1 . parse argumentseqPly : : Loca l In i tData in i tData ;in i tData . parseArguments ( argc , argv ) ;

The second step is to initialize the Equalizer library. The initialization function ofEqualizer also parses the command line, which is used to set certain default valuesbased on Equalizer-specific options10, e.g., the default server address. Furthermore,a NodeFactory is provided. The EQERROR macro, and its counterparts EQWARN,EQINFO and EQVERB allow selective debugging outputs with various logging levels:

// 2 . Equa l i z e r i n i t i a l i z a t i o nNodeFactory nodeFactory ;i f ( ! eq : : i n i t ( argc , argv , &nodeFactory ) ){

EQERROR << ” Equa l i z e r i n i t f a i l e d ” << endl ;return EXIT FAILURE;

}

The node factory is used by Equalizer to create the object instances of the con-figured rendering entities. Each of the classes inherits from the same type providedby Equalizer in the eq namespace. The provided eq::NodeFactory base class in-stantiates ’plain’ Equalizer objects, thus making it possible to selectively subclassindividual entity types, as it is done by eqHello. For each rendering resources usedin the configuration, one C++ object will be created during initialization:class NodeFactory : public eq : : NodeFactory{public :

virtual eq : : Conf ig * c r ea t eCon f i g ( ) { return new eqPly : : Conf ig ; }virtual eq : : Node* createNode ( ) { return new eqPly : : Node ; }virtual eq : : Pipe* c r ea t eP ipe ( ) { return new eqPly : : Pipe ; }virtual eq : : Window* createWindow ( ) { return new eqPly : : Window ; }virtual eq : : Channel* createChannel ( ) { return new eqPly : : Channel ; }

} ;

The third step is to create an instance of the application and to initialize itlocally. The application is an eq::Client, which in turn is an eqNet::Node. Theunderlying network layer in Equalizer is a peer-to-peer network of eqNet::Nodes.The application programmer does not usually have to be aware of the classes in theeqNet namespace, but both the eq::Client and the server are eqNet::Nodes.

The local initialization of a node creates a local listening socket, which allows theeq::Client to communicate over the network with other nodes, such as the serverand the rendering clients.10Equalizer-specific options always start with - -eq-

8

Page 13: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

// 3 . i n i t i a l i z a t i o n o f l o c a l c l i e n t nodeRefPtr< eqPly : : App l i ca t ion > c l i e n t = new eqPly : : App l i ca t ion ( in i tData ) ;i f ( ! c l i e n t −>i n i t L o c a l ( argc , argv ) ){

EQERROR << ”Can ’ t i n i t c l i e n t ” << endl ;eq : : e x i t ( ) ;return EXIT FAILURE;

}

Finally everything is set up, and the eqPly application is executed:

// 4 . run c l i e n tconst int r e t = c l i e n t −>run ( ) ;

After the application has finished, it is de-initialized and the main function re-turns:

// 5 . c leanup and e x i tc l i e n t −>ex i tLo ca l ( ) ;c l i e n t = 0 ;

eq : : e x i t ( ) ;return r e t ;

}

5.2 Application

In the case of eqPly, the application is also the render client. The eqPly executablehas three run-time behaviours:

1. Application: The executable started by the user, the controlling entity inthe rendering session.

2. Auto-launched render client: The typical render client, started by theserver. The server starts the executable with special parameters, which causeClient::initLocal to never return. During exit, the server terminates the process.By default, the server starts the render client using ssh.

3. Resident render client: Manually pre-started render client, listening on aspecified port for server commands. This mode is selected using the command-line option –eq-client and potentially –eq-listen <address> and -r11.

5.2.1 Main Loop

The application’s main loop starts by connecting the application to an Equalizerserver. The command line parameter –eq-server explicitly specifies a server address.If no server was specified, Client::connectServer tries first to connect to a server onthe local machine using the default port. If that fails, it will create a server runningwithin the application process with a default one-channel configuration12.

int Appl i ca t ion : : run ( ){

// 1 . connect to s e rve rRefPtr<eq : : Server> s e r v e r = new eq : : Server ;i f ( ! connectServer ( s e r v e r ) ){

EQERROR << ”Can ’ t open s e r v e r ” << endl ;return EXIT FAILURE;

}

11see http://www.equalizergraphics.com/documents/design/residentNodes.html12see http://www.equalizergraphics.com/documents/design/standalone.html

9

Page 14: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

The second step is to ask the server for a configuration. The ConfigParams area placeholder for later Equalizer implementations to provide additional hints andinformation to the server for choosing the configuration. The configuration chosenby the server is created locally using NodeFactory::createConfig. Therefore it is oftype eqPly::Config, but the return value is eq::Config, making the cast necessary:

// 2 . choose con f i geq : : ConfigParams configParams ;Conf ig * c on f i g = static cast<Config*>( s e rver−>chooseConf ig ( configParams ) ) ;

i f ( ! c on f i g ){

EQERROR << ”No matching c on f i g on s e r v e r ” << endl ;d i s connec tSe rve r ( s e r v e r ) ;return EXIT FAILURE;

}

Finally it is time to initialize the configuration. For statistics, the time for thisoperation is measured and printed. During initialization the server launches andconnects all render client nodes, and calls the appropriate initialization task meth-ods, as explained in later sections. Config::init does return after all nodes, pipes,windows and channels are initialized. It returns true only if all initialization taskmethods were successful.

The EQLOG macro allows topic-specific logging. The numeric topic values arespecified in the respective log.h header files, and logging for various topics is enabledusing the environment variable EQ LOG TOPICS:

// 3 . i n i t con f i geqBase : : Clock c l o ck ;

con f i g−>s e t In i tData ( i n i tDa ta ) ;i f ( ! con f i g−> i n i t ( ) ){

EQERROR << ”Config i n i t i a l i z a t i o n f a i l e d : ”<< con f i g−>getErrorMessage ( ) << endl ;

s e rve r−>r e l e a s eCon f i g ( c on f i g ) ;d i s connec tSe rve r ( s e r v e r ) ;return EXIT FAILURE;

}

EQLOG( eq : :LOG CUSTOM ) << ”Config i n i t took ” << c l o ck . getTimef ( ) << ” ms”<< endl ;

When the configuration was successfully initialized, the main rendering loop isexecuted. The main loop runs until the user exits the configuration, or when amaximum number of frames has been rendered, when specified by a command-lineargument. The latter is useful for benchmarks. The Clock is reused for measuringthe overall performance. A new frame is started using Config::startFrame and aframe is finished using Config::finishFrame.

When the frame is started, the server computes all rendering tasks and sendsthem to the appropriate render client nodes. The render client nodes dispatch thetasks to the correct node or pipe thread, where they are executed in order of arrival.

Config::finishFrame synchronizes on the completion of the frame current - latency.The latency is specified in the configuration file, and allows several outstandingframes. This allows overlapping execution in the node and pipe threads and mini-mizes idle times.

By default, Config::finishFrame also synchronizes the completion of all local ren-dering tasks for the current frame. This facilitates porting of exising renderingcodes, since the database does not have to be multi-buffered. Applications suchas eqPly, which do not need this per-node frame synchronization can disable it, asexplained in Section 5.5.1.

10

Page 15: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

1

3

2

1

11

2 2 2

1

2

33

33

Config Render Threads

(a)

1

3

2

4

1

11

2

221

2

3

3

33

4 44

Config Render Threads

4

(b)

Figure 7: Synchronous and asynchronous execution

Figure 7 shows the exe-cution of (hypothetical) ren-dering tasks without latency(Figure 7(a)) and with alatency of one frame (Fig-ure 7(b)). With eqPly, aspeedup of 15% has been ob-served on a five-node render-ing cluster when using a la-tency of one frame instead ofno latency13. A latency ofone or two frames is normallynot perceived by the user.

When the main render-ing loop has finished, Con-fig::finishAllFrames is called tocatch up with the latency. Itreturns after all outstandingframes have been rendered,and is needed to provide anaccurate measurement of theframerate:

// 4 . run main loopu in t 32 t maxFrames = in i tDa ta . getMaxFrames ( ) ;

c l o ck . r e s e t ( ) ;while ( con f i g−>isRunning ( ) && maxFrames−− ){

con f i g−>startFrame ( ) ;// conf ig−>renderData ( . . . ) ;con f i g−>f in i shFrame ( ) ;

}const u in t 32 t frame = con f ig−>f i n i shAl lFrames ( ) ;const f loat time = c lock . getTimef ( ) ;EQLOG( eq : :LOG CUSTOM ) << ”Rendering took ” << time << ” ms ( ” << frame

<< ” frames @ ” << ( frame / time * 1000 . f )<< ” FPS) ” << endl ;

The remainder of the application code cleans up in the reverse order of initializa-tion. The config is exited, released and the connection to the server is closed:

// 5 . e x i t con f i gc l o ck . r e s e t ( ) ;con f i g−>e x i t ( ) ;EQLOG( eq : :LOG CUSTOM ) << ”Exit took ” << c l o ck . getTimef ( ) << ” ms” <<endl ;

// 6 . c leanup and e x i ts e rve r−>r e l e a s eCon f i g ( c on f i g ) ;i f ( ! d i s connec tSe rve r ( s e r v e r ) )

EQERROR << ” C l i en t : : d i s connec tSe rve r f a i l e d ” << endl ;s e r v e r = 0 ;return EXIT SUCCESS ;

}

5.2.2 Render Clients

In the second and third use case of the eqPly, when the executable is used as arender client, Client::initLocal never returns. Therefore the application’s main loop

13http://www.equalizergraphics.com/scalability.html

11

Page 16: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

is never executed. In order to keep the client resident, the eqPly example overridesthe client loop to keep it running beyond one configuration run:

bool Appl i ca t ion : : c l i en tLoop ( ){

i f ( ! i n i tDa ta . i sRe s i d en t ( ) ) // execute only one con f i g runreturn eq : : C l i en t : : c l i en tLoop ( ) ;

// e l s e execute c l i e n t l oops ’ f o r e v e r ’while ( true ) // TODO: implement SIGHUP handler to e x i t ?{

i f ( ! eq : : C l i en t : : c l i en tLoop ( ) )return fa l se ;

EQINFO << ”One con f i g u r a t i on run s u c c e s s f u l l y executed ” << endl ;}return true ;

}

5.3 Distributed Objects

Equalizer provides distributed objects which help implementing data distributionin a cluster environment. The master version of a distributed object is registeredwith a eqNet::Session, which assigns a session-unique identifier to the object. Othernodes can map their instance of the object to this identifier, thus synchronizing theobject’s data with the remotely registered master version.

Distributed objects are created by subclassing from eqNet::Object. Distributedobjects can be static (immutable) or dynamic. Dynamic objects are versioned.

The eqPly example has a static distributed object to provide initial data to allrendering nodes, as well as a versioned object to provide frame-specific data suchas the camera position to the rendering methods.

5.3.1 InitData - a Static Distributed Object

The InitData class holds a couple of parameters needed during initialization. Theseparameters never change during one configuration run, and are therefore static.

On the application side, the class LocalInitData subclasses InitData to providethe command line parsing and to set the default values. The render nodes onlyinstantiate the distributed part in InitData.

A static distributed object either has to provide a pointer and size to its datausing setInstanceData, or it has to implement getInstanceData and applyInstanceData.The first approach can be used if all distributed member variables are stored in onecontiguous block of memory. The second approach is used otherwise.

The InitData class contains a string of variable length. Therefore it uses thesecond approach of manually serializing and de-serializing its data. Serialization ingetInstanceData and de-serialization in applyInstanceData is performed by streamingall member variable to or from the provided data streams. Efficient buffering anddata transport between nodes is implemented in the data streams:

void In i tData : : get InstanceData ( eqNet : : DataOStream& os ){

os << frameDataID << windowSystem << useVBOs << useGLSL << f i l e name ;}

void In i tData : : applyInstanceData ( eqNet : : DataIStream& i s ){

i s >> frameDataID >> windowSystem >> useVBOs >> useGLSL >> f i l e name ;

EQASSERT( frameDataID != EQ ID INVALID ) ;EQINFO << ”New Ini tData in s t ance ” << endl ;

}

12

Page 17: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

The data input and output streams perform no type checking on the data. It isthe application’s responsibility to exactly match the order and types of variablesduring serialization and de-serialization.

5.3.2 FrameData - a Versioned Distributed Object

Versioned objects have to override isStatic to return false, which indicates that theyare versioned. The current implementation has the following characteristics:

� Only the master instance of the object is writable, that is, eqNet::Object::com-mit can be called only on the master instance to generate a new version.

� Slave instance versions can only be advanced, that is, eqNet::Object::sync(version ) with a version smaller than the current version will fail.

� Newly mapped slave instance are mapped to the oldest available version.

Upon commit the delta data from the previous version is sent to all mappedslave instances. The data is queued on the remote node, and is applied when theapplication calls sync to synchronize the object to a new version. The sync methodmight block if a version has not been committed or is still in transmission.

In addition to the instance data (de)serialization methods used to map an object,versioned objects may implement pack and unpack to serialize or de-serialize thechanges since the last version.

If the delta data happens to be layed out contiguously in memory, setDeltaDatamight be used. The default implementation of pack and unpack (de)serialize thedelta data or the instance data if no delta data has been specified.

The eqPly frame data is layed out in one anonymous structure in memory. It alsodoes not track changes since it is relatively small in size and changes frequently.Therefore, the instance and delta data are the same and set in the constructor. Thedefault implementation will take care of the distribution of the data:

FrameData ( ){

r e s e t ( ) ;s e t Ins tanceData ( &data , s izeof ( Data ) ) ;EQINFO << ”New FrameData ” << std : : endl ;

}

5.4 Config

The configuration is driving the application’s rendering, that is, it is responsible forupdating the data based on received events, requesting new frames to be renderedand to provide the render clients with the necessary data.

5.4.1 Initialization and Exit

The config initialization happens in parallel, that is, all config initialization tasks aretransmitted by the server at once and their completion is synchronized afterwards.

The tasks are executed by the node and pipe threads in parallel. The parent’sinitialization methods are always executed before any child initialization method.This parallelization is necessary to allow a speedy startup of the configuration onlarge-scale graphics clusters. On the other hand, it means that initialization func-tions are called even if the parent’s initialization has failed.

13

Page 18: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Config::init

EqualizerServer

Pipe Threads

Application

Client Node Processes

NodeFactory::createNode

NodeFactory::createChannel

NodeFactory::createWindow

NodeFactory::createPipe

Node::configInit

Pipe::selectWSPipe::configInit

Window::configInit

Channel::configInit

Figure 8: Config Initialization Sequence

The eqPly::Config class holdsthe master versions of the ini-tialization and frame data.Both objects are registeredwith the eq::Config, which isthe eqNet::Session used forrendering. Equalizer takescare of the session setup andexit in Client::chooseConfigand Client::releaseConfig, re-spectively.

The frame data is regis-tered before the initializationdata, since its identifier istransmitted using the Init-Data. The identifier of theinitialization data is trans-mitted to the render clientnodes using the initID param-eter of eq::Config::init.

Equalizer will pass this identifier to all configInit calls of the respective objects:

bool Config : : i n i t ( ){

// i n i t d i s t r i b u t e d o b j e c t sframeData . data . c o l o r = in i tDa ta . useColor ( ) ;

r e g i s t e rOb j e c t ( & frameData ) ;i n i tDa ta . setFrameDataID ( frameData . getID ( ) ) ;

r e g i s t e rOb j e c t ( & in i tDa ta ) ;

// i n i t con f i grunning = eq : : Conf ig : : i n i t ( i n i tDa ta . getID ( ) ) ;

i f ( ! running )return fa l se ;

If the configuration was initialized correctly, the configuration tries to set up atracking device for head tracking. Equalizer does not provide extensive support fortracking devices, as this is an orthogonal problem to parallel rendering. Trackingdevice support has already been solved by a number of implementations14, whichcan easily be integrated with Equalizer. The example code in eqPly provides areference implementation for the integration of such a tracking library. Section 6.3provides more background on head tracking.

// i n i t t r acke ri f ( ! i n i tDa ta . getTrackerPort ( ) . empty ( ) ){

i f ( ! t r a c k e r . i n i t ( i n i tDa ta . getTrackerPort ( ) ) )EQWARN << ” Fa i l ed to i n i t i a l i s e t r a cke r ” << endl ;

else{

// Set up po s i t i on o f t r ac k ing system in world space// Note : t h i s depends on the phy s i c a l i n s t a l l a t i o n .vmml : : Matr ix4f m( vmml : : Matr ix4f : : IDENTITY ) ;m. s c a l e ( 1 . f , 1 . f , −1. f ) ;//m. x = . 5 ;t r a c k e r . setWorldToEmitter ( m ) ;

m = vmml : : Matr ix4f : : IDENTITY;m. rotateZ ( −M PI 2 ) ;

14VRCO Trackd, VRPN, etc.

14

Page 19: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

t r a c k e r . setSensorToObject ( m ) ;EQLOG( eq : :LOG CUSTOM ) << ”Tracker i n i t i a l i s e d ” << endl ;

}}

return true ;}

The exit function of the configuration stops the render clients by calling eq::Con-fig::exit, and then de-registers the initialization and frame data objects with thesession:bool Config : : e x i t ( ){

running = fa l se ;const bool r e t = eq : : Conf ig : : e x i t ( ) ;

i n i tDa ta . setFrameDataID ( EQ ID INVALID ) ;d e r e g i s t e rOb j e c t ( & in i tDa ta ) ;d e r e g i s t e rOb j e c t ( & frameData ) ;

return r e t ;}

5.4.2 Frame Control

The rendering frames are issued by the application. The eqPly::Config only overridesstartFrame in order to update its data before forwarding the start frame request tothe eq::Config.

If a tracker is used, the current head position and orientation is retrieved andpassed to Equalizer, which uses the head matrix together with the wall or projectiondescription to compute the view frustra15.

The camera position is updated and the frame data is commited, which generatesa new version of this object. This version is passed to the rendering callbacks andwill be used by the rendering threads to synchronize the frame data to the statebelonging to the current frame:u in t 32 t Config : : startFrame ( ){

// update head po s i t i oni f ( t r a c k e r . isRunning ( ) ){

t r a c k e r . update ( ) ;const vmml : : Matr ix4f& headMatrix = t r a c k e r . getMatrix ( ) ;setHeadMatrix ( headMatrix ) ;

}

// update databaseframeData . data . r o t a t i on . preRotateX ( −0.001 f * spinX ) ;frameData . data . r o t a t i on . preRotateY ( −0.001 f * spinY ) ;

const u in t 32 t ve r s i on = frameData . commit ( ) ;

return eq : : Conf ig : : startFrame ( ve r s i on ) ;}

5.4.3 Event Handling

Events are sent by the render clients to the application using eq::Config::sendEvent.At the end of the frame, Config::finishFrame calls Config::handleEvents to do theevent handling. The default implementation processes all pending events by callingConfig::handleEvent for each of them.15see http://www.equalizergraphics.com/documents/design/immersive.html

15

Page 20: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

For event-driven execution, the application can override Config::handleEvents toblockingly receive events using Config::nextEvent until a new frame has to be ren-dered.

The eqPly example continuously renders new frames. It implements Config::hand-leEvent to provide the various reactions to user input, most importantly cameraupdates based on mouse events. The camera position has to be handled correctlywith respect to latency, and is therefore saved in the frame data:

bool Config : : handleEvent ( const eq : : ConfigEvent* event ){

switch ( event−>type ){

[ . . . ]case eq : : ConfigEvent : : POINTER MOTION:

i f ( event−>pointerMotion . buttons == eq : :PTR BUTTON NONE )return true ;

i f ( event−>pointerMotion . buttons == eq : :PTR BUTTON1 ){

spinX = 0 ;spinY = 0 ;

frameData . data . r o t a t i on . preRotateX (−0.005 f * event−>pointerMotion . dx ) ;

frameData . data . r o t a t i on . preRotateY (−0.005 f * event−>pointerMotion . dy ) ;

}else i f ( event−>pointerMotion . buttons == eq : :PTR BUTTON2 | |

event−>pointerMotion . buttons == ( eq : :PTR BUTTON1 |eq : :PTR BUTTON3 ) )

{frameData . data . t r a n s l a t i o n . z +=

.005 f * event−>pointerMotion . dy ;}else i f ( event−>pointerMotion . buttons == eq : :PTR BUTTON3 ){

frameData . data . t r a n s l a t i o n . x +=.0005 f * event−>pointerMotion . dx ;

frameData . data . t r a n s l a t i o n . y −=.0005 f * event−>pointerMotion . dy ;

}return true ;

default :break ;

}return eq : : Conf ig : : handleEvent ( event ) ;

}

5.5 Node

For each active render client, one eq::Node instance is created on the appropriatemachine. Nodes are only instantiated on their render client processes, i.e., eachprocess should only have one instance of the eq::Node class. The application processmight also have a node class, which is handled in exactly the same way as the renderclient nodes.

During node initialization the init data is mapped to a local instance using theidentifier passed from Config::init. The model is loaded based on the filename in theinitialization data. No pipe, window or channel tasks methods are executed beforeNode::configInit has returned.

bool Node : : c o n f i g I n i t ( const u in t 32 t i n i t ID ){

16

Page 21: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

eq : : Conf ig * c on f i g = getConf ig ( ) ;const bool mapped = con f ig−>mapObject ( & in i tData , i n i t ID ) ;EQASSERT( mapped ) ;

const s t r i n g& f i l ename = in i tDa ta . getFi lename ( ) ;EQINFO << ”Loading model ” << f i l ename << endl ;

model = new Model ( ) ;i f ( ! model−>readFromFile ( f i l ename . c s t r ( ) ) ){

EQWARN << ”Can ’ t load model : ” << f i l ename << endl ;delete model ;model = 0 ;

}

return eq : : Node : : c o n f i g I n i t ( i n i t ID ) ;}

The node config exit function deletes the loaded model and unmaps the initial-ization data:

bool Node : : c on f i gEx i t ( ){

delete model ;model = NULL;

eq : : Conf ig * c on f i g = getConf ig ( ) ;con f i g−>unmapObject ( & in i tDa ta ) ;

return eq : : Node : : c on f i gEx i t ( ) ;}

5.5.1 Frame Control

The application has extended control over the task synchronization during a frame.Upon Config::startFrame, Equalizer invokes the frameStart task methods of thevarious entities. The entities unlock all its children by calling startFrame, e.g.,Node::frameStart has to call Node::startFrame in order to unlock the pipe threads.Note that certain startFrame calls, e.g., Window::startFrame, are currently emptysince the synchronization is implicit due to the sequential execution within thethread.

Likewise, Config::finishFrame causes the invokation of the frameFinish task meth-ods. These task methods unlock their parents by calling releaseFrame.

The explicit synchronization of child or parent resources allows the application tooptimize the processing, by performing certain, independent operations when thechild or parent resources are already unlocked.

The default thread synchronization synchronizes all Channel::frameDraw opera-tions on a single node with the node’s main thread. This facilitates porting, sincethe scene database does not have to be multi-buffered as all threads of the nodeprocess render the same frame.

The per-node frame synchronization does not break the asynchronous executionacross rendering nodes. Advanced applications can remove per-node frame synchro-nization.

The per-node frame synchronization is achieved through the startFrame�wait-FrameStarted and releaseFrameLocal�waitFrameLocal synchronization points. Notethat this synchronization is only per-node, different nodes in the cluster still runasynchronously.

The first synchronization point ensures that the application (node) thread isdone modifiying the data, typically during event processing. The pipe threads call

17

Page 22: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Node::waitFrameStarted which blocks until the node calls startFrame in Node::frame-Start.

The second pair ensures that after Config::finishFrame all pipe threads are donerendering the current frame, and therefore do not access shared data. At this pointthe application thread can safely modify scene data. The node’s frameDrawFinishwaits for all local pipes to release the synchronization by calling Pipe::releaseFra-meLocal, which happens by default in Pipe::frameDrawFinish. The frameDrawFinishmethods are called after all Channel::frameDraw of the corresponding thread havebeen executed.

Pipe(s)Application Node

Config::startFrame

Pipe(s)

Node::frameStart

Server

Config::finishFrame

Render ClientApplication

Node::startFrameNode::frameStart

Node::startFrame Pipe::frameStartConfig::beginFrame

Pipe::frameStart

Pipe::startFrame

Node::frameFinishNode::releaseFrame

Pipe::frameFinishPipe::releaseFrame

Node::waitFrameStarted

Node::frameDrawFinishPipe(s)::waitFrameLocal

draw tasks

Pipe::frameDrawFinishPipe::releaseFrameLocal

assemble tasks

Pipe::startFrameNode::waitFrameStarted

Pipe::frameFinishPipe::releaseFrame

draw tasks

Pipe::frameDrawFinishPipe::releaseFrameLocal

assemble tasks

Node::frameFinishNode::releaseFrame

Node::frameDrawFinishPipe(s)::waitFrameLocal

Figure 9: Synchronization of frame tasks

Figure 9 outlines the synchronization for the application, node and pipe classes.Please note that Config::finishFrame does block until the current frame has beenreleased locally and until the frame current - latency has been released by all nodes.The window and channel synchronization are similar and omitted for simplicity.

It is absolutely vital for the execution that Node::startFrame and Node::releaseFra-me are called, respectively. The default implementation of the node task methodsdoes take care of that.

Since eqPly multi-buffers all dynamic data, it can completely remove frame syn-chronization by:

� releasing the local synchronization early in Node::frameStart

� not waiting for the node to start the frame by not calling Node::waitFrameStartedin Pipe::frameStart

� not waiting for the pipe synchronization in Node::frameDrawFinish

Releasing the local synchronization early is done by calling releaseFrameLocal,and Node::frameDrawFinish is overwritten with an empty implementation:void Node : : frameDrawFinish ( const u in t 32 t frameID ,

const u in t 32 t frameNumber ){ /* nop , see frameStart */ }

void Node : : f rameStart ( const u in t 32 t frameID , const u in t 32 t frameNumber ){

startFrame ( frameNumber ) ; // unlock pipe threads

// Don ’ t wai t f o r p ipes to r e l e a s e frame l o c a l l y , sync not needed s ince a l l// dynamic data i s mult i−bu f f e r e dre leaseFrameLoca l ( frameNumber ) ;

}

18

Page 23: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

5.6 Pipe

All task methods for a pipe and its children are executed in a separate thread.This approach optimizes usage of the GPU, since all tasks are executed serially andtherefore do not compete for resources or cause OpenGL context switches. Laterversions of Equalizer might introduce threaded windows to allow the parallel andindependent execution of rendering tasks on a single pipe.

5.6.1 Initialization and Exit

Pipe threads are not explicitely synchronized with each other, that is, pipes mightbe rendering different frames at any given time. Therefore frame-specific data hasto be allocated for each pipe thread, which in the eqPly example is the frame data.The frame data is a member variable of the eqPly::Pipe, and is mapped to theidentifier provided by the initialization data. The initialization in eq::Pipe doesthe GPU-specific initialization, which is window-system-dependent. On AGL thedisplay ID is determined, and on glX the display connection is opened.

bool Pipe : : c o n f i g I n i t ( const u in t 32 t i n i t ID ){

const Node* node = static cast<Node*>( getNode ( ) ) ;const In i tData& in i tData = node−>get In i tData ( ) ;const u in t 32 t frameDataID = in i tData . getFrameDataID ( ) ;eq : : Conf ig * c on f i g = getConf ig ( ) ;

const bool mapped = con f ig−>mapObject ( & frameData , frameDataID ) ;EQASSERT( mapped ) ;

return eq : : Pipe : : c o n f i g I n i t ( i n i t ID ) ;}

The config exit function is similar to the config initialization. The frame data isunmapped and GPU-specific data is de-initialized by eq::Config::exit:

bool Pipe : : c on f i gEx i t ( ){

eq : : Conf ig * c on f i g = getConf ig ( ) ;con f i g−>unmapObject ( & frameData ) ;

return eq : : Pipe : : c on f i gEx i t ( ) ;}

5.6.2 Window System

Equalizer supports multiple window system interfaces, at the moment glX/X11,WGL and AGL/Carbon. Some operating systems, and therefore some Equalizerversions, support multiple window systems concurrently16.

Each pipe might use a different window system for rendering, which is determinedbefore Pipe::configInit by Pipe::selectWindowSystem. The default implementation ofselectWindowSystem loops over all window systems and returns the first supportedwindow system, determined by using supportsWindowSystem.

The eqPly examples allows selecting the window system using a command lineoption. Therefore the implementation of selectWindowSystem is overwritten andreturns the specified window system, if supported:

eq : : WindowSystem Pipe : : selectWindowSystem ( ) const{

const Node* node = static cast<Node*>( getNode ( ) ) ;const In i tData& in i tData = node−>get In i tData ( ) ;const eq : : WindowSystem ws = in i tData . getWindowSystem ( ) ;

16see http://www.equalizergraphics.com/compatibility.html

19

Page 24: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

i f ( ws == eq : :WINDOW SYSTEM NONE )return eq : : Pipe : : selectWindowSystem ( ) ;

i f ( ! supportsWindowSystem ( ws ) ){

EQWARN << ”Window system ” << ws<< ” not supported , us ing d e f au l t window system” << endl ;

return eq : : Pipe : : selectWindowSystem ( ) ;}

return ws ;}

5.6.3 Carbon/AGL Thread Safety

Parts of the Carbon API used for window and event handling in the AGL windowsystem are not thread safe. The application has to call eq::Global::enterCarbon be-fore any thread-unsafe Carbon call, and eq::Global::leaveCarbon afterwards. Thesefunctions should be used only during window initialization and exit, not during ren-dering. For various reasons enterCarbon might block up to 50 milliseconds. Carboncalls in the window event handling routine Window::processEvent are thread-safe,since the global carbon lock is set in this method. Please contact the Equalizerdeveloper mailing list if you need to use Carbon calls on a per-frame basis.

5.6.4 Frame Control

All task methods for a given frame of the pipe, window and channel entities be-longing to the thread are executed in one block, starting with Pipe::frameStart andfinished by Pipe::finishFrame. The frame start callback is therefore the natural placeto update all frame-specific data to the version belonging to the frame.

In eqPly, the version of the only frame-specific object FrameData is passed as theper-frame id from Config::startFrame to the frame task methods. The pipe uses thisversion to update its instance of the frame data to the current version, and thenunlocks its child entities by calling startFrame:

void Pipe : : f rameStart ( const u in t 32 t frameID , const u in t 32 t frameNumber ){

// don ’ t wai t f o r node to s t a r t frame , l o c a l sync not needed// node−>waitFrameStarted ( frameNumber ) ;frameData . sync ( frameID ) ;

startFrame ( frameNumber ) ;}

5.7 Window

The Equalizer window holds an OpenGL drawable and a rendering context. Whenusing the default window initialization functions, all windows of a pipe share theOpenGL context. This allows reuse of OpenGL objects such as display lists andtextures between all windows of one pipe.

The window class is the natural place for the application to maintain all dataspecific to the OpenGL context.

5.7.1 Initialization and Exit

The initialization sequence uses multiple, overrideable task methods. The main taskmethod configInit executes a ‘child’ task method to create the drawable and context.The child task method depends on the window system of the pipe. The default im-plementations of configInitGLX, configInitWGL or configInitAGL create an on-screen

20

Page 25: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

window using OS-specific methods. If the OpenGL drawable and context were cre-ated successfully, configInit calls configInitGL, which performs the generic OpenGLstate initialization. The default implementation sets up some typical OpenGL state,e.g., it enables the depth test.

Start

configInit

window-system

configInitGLX configInitWGL configInitAGL

GLX WGL AGL

failed?

configInitGL

success

failed?

failure

no

yes

yes

no

Figure 10: Window Initialization

Figure 10 shows a flow chart of the window ini-tialization. The colored functions are task meth-ods and can be replaced by application-specificimplementations.

The window-system specific initialization takesinto account various attributes set in the con-figuration file. Attributes include the size ofvarious frame buffer attachments (color, alpha,depth, stencil) as well as other framebuffer prop-erties, such as quad-buffered stereo, doublebuffer-ing, fullscreen mode and window decorations.Some of the attributes, such as stereo, double-buffer and stencil can be set to eq::AUTO, inwhich case Equalizer will test for their availabilityand enable them if possible.

The eqPly window initialization function firstcalls eq::Window::configInit to use the generic win-dow setup. If that was successful, it initializes astate object and an overlay logo:

bool Window : : c o n f i g I n i t ( const u in t 32 t i n i t ID ){

i f ( ! eq : : Window : : c o n f i g I n i t ( i n i t ID ) )return fa l se ;

eq : : Pipe* pipe = getPipe ( ) ;Window* f irstWindow = static cast< Window* >( pipe−>getWindow( 0 ) ) ;

EQASSERT( ! s t a t e ) ;

i f ( f irstWindow == this ){

s t a t e = new VertexBuf f e rState ( getGLFunctions ( ) ) ;loadLogo ( ) ;

const Node* node = static cast< const Node* >( getNode ( ) ) ;const In i tData& in i tData = node−>get In i tData ( ) ;

i f ( in i tData . useVBOs ( ) ){

const eq : : GLFunctions* g lFunct ions = getGLFunctions ( ) ;// Check i f a l l VBO funcs a va i l a b l e , e l s e l e ave DISPLAY LIST MODE oni f ( g lFunct ions−>hasGenBuffers ( ) && glFunct ions−>hasBindBuffer ( ) &&

glFunct ions−>hasBufferData ( ) && glFunct ions−>hasDe l e t eBu f f e r s ( ) ){

s t a t e−>setRenderMode ( mesh : :BUFFER OBJECT MODE ) ;EQINFO << ”VBO render ing enabled ” << endl ;

}else

EQWARN << ”VBO func t i on po i n t e r s miss ing , us ing d i sp l ay l i s t s ”<< endl ;

}}else{

s t a t e = firstWindow−> s t a t e ;l ogoTexture = firstWindow−> l ogoTexture ;l o g oS i z e = firstWindow−> l o g oS i z e ;

21

Page 26: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

}

i f ( ! s t a t e ) // happens i f f i r s t window f a i l e d to i n i t i a l i z ereturn fa l se ;

return true ;}

The state object is used to handle the creation of OpenGL objects in a mul-tipipe, multi-threaded execution environment. It uses an object manager, whichis described in detail in Section 5.7.2. It is used in conjunction with a reference-counting smart-pointer here, since it is potentially ‘owned’ by multiple windows atthe same time.

The logo texture is loaded from the file system and bound to a texture ID usedlater by the channel for rendering. A code listing is ommitted, since the codeconsists of standard OpenGL calls and is not Equalizer-specific.

The window exit function de-allocates all OpenGL objects when the state objectis about to be disposed. The object manager does not delete the object in its de-structor, since it does not know if an OpenGL context is still current. Additionally,eq::Window::configExit is called to destroy the drawable and context:

bool Window : : c on f i gEx i t ( ){

i f ( s t a t e . i sVa l i d ( ) && s ta t e−>getRefCount ( ) == 1 )s t a t e−>d e l e t eA l l ( ) ;

s t a t e = 0 ;return eq : : Window : : c on f i gEx i t ( ) ;

}

5.7.2 Object Manager

The object manager is not strictly a part of the window. It is mentioned here sincethe eqPly window uses an object manager.

The state object in eqPly gathers all rendering state, which includes an objectmanager for OpenGL object allocation.

The object manager (OM) is a utility class and can be used to manage OpenGLobjects across shared contexts. Typically one OM is used for each set of sharedcontexts and spawns all contexts of a single GPU17.

The OM is a template class. The template type is the key used to identify objects.The same key is used by all contexts to get the OpenGL name of an object. In eqPly,a key of type const void * is used. The rendering code uses the address of the dataitem to be rendered as the key to obtain the associated OpenGL object.

All objects managed by the OM are reference counted. If an application releasesthe objects properly, they are automatically de-allocated. It is also possible tomanually manage de-allocation of objects, which might be more convenient in somecases.

Currently, support for display lists, VBO’s, textures and shaders is implemented.For each object, the following functions are available:

supportsObjects() returns true if the usage for this particular type of objects issupported. For objects available in OpenGL 1.1 or earlier, this function is notimplemented.

getObject( key ) returns the object associated with the given key, or FAILED.Increases the reference count of existing objects.

17http://www.equalizergraphics.com/documents/design/objectManager.html

22

Page 27: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

newObject( key ) allocates a new object for the given key. Returns FAILED if theobject already exists or if the allocation failed. Sets the reference count of anewly created object to one.

obtainObject( key ) convenience function which gets or obtains the object associ-ated with the given key. Returns FAILED only if the object allocation failed.

releaseObject( key | name ) decreases the reference count and deletes the objectif the reference count reaches zero.

deleteObject( key | name ) manually deletes the object. To be used if referencecounting is not used.

5.8 Channel

The channel is the heart of the application in that it contains the actual renderingcode. The channel is used to perform the various rendering operations for thecompounds.

5.8.1 Initialization and Exit

During channel initialization, the near and far planes are set to reasonable valuesto contain the whole model. During rendering, the near and far planes are adjusteddynamically to the current model position:

bool Channel : : c o n f i g I n i t ( const u in t 32 t i n i t ID ){

setNearFar ( 0 . 1 f , 10 .0 f ) ;return true ;

}

5.8.2 Rendering

The central rendering routine is Channel::frameDraw. This routine contains theapplication’s OpenGL rendering code, which is being rendered using the contextualinformation provided by Equalizer. As most of the other task methods, frameDrawis called in parallel by Equalizer on all pipe threads in the configuration. Thereforethe rendering must not write to shared data, which is the case for all major scenegraph implementations.

In eqPly, the OpenGL context is first set up using various apply conveniencemethods from the base Equalizer channel class. Each of the apply methods uses thecorresponding get method(s) and then calls the appropriate OpenGL function(s).It is also possible to just query the values from Equalizer using the get methods,and use them to set up the OpenGL state appropriatly, for example by passing theparameters to the renderer used by the application.

For example, the implementation for eq::Channel::applyBuffer does set up the cor-rect rendering buffer and color mask, which depends on the current eye pass andpossible anaglyphic stereo parameters:

void eq : : Channel : : app lyBuf fe r ( ){

glReadBuf fer ( getReadBuffer ( ) ) ;glDrawBuffer ( getDrawBuffer ( ) ) ;

const ColorMask& colorMask = getDrawBufferMask ( ) ;glColorMask ( colorMask . red , colorMask . green , colorMask . blue , true ) ;

}

23

Page 28: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

The contextual information has to be used in order to render the view as expectedby Equalizer. Failure to use certain information will result in incorrect renderingfor some or all configurations. The channel render context consist of:

Buffer The OpenGL read and draw buffer as well as color mask. These parametersare influenced by the current eye pass, eye separation and anaglyphic stereosettings.

Viewport The two-dimensional pixel viewport restricting the rendering area withinthe channel. For correct operations, both glViewport and glScissor have to beused. The pixel viewport is influenced by the destination channel’s viewportdefinition and compound viewports set for sort-first/2D decompositions.

Frustum The same frustum parameters as defined by glFrustum. Typically thefrustum used to set up the OpenGL projection matrix. The frustum is influ-enced by the destination channel’s view definition, compound viewports, headmatrix and the current eye pass.

Head Transformation A transformation matrix positioning the frustum. This istypically an identity matrix and is used for off-axis frustra in immersive ren-dering. It is normally used to set up the ‘view’ part of the modelview matrix,before static light sources are defined.

Range A one-dimensional range with the interval [0..1]. This parameter is optionaland should be used by the application to render only the appropriate subsetof its data. It is influenced by the compound range attribute.

The frameDraw method in eqPly calls the frameDraw method from the parent class,the Equalizer channel. The default frameDraw method uses the apply conveniencefunctions to setup the OpenGL state for all render context information, with theexception of the range which will be used later during rendering:void eq : : Channel : : frameDraw ( const u in t 32 t frameID ){

app lyBuf f e r ( ) ;applyViewport ( ) ;

glMatrixMode ( GL PROJECTION ) ;g lLoadIdent i ty ( ) ;applyFrustum ( ) ;

glMatrixMode ( GL MODELVIEW ) ;g lLoadIdent i ty ( ) ;applyHeadTransform ( ) ;

}

void Channel : : frameDraw ( const u in t 32 t frameID ){

// Setup OpenGL s t a t eeq : : Channel : : frameDraw ( frameID ) ;

Figure 11: Destination view of anDB compound

After the basic view setup, a directional lightis configured, and the model is positioned usingthe camera parameters from the frame data. Thecamera parameters are transported using the theframe data to ensure that all channels render agiven frame using the same position.

Furthermore, a white color is set in case themodel does not contain color information, or thecolor information is not used. In sort-last render-ing, eqPly uses a different color for each channel

24

Page 29: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

to illustrate the database decomposition, as shown in Figure 11. The Equalizerchannel provides a method to obtain a random, but unique color for all channelsin the configuration. This color is determined by the server to ensure uniquenessacross all channels of the configuration:

g lL i gh t f v ( GL LIGHT0 , GL POSITION, l i g h tP o s i t i o n ) ;g lL i gh t f v ( GL LIGHT0 , GL AMBIENT, l ightAmbient ) ;g lL i gh t f v ( GL LIGHT0 , GL DIFFUSE, l i g h tD i f f u s e ) ;g lL i gh t f v ( GL LIGHT0 , GL SPECULAR, l i g h tSp e cu l a r ) ;

g lMa t e r i a l f v ( GL FRONT, GL AMBIENT, materialAmbient ) ;g lMa t e r i a l f v ( GL FRONT, GL DIFFUSE, ma t e r i a lD i f f u s e ) ;g lMa t e r i a l f v ( GL FRONT, GL SPECULAR, mate r i a lSpecu la r ) ;g lMa t e r i a l i ( GL FRONT, GL SHININESS , mat e r i a l Sh i n i n e s s ) ;

const Pipe* pipe = static cast<Pipe*>( getPipe ( ) ) ;const FrameData& frameData = pipe−>getFrameData ( ) ;

g lT r an s l a t e f ( frameData . data . t r a n s l a t i o n . x ,frameData . data . t r a n s l a t i o n . y ,frameData . data . t r a n s l a t i o n . z ) ;

g lMultMatr ixf ( frameData . data . r o t a t i on . ml ) ;

Node* node = (Node*) getNode ( ) ;const Model* model = node−>getModel ( ) ;const eq : : Range& range = getRange ( ) ;

i f ( ! range . i s F u l l ( ) ) // Color DB−patches{

const vmml : : Vector3ub co l o r = getUniqueColor ( ) ;g lColor3ub ( c o l o r . r , c o l o r . g , c o l o r . b ) ;

}else i f ( ! frameData . data . c o l o r | | ( model && ! model−>hasColors ( ) ) ){

g lCo l o r 3 f ( 1 . 0 f , 1 . 0 f , 1 . 0 f ) ;}

Finally the model, which has been loaded by the node, is rendered. If the modelwas not loaded during node initialization, a quad is drawn in its place:

i f ( model ){

drawModel ( model ) ;}else{

g lCo l o r 3 f ( 1 . f , 1 . f , 0 . f ) ;g lNormal3f ( 0 . f , −1. f , 0 . f ) ;g lBeg in ( GL TRIANGLE STRIP ) ;g lVe r t ex3 f ( . 25 f , 0 . f , . 25 f ) ;g lVe r t ex3 f ( . 25 f , 0 . f , −.25 f ) ;g lVe r t ex3 f ( −.25 f , 0 . f , . 25 f ) ;g lVe r t ex3 f ( −.25 f , 0 . f , −.25 f ) ;glEnd ( ) ;

}}

In order to draw the model, a helper class for view frustum culling is set up usingthe view frustum from Equalizer and the camera position from the frame data. Thefrustum helper computes the six frustum planes from the projection and modelViewmatrices. During rendering, the bounding spheres of the model are tested againstthese planes to determine the visibility with the frustum.

Furthermore, the render state from the window and the database range from thechannel is obtained. The render state manages display list or VBO allocation:void Channel : : drawModel ( const Model* model ){

25

Page 30: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

Window* window = static cast<Window*>( getWindow ( ) ) ;mesh : : Ver texBuf f e rState& s t a t e = window−>ge tSta t e ( ) ;

const Pipe* pipe = static cast<Pipe*>( getPipe ( ) ) ;const FrameData& frameData = pipe−>getFrameData ( ) ;

const eq : : Range& range = getRange ( ) ;vmml : : FrustumCul ler f c u l l e r ;

s t a t e . s e tCo l o r s ( frameData . data . c o l o r &&range . i s F u l l ( ) &&model−>hasColors ( ) ) ;

in i tFrustum ( cu l l e r , model−>getBoundingSphere ( ) ) ;

model−>beginRendering ( s t a t e ) ;

The model data is spatially organized in an 3-dimensional kD-tree18 for efficientview frustum culling. When the model is loaded by Node::configInit, it is prepro-cessed into the kD-tree and each node of the tree gets a database range assigned.The root node has the range [0, 1], its left child [0, 0.5] and its right child [0.5, 1],and so on for all nodes in the tree. The preprocessed model is saved in a binaryformat for accelerating subsequent use.

setup render state

add root node to candidates

candidates empty?

get candidate

no

fully visible?

no

partially visible?

no

cleanup render stateyes

yesrender candidate

yesadd children to

candidates

Start

Stop

fully in range?yes

has children?yes

no

in range?yes

no

no

render candidate

Figure 12: Main Rendering Loop

The rendering loop main-tains a list of candidates torender, which initially con-tains the root node. Eachcandidate of this list is testedfor full visiblity against thefrustum and range, and ren-dered if visible. It is droppedif it is fully invisible or fullyout of range. If it is partiallyvisible or partially in range,the children of the node areadded to the candidate list.Figure 12 shows a flow chartof the rendering algorithm,which performs efficient viewfrustum and range culling.

The actual rendering usesdisplay lists or vertex bufferobjects. These OpenGL ob-jects are allocated using theobject manager. The render-ing is done by the leaf nodes,which are small enough tostore the vertex indices in a short value for optimal performance with VBO’s. Theleaf nodes reuse the objects stored in the object manager, or create and set up newobjects if it was not yet set up. Since one object manager is used per thread (pipe),this allows a thread-safe sharing of the compiled display lists or VBO’s across allwindows of a pipe.

The main rendering loop in eqPly looks like this:

model−>beginRendering ( s t a t e ) ;

// s t a r t with root nodevector< const VertexBufferBase * > cand idate s ;

18See also http://en.wikipedia.org/wiki/Kd-tree

26

Page 31: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

cand idate s . push back ( model ) ;

while ( ! cand idate s . empty ( ) ){

const VertexBufferBase * treeNode = cand idate s . back ( ) ;cand idate s . pop back ( ) ;

// comple te l y out o f range checki f ( treeNode−>getRange ( ) [ 0 ] >= range . end | |

treeNode−>getRange ( ) [ 1 ] < range . s t a r t )continue ;

// bounding sphere view frustum c u l l i n gswitch ( c u l l e r . t e s tSphere ( treeNode−>getBoundingSphere ( ) ) ){

case vmml : : VISIBILITY FULL :// i f f u l l y v i s i b l e and f u l l y in range , render i ti f ( treeNode−>getRange ( ) [ 0 ] >= range . s t a r t &&

treeNode−>getRange ( ) [ 1 ] < range . end ){

treeNode−>render ( s t a t e ) ;break ;

}// p a r t i a l range , f a l l through to p a r t i a l v i s i b i l i t y

case vmml : : VISIBILITY PARTIAL :{

const VertexBufferBase * l e f t = treeNode−>ge tLe f t ( ) ;const VertexBufferBase * r i g h t = treeNode−>getRight ( ) ;

i f ( ! l e f t && ! r i g h t ){

i f ( treeNode−>getRange ( ) [ 0 ] >= range . s t a r t )treeNode−>render ( s t a t e ) ;

// e l s e drop , to be drawn by ’ prev ious ’ channel}else{

i f ( l e f t )cand idate s . push back ( l e f t ) ;

i f ( r i g h t )cand idate s . push back ( r i g h t ) ;

}break ;

}case vmml : : VISIBILITY NONE :

// do nothingbreak ;

}}

model−>endRendering ( s t a t e ) ;}

6 Advanced Features

This section discusses some additional important features not covered by the pre-vious eqPly section. Where possible code examples from the Equalizer distributionare used.

6.1 Event Handling

Event handling requires a lot of flexibility. On one hand, the implementation differsslightly for each operating and window system due to conceptual differences in the

27

Page 32: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

OS-specific implementation. On the other hand, each application and widget sethas its own model on how events are to be handled. Therefore, event handling inEqualizer is customizable at any stage of the processing, to the extreme of makingit possible to disable all event-related code in Equalizer. In this aspect, Equalizersubstantially differs from GLUT, which imposes an event model and hides most ofthe event handling in glutMainLoop. More information on event handling can befound on the Equalizer website19.

The default implementation provides a convenient, easily accessible event frame-work, while allowing all necessary customizations. It gathers all events in themain thread of the application, so that the developer only has to implement Con-fig::processEvent to update its data based on the pre-processed, generic keyboardand mouse events. It is very easy to use and similar to an GLUT-based implemen-tation.

6.1.1 Threading

Where possible events are received and processed by a separate per-node eventthread to allow asynchronous20 event handling. Currently an event thread is onlyused by the X11/glX window system. WGL receives and processes the events fromthe pipe threads that created the windows. AGL receives the events from applicationor node main thread. Whenever the term event thread is used, it refers to thethread receiving the event, such as a per-node thread for glX, the pipe thread forWGL and the main thread for AGL.

6.1.2 Initialization and Exit

config initpipe init

get event thread

ApplicationPipe Thread(s)

Event Threadadd pipe to

event threadopen pipe

window init

add window to event thread

listen for window events

Figure 13: Event Handling Ini-tialization

During window and pipe initialization the eventhandling is set up. For both entities, initEvent-Handler is called to register the pipe or windowwith an event handler. This method may be over-written to use a custom event handler, or to notinstall an event handler at all and so disable eventhandling. Likewise, exitEventHandler is called tode-initialize event handling.

An event handler consists of two parts: thegeneric base class providing the interface andgeneric functions, and the window-system-specificpart providing the actual implementation.

Event handling is initialized whenever a newwindow-system-specific pipe or window handle is set. First, exitEventHandler iscalled to de-initialize event handling for the old handle (if set), and then initEvent-Handler is called for the new handle.

AGL and glX use an event handler singleton, whereas WGL uses one event handlerper window. Pipe event handling is only used for glX, where one Display connectionis created and used to subscribe to window events.

6.1.3 Message Pump

For the WGL and AGL window systems, it is required to manually receive and dis-patch (‘pump’) events. On WGL, this has to happen on each thread with windows,whereas on AGL it has to happen only on the main thread. By default, Equalizerpumps these events automatically for the application.

19see http://www.equalizergraphics.com/documents/design/eventHandling.html20with respect to the rendering

28

Page 33: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

The methods Client::useMessagePump and Pipe::useMessagePump can be over-ridden to return false to disable this behaviour for their respective threads. Onnon-threaded pipes, Pipe::useMessagePump is not called.

If the application disables message pumping in Equalizer, it has to make sure theevents are pumped externally, as it often done within external widget sets such asQt.

6.1.4 Event Data Flow

OS Event

EventHandler

WindowEvent

ConfigEvent

OS Event

Window::processEvent

Config::sendEvent

Event Thread App Thread

Config::handleEvents

Config::handleEvent

EventQueue

ConfigEvent

Config::finishFrame

Figure 14: Event Processing

Events are received by an event handler. Theevent handler finds the eq::Window associated tothe event. It then creates a generic WindowEvent,which holds important event data in a windowsystem-independent format. The original eventis attached to the generic window event.

The event handler then passes the windowevent to Window::processEvent, which is respon-sible for either handling the event locally, or fortranslating it into a generic ConfigEvent. The con-fig events are send to the application thread usingConfig::sendEvent.

If the event was processed by processEvent,the function has to return true. If false is re-turned, the event will be passed to a previouslyinstalled, window-system-specific event handlingfunction. The default implementation of Win-dow::processEvent passes most events on to theapplication.

Events sent using Config::sendEvent are queued in the application thread. After aframe has been finished, Config::finishFrame calls Config::handleEvents. The defaultimplementation of this method provides non-blocking event processing, that is, itcalls Config::handleEvent for each queued event. By overriding handleEvents, event-driven execution can be implemented.

Later Equalizer versions will introduce Pipe::processEvent and PipeEvent to com-municate pipe-specific events, e.g., monitor resolution changes.

6.1.5 Custom Events in eqPixelBench

The eqPixelBench example is a benchmark program to measure the pixel transferrates from and to the framebuffer of all channels within a configuration. It usescustom config events to send the gathered data to the application. It is muchsimpler than the eqPly example since it does not provide any useful rendering oruser interaction.

The rendering routine of eqPixelBench in Channel::frameDraw loops through anumber of pixel formats and types. For each of them, it measures the time toreadback and assemble a full-channel image. The format, type, size and time isrecorded in a config event, which is sent to the application.

The ConfigEvent derives from the eq::ConfigEvent structure and has the followingdefinition:

struct ConfigEvent : public eq : : ConfigEvent{public :

enum Type{

READBACK = eq : : ConfigEvent : : USER,

29

Page 34: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

ASSEMBLE} ;

ConfigEvent ( ){

s i z e = s izeof ( ConfigEvent ) ;}

// channel name i s in user event datachar formatType [ 6 4 ] ;vmml : : Vector2 i area ;f loat msec ;

} ;

The Config::sendEvent method transmits an eq::ConfigEvent or derived class tothe application. The ConfigEvent has to be a C-type structure, and its size memberhas to be set to the full size of the event to be transmitted. Each event has a typewhich is used to identify it by the config processing function.

User-defined types start at eq::ConfigEvent::USER, and the member variable Con-figEvent::user can be used to store EQ USER EVENT SIZE21 bytes. In this space,the channel’s name is stored. Additional variables are used to transport the pixelformat and type, the size and the time it took for rendering.

On the application end, Config::handleEvent uses the ostream operator for thederived config event to output these events in a nicely formatted way:

std : : ostream& operator << ( std : : ostream& os , const ConfigEvent* event ) ;. . .bool Config : : handleEvent ( const eq : : ConfigEvent* event ){

switch ( event−>type ){

case ConfigEvent : :READBACK:case ConfigEvent : :ASSEMBLE:

cout << static cast< const ConfigEvent* >( event ) << endl ;return true ;

default :return eq : : Conf ig : : handleEvent ( event ) ;

}}

6.2 Image Compositing for Scalable Rendering

Two task methods are responsible for collecting and compositing the result imageduring scalable rendering. Scalable rendering is a use case of parallel rendering,when multiple channels are contributing to a single view.

The source channels producing one or more outputFrames use Channel::frame-Readback to read the pixel data from the frame buffer. The channels receiving oneor multiple inputFrames use Channel::frameAssemble to assemble the pixel data intothe framebuffer. Equalizer takes care of the network transport of frame buffer databetween nodes, if needed.

Normally the programmer does not need to interfere with the image composit-ing. Changes are sometimes required at a high level, for example to order theinput frames or to optimize the readback. The following sections provide a detaileddescription of the image compositing API in Equalizer.

21currently 32 bytes

30

Page 35: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

6.2.1 Parallel Direct Send Compositing

In order to provide a motivation for the design of the image compositing API, thedirect send parallel compositing algorithm is introduced in this section.

read

-bac

k

send/receivecolor + depth

asse

mbl

e/re

ad-b

ack

asse

mbl

e

source 1(destination) source 2 source 3

send/receivecolor

Figure 15: Parallel Direct Send Compositing

The main idea for di-rect send is to parallelizethe costly recomposition fordatabase (sort-last) decom-position. With each ad-ditional source channel, theamount of pixel data tobe composited grows linearly.When using the simple ap-proach of compositing allframes on the destinationchannel, this channel quicklybecomes the bottleneck in thesystem. Direct send dis-tributes this workload evenlyacross all source channels,and thereby keeps the com-positing work per channelconstant.

In direct send compositing,each rendering channel is alsoresponsible for the sort-lastcomposition22 of one screen-space tile. He receives the framebuffer pixels for histile from all the other channels. The size of one tile decreases linearly with thenumber of source channels, which keeps the total amount of pixel data per channelconstant.

After performing the sort-last compositing, the color information is transferredto the destination channel, similarly to an 2D (sort-first) compound. The amountof pixel data for this part of the compositing pipeline also approaches a constantvalue, i.e., the full frame buffer.

Figure 15 illustrates this algorithm for three channels. The Equalizer websitecontains a presentation explaining this algorithm23.

The following operations have to be possible in order to perform this algorithm:

� Selection of frame buffer attachments: color and/or depth

� Restricting the read back area to a part of the rendered area

� Positioning the pixel data correctly on the receiving channels

Furthermore it should be possible for the application to implement a read backof only the relevant region of interest, that is, the 2D area of the framebuffer actu-ally updated during rendering. This optimization will be fully supported by laterversions of Equalizer.

6.2.2 Frame, Frame Data and Images

An eq::Frame references an eq::FrameData. The frame data is the object connectingoutput with input frames. Output and input frames with the same name withinthe same compound tree will reference the same frame data.22depth-based for polygonal data or sorted-blend for volume data23http://www.equalizergraphics.com/documents/EGPGV07.pdf

31

Page 36: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

The frame data is a holder for images and additional information, such as outputframe attributes and pixel data availability.

An eq::Image holds a two-dimensional snapshot of the framebuffer and can containcolor and/or depth information.

The frame synchronization through the frame data allows the input frame towait for the pixel data to become ready, which is signalled by the output frameafter readback.

Furthermore, the frame data transports the inherited range of the output frame’scompound. The range can be used to compute the assembly order of multiple inputframes, e.g., for sorted-blend compositing in volume rendering applications.

Readback and assemble operations on frames and images are designed to beasynchronous. They have a start and finish method for both readback and assem-ble to allow the initiation and synchronization of the operation. Currently, onlysynchronous readback and assembly using glReadPixels and glDrawPixels is imple-mented in the respective start method of the image. Later versions of Equalizerwill implement asynchronous pixel transfers.

Frame

FrameOffset

FrameData

FrameDataPVP

Image

Image

ImagePVP

Figure 16: Hierarchy of assembly classes

The offset of input and out-put frames characterizes theposition of the frame datawith respect to the frame-buffer, that is, the window’slower-left corner. For outputframes this is the position ofthe channel with respect tothe window.

For output frames, theframe data’s pixel viewport isthe area of the frame buffer toread back. It will transportthe offset from the source tothe destination channel, thatis, the frame data pixel view-port for input frames positionthe pixel data on the destina-tion. This has the effect thata partial framebuffer readback will end up in the same place in the destinationchannels.

The image pixel viewport signifies the region of interest that will be read back.The default readback operation reads back one image using the full pixel viewportof the frame data.

Figure 16 illustrates the relationship between frames, frame data and images.

6.2.3 Custom Assembly in eVolve

The eVolve example is a scalable volume renderer. It uses 3D texture-based volumerendering, where the 3D texture is intersected by view-aligned slices. The slices arerendered back-to-front and blended together to produce the final image, as shownin Figure 1724.

When using 2D (sort-first) or stereo decompositions, no special programming isneeded to achieve good scalability, as eVolve is mostly fill-limited and thereforescales nicely in this mode.

24Volume Data Set courtesy of: SFB-382 of the German Research Council (DFG)

32

Page 37: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

ViewDirection

Figure 17: Blending Slices in 3D-Texture-based VolumeRendering

The full power of scalable volume rendering ishowever in DB (sort-last) compounds, where thefull volume is divided into separate bricks. Eachof the bricks is rendered like a separate volume.For recomposition, the RGBA frame buffer dataresulting from these render passes then has to beassembled correctly.

Conceptually, the individual volume bricks ofeach of the source channels produces pixel datawhich can be handled like one big ’slice’ throughthe full texture. Therefore they have to be blen-ded back-to-front in the same way as the sliceplanes during rendering.

DB compounds have the advantage of scalingany part of the volume rendering pipeline: tex-ture and main memory (smaller bricks for eachchannel), fill rate (less samples per channel) andIO bandwidth for time-dependent data (less data per time step and channel). Sincethe amount of texture memory needed for each node decreases linearly, they makeit possible to render data sets which are not feasible to visualize with any otherapproach.

Back t

o Fron

t

Compo

siting

Zmodel

Zviewα

nearplane

(a)

1 2 3 4

(b)

Figure 18: Back-to-Front Compositing for Orthogonal and Perspective View Frustra

For recomposition, the 2D frame buffer contents are blended together to form aseamless picture. For correct blending, the frames are ordered in the same back-to-front order as the slices used for rendering, and use the same blending parameters.Simplified, the frame buffer images are ‘thick’ slices which are ‘rendered’ by writingtheir content to the destination frame buffer using the correct order.

For orthographic rendering, determining the compositing order of the input framesis trivial. The screen-space orientation of the volume bricks determines the orderin which they have to be composited. The bricks in eVolve are created by slic-ing the volume along one dimension. Therefore the range of the resulting framebuffer images, together with the sorting order, is used to arrange the frames duringcompositing. Figure 18(a) shows this composition for one view.

Finding the correct assembly order for perspective frustra is more complex. Theperspective distortion invalidates a simple orientation criteria like the one used for

33

Page 38: Equalizer Programming Guide - GitHub Pageseyescale.github.io/equalizergraphics.com/downloads/ProgrammingGuide-1.0.pdf · The application can also be a rendering client, in which case

orthographic frustra. For the view and frustum setup shown in Figure 18(b)25 thecorrect compositing order is 4-3-1-2 or 1-4-3-2.

Figure 19: Result of Figure 18(b)

In order to compute the assembly order, eVolveuses the angle between the origin→slice vectorand the near plane, as shown in Figure 18(b).When the angle becomes greater than 90°, thecompositing order of the remaining frames has tobe changed. The result image of this compositionnaturally looks the same as the volume renderingwould when rendered on a single channel. Fig-ure 19 shows the result of the composition fromFigure 18(b).

The assembly algorithm described in this sec-tion also works with parallel compositing algo-rithms such as direct-send.

6.3 Head Tracking

The eqPly example contains rudimentary support for head tracking, in order toshow how head tracking can be integrated with Equalizer. Support for a widerange of tracking devices is not within the scope of Equalizer. Other open sourceand commercial implementations cover this functionality sufficiently and can easilybe integrated with Equalizer.

x

wall definition from configuration file

near plane

far plane

(a)

x

z

(b)

x

z

(c)

Figure 20: Monoscopic, Stereoscopic and Tracked frustra

Figure 20(a) illustrates a monoscopic view frustum. The viewer is positioned atthe origin of the global coordinate system, and the frustum is completely symmetric.This is the typical view frustum for non-stereoscopic applications.

In stereo rendering, the scene is rendered twice, with the two frustra ’moved’ bythe distance between the eyes, as shown in Figure 20(b).

In immersive visualization, the observer is tracked in and the view frustra areadapted to the viewer’s position and orientation, as shown in Figure 20(c). Thetransformation origin→ viewer is set by the application using Config::setHeadMatrix,which is used by the server to compute the frustra. The resulting off-axis frustraare positioned using the channel’s head transformation.

25Volume Data Set courtesy of: AVS, USA

34


Recommended