4

I use FBO to render multi pass blur shader in my LevelScreen render method. What I want to achieve is to make a MenuScreen that render the LevelScreen on background applying another blur effect on top of it.

here is the pseudo code

    protected void render(float delta) {
        // render the scene to the FBO
        fbo.begin();
        levelScreen.render(delta);
        fbo.end();

        // retrieve texture and flip Y
        Sprite background = new Sprite(fbo.getColorBufferTexture());
        background.flip(false, true);

        // apply the blurX shader and
        // render the sprite
        batch.setShader(blurH);
        batch.begin();
        background.draw(batch);
        batch.end();
    }

The problem is that the levelScreen.render() function already contains a fbo.begin() fbo.end() and the scene is directly rendered on the screen.

Is there a way to handle properly nested fbo?

Alexandre GUIDET
  • 852
  • 9
  • 27

2 Answers2

8

You can handle things manually, use custom class to handle the FrameBuffers, possibly backed by Stack, with methods like startRender(FrameBuffer) and endRender() whch will properly manage things, like close old FrameBuffer and open new one then, etc.

Also, try to avoid creating new objects inside the render methods.

Sprite background = new Sprite(fbo.getColorBufferTexture());

This should work just fine if this was a field instead.

EDIT:

If I understand you correctly, your current problem looks like this:

FrameBuffer fb1 = ..., fb2 = ...;
fb1.begin();
// draw something on fb1
fb2.begin();
// draw something on fb2
fb2.end();
// draw something on fb1 again
fb1.end();

But you can't have multiple FrameBuffers opened at the same time, right? So create a stack-based class to manage the FrameBuffers for you, something simple can do:

class FrameBufferManager {
    private Stack<FrameBuffer> stack = new Stack<FrameBuffer>();

    public void begin(FrameBuffer buffer) {
        if (!stack.isEmpty()) {
            stack.peek().end();
        }
        stack.push(buffer).begin();
    }

    public void end() {
        stack.pop().end();
        if (!stack.isEmpty()) {
            stack.peek().begin();
        }
    }
}

Then you use it like this:

FrameBuffer fb1 = ..., fb2 = ...;
FrameBufferManager manager = ...;

manager.begin(fb1);
// draw something on fb1
manager.begin(fb2);
// draw something on fb2
manager.end();
// draw something on fb1 again
manager.end();

However i haven't tried this with or without the manager, but it should solve you problem. I'm not sure if you will need to re-open renderers and batches every time you change FrameBuffer (I usually do).

kajacx
  • 12,361
  • 5
  • 43
  • 70
  • Can you explain a bit more your idea behind the stack of frameBuffer? I don't see how to do that. – Alexandre GUIDET Aug 26 '14 at 08:56
  • I just tried your solution and it work well and transparently, thank you for the awesome tip! – Alexandre GUIDET Aug 29 '14 at 18:52
  • @kajacx thanks bro, works perfectly. It's super clean, I thought solving it will be a huge mess – Gintas_ Feb 21 '16 at 19:02
  • This solution is great for me as well, just be careful if you have complex render strategies that switch from using Frame Buffers to not using them (for example, start using it when entering a Pause menu) - it can happen that the render strategy changes within a render loop and you end up calling the stack.pop() on an empty stack. – Azurlake Jun 11 '21 at 10:20
  • @azurlake yea, best approach would be to use try-with-resources. – kajacx Jun 28 '21 at 17:03
1

A simplistic solution, but this might be all you need:

Break up your LevelScreen into separate methods:

protected void render(float delta){
    update(delta);
    drawFBOs(delta);
    drawMain(delta);
}

And implement each of the above three methods. Then also create this method, that the MenuScreen can call with it's own FBO reference passed in.

protected void renderToFBO(float delta, FrameBuffer target){
    update(delta); //if necessary
    drawFBOs(delta);
    target.begin();
    drawMain(delta);
    target.end();
}

If necessary to set certain parameters, you can add a boolean to the drawMain method constructor to determine if it's drawing directly to screen or to a frame buffer.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • I have already tried this solution and it works well, but it seems no to be as reusable as the one kajacx propose. Thank you for the suggestion, both solutions work – Alexandre GUIDET Aug 29 '14 at 18:50