TextureRender vs Renderer - very slow performance

I'm having very bad performance with TextureRenderer. Below is the test that renders directly to the screen and the does copyToTexture, by some reason copyToTexture implementation performs much faster (500fps) vs textureRender (100fps).



The test is ready to run, just copy-pase it in any project that has jme and lwjgl.jar in the classpass. Please run it and post your results.



Hit SPACE to toggle between different renderers.

EDIT: The code is updated to reflect all the changes done to the test throughout this topic.


import java.text.DecimalFormat;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.lwjgl.opengl.RenderTexture;

import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.renderer.lwjgl.LWJGLPbufferTextureRenderer;
import com.jme.renderer.lwjgl.LWJGLRenderer;
import com.jme.renderer.lwjgl.LWJGLTextureRenderer;
import com.jme.scene.SceneElement;
import com.jme.scene.Text;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;

public class TextureRendererTest extends SimpleGame {

    private KeyBindingManager keyboard = KeyBindingManager
            .getKeyBindingManager();
   
    private String switchMode = "switchMode";
   
    private enum Mode{ COPY_TEXTURE, PBUFFER, FBO, DISABLED };
    private Mode mode;
   
    private int width;
    private int height;
   
    private Quad sampleObject;
    private Quad fullscreenQuad;
    private TextureState fullScreenTextureState;
   
    private TextureRenderer fboRenderer;
    private TextureRenderer pbufferRenderer;
   
    private Texture fboTexture;
    private Texture pbufferTexture;
    private Texture copyTexture;

    private long timeSpent = 0;
    private long start = 0;
    private float runs = 0;
   
    private DecimalFormat format = new DecimalFormat("0.000");
   
    @Override
    protected void simpleInitGame() {
        width = display.getWidth();
        height = display.getHeight();

        sampleObject = new Quad("Sample", 10, 10);
        sampleObject.updateRenderState();

        fullscreenQuad = new Quad("FullScreenQuad", width, height);
        fullscreenQuad.setLocalTranslation(width / 2.0f, height / 2.0f, 0);
        fullscreenQuad.updateWorldVectors();

        fullscreenQuad.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        fullScreenTextureState = display.getRenderer().createTextureState();
        fullscreenQuad.setRenderState(fullScreenTextureState);

        fullscreenQuad.setCullMode(SceneElement.CULL_NEVER);
        fullscreenQuad.setTextureCombineMode(TextureState.REPLACE);
        fullscreenQuad.setLightCombineMode(LightState.OFF);

        fullscreenQuad.updateRenderState();
       
        keyboard.add(switchMode, KeyInput.KEY_SPACE);
       
        initRenderers();
        fullScreenTextureState.setTexture(copyTexture);
        mode = Mode.COPY_TEXTURE;
       
        Text info = Text.createDefaultTextLabel( "infoKeys" );
      info.setTextureCombineMode( TextureState.REPLACE );
      info.setLocalTranslation(0, display.getHeight() - 20, 0);
      info.print("Press SPACE to toggle between different renderers.");
        fpsNode.attachChild(info);
    }
   
