OpenSceneGraph, Dual Screens and TwinView

Build dual screen apps with Open Scene Graph.

💡
Obsolete Alert: The information in this post is very old

So some of my work at uni involves programming using OpenSceneGraph. Now, anybody who has used OSG before will know that as powerful as it may be, it is seriously lacking in the documentation department. So, this article describes how to do dual screen graphics on Linux using OpenSceneGraph. First we’ll look at the X Screens approach, which is easier but probably not the best solution. Then we’ll look at a method that works with a single X screen.

Multiple X Screens

The easiest way to do dual screen output is if you have your X server configured so each output is its own X screen. The first thing you need to do is make sure you have enough screens. Finding this out is easy enough:

int getNumScreens()
{
    osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
    osg::GraphicsContext::ScreenIdentifier si;
    si.readDISPLAY();
    return wsi->getNumScreens(si);
}

You should do this before attempting to create your screens to make sure the X server is configured correctly. Otherwise OSG will throw an error when you try and create a graphics context for a screen that doesn’t exist. Setting up the viewers is fairly straight forward:

//Create main scene viewer

ref_ptr compositeViewer = new osgViewer::CompositeViewer;

// create first view
osgViewer::View* v0 = new osgViewer::View();
v0->setUpViewOnSingleScreen(0);

// add view to the composite viewer
compositeViewer->addView(v0);

// do the same with the second view
osgViewer::View* v1 = new osgViewer::View();
v1->setUpViewOnSingleScreen(1);
compositeViewer->addView(1);

And thats it. You also need to set the scene data for each of your views and a couple of things I’ve missed, but that is the basic idea. The problem with this method is it creates 2 graphics contexts, which in most cases will cause a performance hit.

Single X Screen, Single Context

I looked into this method because I use TwinView on my Linux desktop boxes. Of course, TwinView means that there is only 1 XScreen that spans both monitors. Therefore, the getNumScreens function above will return 1. I have also set up our projector setup to use TwinView, so I don’t need to have one lot of code for testing on the desktop and then do something completely different when using the projectors. The other benefit of this approach is you only create one graphics context.

First thing we do is get the dimensions of the screen, which will be the combination of both screens.

// get the total resolution of the xscreen
osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();
unsigned width, height;
wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);

Once that is done we can create our (single) graphics context.

// create a context that spans the entire x screen
traits->x = 0;
traits->y = 0;
traits->width = width;
traits->height = height;
traits->windowDecoration = false;
traits->doubleBuffer = true;
traits->sharedContext = 0;
traits->overrideRedirect = true;
osg::ref_ptr gc = osg::GraphicsContext::createGraphicsContext(traits.get());

The important one here is overrideRedirect. Some window managers (I’m looking at you Gnome) will redirect the position of your graphics context, so it won’t appear where you want it. The overrideRedirect option is kindof new, it does not exist in the version of OSG shipping with Ubuntu 8.10. Therefore, I am running the latest stable release (2.6) compiled from source.

To get the equivalent of 2 screens to draw on, we create 2 views like before. However, we have to set their viewport manually. Here we just make v0 use the left half of the screen, and v1 use the right half. Easy enough?

//first screen
osgViewer::View* v0 = new osgViewer::View();

osg::ref_ptr cam = v0->getCamera();
cam->setGraphicsContext(gc.get());
cam->setViewport(0, 0, width/2, height);
compositeViewer->addView(v);

//second screen
osgViewer::View* v1 = new osgViewer::View();
osg::ref_ptr cam2 = v1->getCamera();
cam2->setGraphicsContext(gc.get());
cam2->setViewport(width/2, 0, width/2, height);
compositeViewer->addView(v1);

setViewport sets the viewport of the camera. The first 2 parameters are the position in the context’s window, the next 2 are the dimensions. So, each view gets width/2 for the width, and the second screen’s position is offset by half the screen width meaning it starts on the second monitor.

Conclusion

And there you have it. Two methods for dual screen using OpenSceneGraph. Looking at the code, it is fairly simple. However, after browsing the doxygen docs for OSG it was not at all obvious to me. Of course, the osg-users mailing list was a big help here. In fact, here is the thread from the mailing list.

Enjoy!