rendering multiple openGL views

pseudostereo's icon

Hi all -

I've ended up switching between a number of strategies for rendering multiple views of opengl scenes to different screens (a subject which has come up here a couple of times), and I finally decided I needed a basis of comparison of the various options. So I built the following patch + javascript, which allows a comparison of the relative speeds of four methods for rendering two views.

1. drawto: uses a single render context and switches the destination with a drawto message

2. renderContext: uses multiple render contexts, one for each view

3. viewports: uses openGL scissor_test to divide a single window into multiple viewports

4. drawpixels: draws both views, then uses glreadpixels to copy each view to a visible window. This method is clearly not useful for rendering two different views, as you have to first render both views somewhere (I'm rendering to a hidden window); it's more useful when you have to make an exact copy of an existing window. Anyway, I included it here for completeness.

On my aging powerbook (1 GHz G4) at 1024 x 768, I get about 10fps from "drawto" with the Max window open; this goes up to about 12fps when the Max window is closed, I guess because it doesn't have to display all those messages. "RenderContext" gives me about 12fps. "Viewports" at 1024 x 768 gives me a blank white screen; at 800 x 600 I get about 11fps. "Drawpixels" is predictably the slowest, about 6fps.

On a dual 1.8 GHz G5: "drawto" and "renderContext" both top out at about 40fps; "viewports" about 30fps; and "drawpixels" at 20fps.

So it seems that the first two methods give almost identical results, at least on these two machines. I was surprised; I expected the "drawto" method to be slower, due to all the messages.

Of the two, "renderContext" requires multiple copies of objects (one for each render context), which means a lot of extra housekeeping, especially when mixing patcher-based and javascript-based objects. Since there doesn't seem to be much (if any) performance hit from "drawto", I'm inclined to go with that method. It would be nice if it didn't spit out those messages, though.

I'd be curious what kinds of results other people get, also if there are other methods that anyone can think of.

- pH

Max Patch
Copy patch and select New From Clipboard in Max.

//begin multiScreen.js
autowatch = 1;
post ("recompiledn");

var multiMethod = "drawto"
var vues = new Array;
var rand = Math.floor(Math.random()*1000);
var theContext = "context"+rand;
var angle = 0;
var wind = {width:320,height:240};
var textureFlag = 0;
var pixelWindow = new JitterObject("jit.window",theContext+2);

initAll();

function initAll() {

for (n=0;n
vues[n] = {sketch:0,sketchobj:0,render:0,window:0,matrix:0,matrixFlip: 0}

vues[n].sketch = new JitterObject("jit.gl.sketch",theContext+n);
vues[n].sketch.lighting_enable = 1;
vues[n].sketch.smooth_shading = 1;

vues[n].sketchobj = new JitterObject("jit.gl.sketch",theContext+n);
vues[n].sketchobj.automatic = 0;
vues[n].sketchobj.name = "sketchobj"+theContext+n;
vues[n].sketchobj.gltranslate(-2,-2,0);
for(x=0;x
for(y=0;y
vues[n].sketchobj.torus(0.5);
vues[n].sketchobj.gltranslate(1,0,0);
}
vues[n].sketchobj.gltranslate(-5,1,0);
}

vues[n].qtmovie = new JitterObject("jit.qt.movie",320,240);

vues[n].qtmatrix = new JitterMatrix(4,"char",320,240);
vues[n].qtmatrix.name = "qtmatrix"+theContext+n;

vues[n].texture = new JitterObject("jit.gl.texture",theContext+n);
vues[n].texture.name = "texture"+theContext+n;

vues[n].render = new JitterObject("jit.gl.render",theContext+n);
vues[n].render.erase_color = [0,0,0,1];

vues[n].window = new JitterObject("jit.window",theContext+n);
vues[n].window.depthbuffer = 1;
vues[n].window.size = [wind.width,wind.height];
vues[n].window.pos = [(n*wind.width),50];

vues[n].matrix = new JitterMatrix(4,"char",wind.width,wind.height);
vues[n].matrix.name = "matrix"+theContext+n;

vues[n].matrixFlip = new JitterMatrix(4,"char",wind.width,wind.height);
vues[n].matrixFlip.name = "matrixFlip"+theContext+n;
vues[n].matrixFlip.usesrcdim = 0;
vues[n].matrixFlip.srcdimstart = [0,wind.height-1];
vues[n].matrixFlip.srcdimend = [wind.width-1,0];
vues[n].matrixFlip.usesrcdim = 1;
}
pixelWindow.size = [wind.width,wind.height];
pixelWindow.depthbuffer = 1;
pixelWindow.visible = 0;
}
function setMethod(a) {
multiMethod = a;
switch(multiMethod) {
case "drawto":
vues[0].sketch.gldisable("scissor_test");
vues[0].window.size = [wind.width,wind.height];
vues[1].window.visible = 1;
toggleContext(0,theContext+0);
toggleContext(1,"nowhere");
break;
case "renderContext":
vues[0].sketch.gldisable("scissor_test");
vues[0].window.size = [wind.width,wind.height];
vues[1].window.visible = 1;
toggleContext(0,theContext+0);
toggleContext(1,theContext+1);
break;
case "viewports":
vues[0].sketch.glenable("scissor_test");
vues[1].window.visible = 0;
vues[0].window.size = [wind.width*2,wind.height];
toggleContext(0,theContext+0);
toggleContext(1,"nowhere");
break;
case "drawpixels":
vues[0].sketch.gldisable("scissor_test");
toggleContext(0,theContext+2);
vues[0].window.size = [wind.width,wind.height];
vues[1].window.visible = 1;
toggleContext(1,"nowhere");
break;
}
}
function bang() {
theRotate1 = [++angle,0,1,0];
theRotate2 = [angle,1,0,0];
switch(multiMethod) {
case "drawto":
for (n=0;n
toggleContext(0,theContext+n)
vues[0].sketch.reset();
vues[0].sketch.glclear();
vues[0].sketch.position = [0,0,-3];
if(n) {
vues[0].sketch.rotate = theRotate1;
} else {
vues[0].sketch.rotate = theRotate2;
}
if(textureFlag) {
vues[0].sketch.glbindtexture(vues[0].texture.name);
}
vues[0].sketch.drawobject(vues[0].sketchobj.name,1);
vues[0].render.erase();
vues[0].render.drawswap();
}
break;
case "renderContext":
for (n=0;n
vues[n].sketch.reset();
vues[n].sketch.glclear();
vues[n].sketch.position = [0,0,-3];
if(n) {
vues[n].sketch.rotate = theRotate1;
} else {
vues[n].sketch.rotate = theRotate2;
}
if(textureFlag) {
vues[n].sketch.glbindtexture(vues[n].texture.name);
}
vues[n].sketch.drawobject(vues[n].sketchobj.name,1)
vues[n].render.erase();
vues[n].render.drawswap();
}
break;
case "viewports":
vues[0].sketch.reset();
vues[0].sketch.glclear();
for(n=0;n
myRect = [n*wind.width,0,wind.width,wind.height];
vues[0].sketch.glviewport(myRect);
vues[0].sketch.glscissor(myRect);
vues[0].sketch.glmatrixmode("projection");
vues[0].sketch.glloadidentity();
vues[0].sketch.glfrustum([-0.8,0.8,-0.6,0.6,1,120]);
vues[0].sketch.glmatrixmode("modelview");
vues[0].sketch.glloadidentity();
vues[0].sketch.glulookat([0,0,-4,0,0,0,0,1,0]);
if(n) {
vues[0].sketch.glrotate(theRotate1);
} else {
vues[0].sketch.glrotate(theRotate2);
}
if(textureFlag) {
vues[0].sketch.glbindtexture(vues[0].texture.name);
}
vues[0].sketch.drawobject(vues[0].sketchobj.name,1)
}
vues[0].render.erase();
vues[0].render.drawswap();
break;
case "drawpixels":
vues[0].sketch.reset();
for(n=0;n
vues[0].sketch.glclear();
vues[0].sketch.glmatrixmode("projection");
vues[0].sketch.glloadidentity();
vues[0].sketch.glfrustum([-0.8,0.8,-0.6,0.6,1,120]);
vues[0].sketch.glmatrixmode("modelview");
if(n) {
vues[0].sketch.rotate = theRotate2;
} else {
vues[0].sketch.rotate = theRotate1;
}
if(textureFlag) {
vues[0].sketch.glbindtexture(vues[0].texture.name);
}
vues[0].sketch.drawobject(vues[0].sketchobj.name,1)
vues[0].sketch.glreadpixels(vues[n].matrix.name);
vues[n].matrixFlip.frommatrix(vues[n].matrix.name);
vues[n].window.jit_matrix(vues[n].matrixFlip.name);
vues[0].render.erase();
vues[0].render.drawswap();
}
}
}
function windowSize(h,v) {
wind.width = h;
wind.height = v;
initAll();
setMethod(multiMethod);
}
function windowSet(a) {
for (n=0;n
if(a) {
vues[n].window.pos = [(n*wind.width),0];
vues[n].window.notitle = 1;
vues[n].window.front(1);
} else {
vues[n].window.pos = [(n*wind.width),50];
vues[n].window.title = 1;
}
vues[n].window.exec;
vues[n].window.exec;
}
}
function toggleContext(source,destination) {
vues[source].sketch.drawto = destination;
vues[source].sketchobj.drawto = destination;
vues[source].texture.drawto = destination;
vues[source].render.drawto = destination;
}
function toggleTexture(a) {
textureFlag = a;
}
function readTexture(filename){
if (arguments.length==0) {
mov = vues[0].qtmovie.read();
} else {
mov = vues[0].qtmovie.read(filename);
}
if(mov[1]) {
vues[1].qtmovie.read(mov[0]);
for(n=0;n
vues[n].qtmovie.matrixcalc(vues[n].qtmatrix,vues[n].qtmatrix );
vues[n].texture.jit_matrix(vues[n].qtmatrix.name);
}
}
}
//end multiScreen.js

Maarten's icon

We have made a test some while ago between drawto and multiplerender
contexts. Look for my name on the list and you will find that test. We
came to the conclusion that multiple contexts for different windows
works the best when using multiple videocards. I'm interested in the
result for this one, but I think using multiple contexts will also be
the fastest for this case as well.

Maybe I a bit biased but I always thought you should avoid as much
javascript as possible because that would slow the patch down, maybe
you could rewrite that code in a suppatcher and see the results.

Grtz

pseudostereo's icon

Maarten -

OK, here's almost the exact same example rewritten as a javascript-free patch, first as multiple render contexts, and second (in the next message) as a single render context, switching destinations with drawto.

On my PB G4, I get almost exactly the same 12fps (from both versions, with the Max window closed) that I was getting with javascript. I'll see how it does on the G5 tomorrow, but for now, it seems to me that this disproves the idea that using javascript means taking a big speed hit.

Also, I remembered Joshua's suggestion of turning off the jit.window sync attribute, so I tried toggling that here, but (at least on the PB) it doesn't seem to make much difference.

- pH

[multiple render contexts example]

Max Patch
Copy patch and select New From Clipboard in Max.

pseudostereo's icon

[part 2 of previous message]

[single render context with drawto]

Max Patch
Copy patch and select New From Clipboard in Max.

pseudostereo's icon

Further reports, running the patches on the G5:

The sync attribute doesn't seem to make much difference to any of the javascript-based patch options, drawto and renderContext both run at somewhere between 35 and 40fps.

As far as the javascript-free patches: both versions (the single-context drawto version with the Max window closed, and the 2x render context version) run at about 40fps, but they do both jump up to about 45fps with sync=0.

My conclusions:

Javascript might be a tiny bit slower, but not by much. Except when you turn off sync for the jit.windows, in which case the patcher-based versions clearly have the edge (because the sync attribute doesn't seem to have any affect on the javascript versions).

Surprisingly, multiple render contexts seems to have little or no edge over drawto.

This just in: I've been running ShadowKiller (http://www.unsanity.com/haxies/shadowkiller), and I just realized that when I turn shadows back on I lose about 5fps on everything. Pretty significant. Makes me wonder if there are other OS X niceties that could be switched off for extra speed...

Maarten's icon

I've noticed that some widget use cpu when not activated. I always
clear out dashboard before doing something which needs my full
processor.

Also programs like menumeters or any other measuring programm uses cycles.
Just shut everything down before doing such a test.

I also have some HP software for printing on my laptop which is ported
to osx I believe. It uses alot of my cpu cycles.

In all just open activity monitor and check out what takes cycles or
memory space.

dtr's icon

[hyperdrive 5 years forward...]

thanks for the methods round up! here's another one using to_texture: https://cycling74.com/forums/sharing-is-bladibla-render-multiple-views-of-1-gl-scene-for-triplehead-setup