    private void initRenderers() {
       if (!(display.getRenderer() instanceof LWJGLRenderer)) {
          System.err.println("Only LWJGLRenderer can be tested. Exiting...");
          System.exit(0);
       }
       
       // fbo renderer
       fboRenderer = new LWJGLTextureRenderer(width, height,
                (LWJGLRenderer) display.getRenderer());

        fboRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 1.0f));
        fboRenderer.setCamera(cam);

        fboTexture = makeRenderTexture();
        fboRenderer.setupTexture(fboTexture);
       
        // copy texture
        copyTexture = makeRenderTexture();
        fboRenderer.setupTexture(copyTexture);
       
        // pbuffer renderer
        RenderTexture renderTexture = new RenderTexture(false, true, true,
              false, RenderTexture.RENDER_TEXTURE_2D, 0);
       
        pbufferRenderer = new LWJGLPbufferTextureRenderer(width, height,
                (LWJGLRenderer) display.getRenderer(), renderTexture);
       
        pbufferRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 1.0f));
        pbufferRenderer.setCamera(cam);
       
        pbufferTexture = makeRenderTexture();
        pbufferRenderer.setupTexture(pbufferTexture);
    }
   
    private Texture makeRenderTexture() {
       Texture texture = new Texture();
        texture.setWrap(Texture.WM_CLAMP_S_CLAMP_T);
        texture.setFilter(Texture.FM_NEAREST);
        texture.setMipmapState(Texture.MM_NONE);
        texture.setRTTSource(Texture.RTT_SOURCE_RGBA);
        return texture;
    }

    protected void simpleRender() {
       Renderer mainRenderer = display.getRenderer();
        // *********** TIMING ***********
        // wait for opengl
        mainRenderer.finish();

        start = System.nanoTime();
        // *********** /TIMING ***********

        if (mode == Mode.COPY_TEXTURE) {
            // draw quad and copy to texture
           mainRenderer.draw(sampleObject);
           mainRenderer.renderQueue();
            fboRenderer.copyToTexture(copyTexture, width, height);
            // clear the buffer... You need to do this or your rendered
            // stuff will bleed into your real scene.
            mainRenderer.clearBuffers();
        }
        else if (mode == Mode.PBUFFER) {
           pbufferRenderer.render(sampleObject, pbufferTexture);
        }
        else if (mode == Mode.FBO) {
           
            fboRenderer.render(sampleObject, fboTexture);
        }

        fullscreenQuad.onDraw(mainRenderer);

        // *********** TIMING ***********
        // force queue to render and wait for finish so we can get complete
        // timing info.
        mainRenderer.renderQueue();
        mainRenderer.finish();
        timeSpent += (System.nanoTime() - start);
        runs++;
        // *********** /TIMING ***********
    }
   
    protected void simpleUpdate() {
       updateBuffer.setLength(0);
       updateBuffer.append("Renderer: ");
       updateBuffer.append(mode);
       updateBuffer.append(". FPS: ");
       updateBuffer.append(format.format(timer.getFrameRate()));
       updateBuffer.append(". Average TPF: ");
       updateBuffer.append(format.format(timeSpent / runs / 1e6d));
       updateBuffer.append(" ms. Runs: ");
       updateBuffer.append((int) runs);
       
        fps.print( updateBuffer );
    }

    protected void updateInput() {
        super.updateInput();

        if (keyboard.isValidCommand(switchMode, false)) {
           switchMode();
           
            runs = 0;
            timeSpent = 0;
        }
    }
   
    private void switchMode() {
       switch(mode) {
       
       case COPY_TEXTURE:
          mode = Mode.PBUFFER;
          fullScreenTextureState.setTexture(pbufferTexture);
          if (pbufferRenderer.isSupported()) break;
          
       case PBUFFER:
          mode = Mode.FBO;
          fullScreenTextureState.setTexture(fboTexture);
          if (fboRenderer.isSupported()) break;
          
       case FBO:
          mode = Mode.DISABLED;
          break;
          
       default:
          mode = Mode.COPY_TEXTURE;
         fullScreenTextureState.setTexture(copyTexture);
       }
    }
   
    protected void cleanup() {
       fboRenderer.cleanup();
       pbufferRenderer.cleanup();
       super.cleanup();
    }

    public static void main(String[] args) {
        Logger.getLogger("").setLevel(Level.WARNING);

        TextureRendererTest game = new TextureRendererTest();
        game.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
        game.start();
    }

}

lex, as your timing and illustration of the bottleneck show, FBO is poorly supported by your card.  Either see if you can update your driver or alternatively we’ve discussed adding a flag to allow for manual fallback to pbuffer.



And yes, it’s great that you can use a profiler to say where most of the time is spent for one half of a comparison and sorry if you still don’t understand why you can’t easily compare the two methods that way.



edit:  meh… sorry… having a rotten day… not feeling great after taking the kids out to trick-or-treat last night.




The test did not run as is on my mac g4 powerbook.  After tweaking it to run, it ran faster (almost 2x) when copy==false, or iow using the texture renderer.  The tweaks I did were changing main() -> main(void) in the shaders and using 512, 512 for the textureRenderer's width and height options for init and copy.  It's late, so i may be wrong, but the slowness you are seeing either sounds like you are wrong about which one is running, or it's an issue with how your card deals with non-pow2 textures.  I would expect the texRenderer to be faster than copying to a texture on most cards because of how FBO and pBuffer can bind directly to the texture.


Where do you get that from?  huh  glGenerateMipmapEXT is used to manually (re)generate mipmaps.  Have another look at the spec, specifically the examples:

http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt

For example, check out examples #4 and #5...  We are doing #4 where when you change the base level texture, you redo the mipmaps by calling generate.  #5 is similar to your assertion to only use it during setup...  in that test, mipmaps are manually draw at each mipmap level by the application rather than generated by opengl.


You are right! I was reading alot of data on FBO and got some of it wrong (blame information overload). Sorry.
However by looking at the example I have realized that they are calling glGenerateMipmapEXT AFTER drawing to the texture, and not before, as in LWJGLTextureRenderer.render()...

