rendering multiple openGL views


    Feb 22 2006 | 7:40 am
    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 v2;
    //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

    • Feb 22 2006 | 8:39 am
      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
    • Feb 22 2006 | 12:57 pm
      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 v2;
    • Feb 22 2006 | 12:59 pm
      [part 2 of previous message]
      [single render context with drawto]
      max v2;
    • Feb 22 2006 | 7:13 pm
      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...
    • Feb 22 2006 | 8:17 pm
      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.
    • May 14 2011 | 2:23 pm
      [hyperdrive 5 years forward...]
      thanks for the methods round up! here's another one using to_texture: http://cycling74.com/forums/topic.php?id=33397