They even unbind the FBO first:

        loop {
            glBindTexture(GL_TEXTURE_2D, 0);

            for (int i=0; i<N; i++) {
              glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb_array);<-- FBO is bound
              <draw to texture i>
            }

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);<-- FBO is unbound

            // automatically generate mipmaps
            for (int i=0; i<N; i++) {
              glBindTexture(GL_TEXTURE_2D, color_tex_array);
              glGenerateMipmapEXT(GL_TEXTURE_2D);<-- glGenerateMipmapEXT call
            }

            <draw to the window, reading from the color textures>
        }

The profiler result above was for Linux. I just finished profiling in windows (with latest windows drivers) and got the same result.



I have run more tests and traced the loss of performance to this line:

EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fboID );


Which takes up most of the running time for LWJGLTextureRenderer in the test.

My system: Intel Celeron D 2.8GHz (256k L2 cache), 1Gig DDR RAM 400MHz, Geforce 7600 GT AGP.
Tested on Ubunti Linux 7.10 (driver version 96) and Windows XP (driver version 101).

Problem persists on both linux and windows.

I've cleaned up the test with proper usage…  Also, made the two sides of the drawing equal in terms of work and exaggerated which is the FBO/RTT by making the bg color bright.  It still runs 2x as fast when rendering to texture.  Your profiling is kind of useless…  One method is centralized under the texRenderer and the other is spread out amongst the renderer, texRenderer, etc.  Plus, if one side is running twice as fast it will exaggerate certain areas of the code.  Instead, doing average times is useful…  I've put in timing code which are marked by comments.  Please post your console output after running it and pressing space a few times:



import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.app.SimplePassGame;
import com.jme.image.Texture;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.renderer.pass.Pass;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.SceneElement;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;

public class TextureRendererVsCopy extends SimplePassGame {

    private KeyBindingManager keyboard = KeyBindingManager
            .getKeyBindingManager();

    private Quad quad;
    private boolean copy;

    private int width;
    private int height;

    @Override
    protected void simpleInitGame() {
        width = display.getWidth();
        height = display.getHeight();

        quad = new Quad("Quad", 10, 10);
        quad.setLocalTranslation(-10, 5, 0);
        quad.updateRenderState();
        quad.updateWorldVectors();

        pManager.add(new MyPass());

        RenderPass fpsPass = new RenderPass();
        fpsPass.add(fpsNode);
        pManager.add(fpsPass);

        keyboard.add("copy", KeyInput.KEY_SPACE);
    }

    private class MyPass extends Pass {
        /**
         *
         */
        private static final long serialVersionUID = 1L;
        private Quad q = new Quad("Q", width / 2.0f, height / 2.0f);
        private TextureRenderer tRenderer;
        private Texture texture;

        public MyPass() {
            tRenderer = display.createTextureRenderer(width, height,
                    TextureRenderer.RENDER_TEXTURE_2D);

            tRenderer.setBackgroundColor(new ColorRGBA(0.9f, 0.9f, 0.2f, 1.0f));
            tRenderer.setCamera(cam);

            texture = new Texture();
            texture.setWrap(Texture.WM_CLAMP_S_CLAMP_T);
            texture.setFilter(Texture.FM_NEAREST);
            texture.setRTTSource(Texture.RTT_SOURCE_RGBA);
            tRenderer.setupTexture(texture);

            q.setLocalTranslation(width / 4.0f + 10, height / 4.0f + 20, 0);
            q.updateWorldVectors();

            q.setRenderQueueMode(Renderer.QUEUE_ORTHO);
            TextureState ts = display.getRenderer().createTextureState();
            ts.setTexture(texture);
            q.setRenderState(ts);

            q.setCullMode(SceneElement.CULL_NEVER);
            q.setTextureCombineMode(TextureState.REPLACE);
            q.setLightCombineMode(LightState.OFF);

            q.updateRenderState();
        }

        protected void doRender(Renderer r) {
            // *********** TIMING ***********
            // wait for opengl
            r.finish();

            start = System.nanoTime();
            // *********** /TIMING ***********

            if (!copy) {
                // draw to offscreen buffer
                tRenderer.render(quad, texture);
            } else {
                // draw quad and copy to texture
                r.draw(quad);
                r.renderQueue();
                tRenderer.copyToTexture(texture, width, height);
                // clear the buffer... You need to do this or your rendered
                // stuff will bleed into your real scene.
                display.getRenderer().clearBuffers();
            }

            q.onDraw(r);

            // *********** TIMING ***********
            // force queue to render and wait for finish so we can get complete
            // timing info.
            r.renderQueue();
            r.finish();
            timeSpent += (System.nanoTime() - start);
            runs++;
            // *********** /TIMING ***********
        }
    }

    long timeSpent = 0;
    long start = 0;
    int runs = 0;

    protected void updateInput() {
        super.updateInput();

        if (keyboard.isValidCommand("copy", false)) {
            copy = !copy;
            // *********** TIMING ***********
            System.out
                    .println("average time spent: " + timeSpent / runs + "ns" + "  runs: "+runs);
            runs = 0;
            timeSpent = 0;
            // *********** /TIMING ***********
            if (copy) {
                System.out.println("Switching to copyToTexture mode.");
            } else {
                System.out.println("Switching to direct RTT mode.");
            }
        }
    }

    public static void main(String[] args) {
        Logger.getLogger("").setLevel(Level.WARNING);

        TextureRendererVsCopy game = new TextureRendererVsCopy();
        game.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
        game.start();
    }

}



Here's my run:

Switching to copyToTexture mode.
average time spent: 1801081ns  runs: 410
Switching to direct RTT mode.
average time spent: 958688ns  runs: 554
Switching to copyToTexture mode.
average time spent: 1791319ns  runs: 400
Switching to direct RTT mode.
average time spent: 855691ns  runs: 617
Switching to copyToTexture mode.
average time spent: 1761248ns  runs: 510
Switching to direct RTT mode.


edit: removed package declaration...

Also, if you are still running slower, try hardcoding width and height to a pow2 like 512.

Also, as another possibility we can also look at an ultimate fallback of copyToTexture for people with both slow fbo and pbuffer implementations, but then you would have to guarantee that you were doing the operation before all other frame drawing operations.  As that is a big departure from the TextureRenderer behavior, it could be hard to wrap those together.


lex, as your timing and illustration of the bottleneck show, FBO is poorly supported by your card.  Either see if you can update your driver or alternatively we've discussed adding a flag to allow for manual fallback to pbuffer.


I've tried both linux and windows with latest drivers available with similar results.

It would be very helpful if people with various hardware could do more testing and post their results and specs.

Personally I don't believe nVidia would have such horrible FBO support, they would have been out of business long ago... I have a suspicion the problem lies in lwjgl. I'm going to write a simple test in C and pure openGL and benchmark it against similar test in lwjgl. C should be bit faster but the difference here is rather striking (FBO being 10 times slower than copy to texture), so if the problem is in lwjgl it will show up.

What hardware are you running on anyhow?  My results are on Win XP with an nVidia 8800 GTX.  Last night I was getting similar results on my Mac powerbook g4 1.5 on ATI Radeon 9700 mobility

My system: Intel Celeron D 2.8GHz (256k L2 cache), 1Gig DDR RAM 400MHz, Geforce 7600 GT AGP.

Tested on Ubunti Linux 7.10 (driver version 96) and Windows XP (driver version 101).

I had lots of issues with FBO before (driver crashing). All shaders in jME seem to run extremely slow but when I tried out a bloom render pass test from the ogre rendering engine it ran full speed…

Seems like others have had similar issues… and found older drivers worked better?

http://www.gamedev.net/community/forums/topic.asp?topic_id=443116

http://www.gamedev.net/community/forums/topic.asp?topic_id=469438

Momoko_Fan said:

All shaders in jME seem to run extremely slow


Do you mean currently, or before a driver update?

Is it possible to webstart one of the tests? I.e. that way everyone can confirm that they are running the same test, and we can start getting more input. Trying to follow the thread to determine which code is being run for each test is a little difficult.

I have changed the test to be suitable for web-starting. But I'm having a problem display the texture from PBufferRenderer. I'm not sure if pbuffer texture requires special handling or pbuffer is no longer supported and so not kept up to date with the rest of the jme.



Here is the code:


// See the code in the first post of this topic (PBuffer rendering is now fixed).



EDIT: changed to print out both fps and average time spent rendering.
lex said:

According to the specifications, glGenerateMipmapEXT only needs to be called once. And it is called during texture setup. Calling it in render() method really slows down FBO renderer on my system even further:


Where do you get that from?  :?  glGenerateMipmapEXT is used to manually (re)generate mipmaps.  Have another look at the spec, specifically the examples:

http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt

For example, check out examples #4 and #5...  We are doing #4 where when you change the base level texture, you redo the mipmaps by calling generate.  #5 is similar to your assertion to only use it during setup...  in that test, mipmaps are manually draw at each mipmap level by the application rather than generated by opengl.

    (4) Render-to-texture loop with automatic mipmap generation.  There
    are N framebuffers, N mipmap color textures, and a single shared
    depth renderbuffer.  The depth renderbuffer is not a mipmap.

        GLuint fb_array[N];
        GLuint color_tex_array[N];
        GLuint depth_rb;

        glGenFramebuffersEXT(N, fb_array);
        glGenTextures(N, color_tex_array);
        glGenRenderbuffersEXT(1, &depth_rb);

        // initialize color textures
        for (int i=0; i<N; i++) {
          glBindTexture(GL_TEXTURE_2D, color_tex_array[N]);
          glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 512, 512, 0,
                      GL_RGB, GL_INT, NULL);

          // establish a mipmap chain for the texture
          glGenerateMipmapEXT(GL_TEXTURE_2D);
        }

        // initialize depth renderbuffer
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
                                GL_DEPTH_COMPONENT24, 512, 512);

        // setup framebuffers, sharing depth
        for (int i=0; i<N; i++) {
          glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb_array);
          glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                                    GL_COLOR_ATTACHMENT0_EXT,
                                    GL_TEXTURE_2D, color_tex_array, 0);
          glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                      GL_DEPTH_ATTACHMENT_EXT,
                                      GL_RENDERBUFFER_EXT, depth_rb);
        }

        // Check framebuffer completeness at the end of initialization.
        CHECK_FRAMEBUFFER_STATUS();

        loop {
            glBindTexture(GL_TEXTURE_2D, 0);

            for (int i=0; i<N; i++) {
              glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb_array);
              <draw to texture i>
            }

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

            // automatically generate mipmaps
            for (int i=0; i<N; i++) {
              glBindTexture(GL_TEXTURE_2D, color_tex_array);
              glGenerateMipmapEXT(GL_TEXTURE_2D);
            }

            <draw to the window, reading from the color textures>
        }


    (5) Render-to-texture loop with custom mipmap generation.
        The depth renderbuffer is not a mipmap.

        glGenFramebuffersEXT(1, &fb);
        glGenTextures(1, &color_tex);
        glGenRenderbuffersEXT(1, &depth_rb);

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

        // initialize color texture and establish mipmap chain
        glBindTexture(GL_TEXTURE_2D, color_tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 512, 512, 0,
                    GL_RGB, GL_INT, NULL);
        glGenerateMipmapEXT(GL_TEXTURE_2D);
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                                  GL_COLOR_ATTACHMENT0_EXT,
                                  GL_TEXTURE_2D, color_tex, 0);

        // initialize depth renderbuffer
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_rb);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
                                GL_DEPTH_COMPONENT24, 512, 512);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                    GL_DEPTH_ATTACHMENT_EXT,
                                    GL_RENDERBUFFER_EXT, depth_rb);

        // Check framebuffer completeness at the end of initialization.
        CHECK_FRAMEBUFFER_STATUS();

        loop {
            glBindTexture(GL_TEXTURE_2D, 0);

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                                      GL_COLOR_ATTACHMENT0_EXT,
                                      GL_TEXTURE_2D, color_tex, 0);
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                        GL_DEPTH_ATTACHMENT_EXT,
                                        GL_RENDERBUFFER_EXT, depth_rb);

            <draw to the base level of the color texture>

            // custom-generate successive mipmap levels
            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                        GL_DEPTH_ATTACHMENT_EXT,
                                        GL_RENDERBUFFER_EXT, 0);
            glBindTexture(GL_TEXTURE_2D, color_tex);
            foreach (level > 0, in order of increasing values of level) {
                glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                                          GL_COLOR_ATTACHMENT0_EXT,
                                          GL_TEXTURE_2D, color_tex, level);
                glTexParameteri(TEXTURE_2D, TEXTURE_BASE_LEVEL, level-1);
                glTexParameteri(TEXTURE_2D, TEXTURE_MAX_LEVEL, level-1);

                <draw to level>
            }
            glTexParameteri(TEXTURE_2D, TEXTURE_BASE_LEVEL, 0);
            glTexParameteri(TEXTURE_2D, TEXTURE_MAX_LEVEL, max);

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
            <draw to the window, reading from the color texture>
        }



Right now I am trying to get the test to work… I will post results then.

Ok… FBO is fastest, rendering full speed (60 fps i guess), copy texture is about 1 fps, pbuffer i can't tell because the screen is completely white.