-
Notifications
You must be signed in to change notification settings - Fork 25
VFX Frame Buffer
Instead of regular LibGDX's FrameBuffer
, the library uses it's own VfxFrameBuffer
to extend standard functionality and provide extra state management.
If you're wondering why this library uses VfxFrameBuffer
, skip to the justification section.
Since VfxFrameBuffer
directly messes up with a few more OpenGL states then it should, it may be considered as unintended behavior by LibGDX, and there are a number of dirty hacks inside. Thus, you should be aware of the next things:
Because VfxFrameBuffer
internally manages global OpenGL state, do not use FrameBuffer
as it will most likely lead to ugly bugs. The rule of thumb is to use VfxFrameBuffer
over FrameBuffer
always when:
- Between
VfxFrameBuffer
begin/ends calls. - Between
VfxManager
begin/end. - Inside any of
VfxEffect
related classes.
VfxFrameBuffer
tracks and manages currently bound frame buffer and viewport. In order to do that, VfxFrameBuffer
should have a way to query these values somewhere. At the moment, LibGDX doesn't hold the reference to the currently bound frame buffer object or viewport. So in order to get these values, we should make a direct OpenGL state request calls.
Now here's the deal, OpenGL requests are considered as slow, and they stall the rendering pipeline. There are many discussions on how this impacts the performance, but for now, this seems to be the only solution. This is a trade off, we give up some performance to gain some flexibility and convenience. Maybe once LibGDX reconsiders the situation around local OpenGL state management, the way this library deals with these requests may be changed.
Version 0.5.0 note: As the library takes better and better shape; I'm getting more concerned about its performance. There's a chance these ugly hacks will go away in the next release. Stay tuned.
The library implies a strict requirement to use VfxFrameBuffer
over regular FrameBuffer
whenever you deal with the gdx-vfx
code. So why use custom implementation? Well, there are a number of reasons for that decision:
The first and primary reason is to introduce the bound frame buffer tracking to the LibGDX context.
The problem here is FrameBuffer
doesn't support what happens to be nested buffer drawing (e.g., draw into a frame buffer while drawing into another one). The implementation of FrameBuffer
is simple; you call FrameBuffer#begin()
, and it configures OpenGL's context to draw to the buffer. When you're done, you call FrameBuffer#end()
and it switches OpenGL's context back to the default buffer (the screen buffer).
Let's look at the practical problem. We have two off-screen frame buffers, and different parts of the application intend to draw into them without any knowledge of each other. This is how the order of drawing may look like:
FrameBuffer buffer0, buffer1;
void render() {
// Rendering happens directly to the screen.
buffer0.begin();
// Rendering happens into buffer0.
buffer1.begin();
// Rendering happens into buffer1.
buffer1.end();
// Rendering happens directly to the screen. <-- Here is where it breaks. As we expect to draw into buffer0 here.
buffer0.end();
// Rendering happens directly to the screen.
}
For the library like this, the problem is essential, as there are multiple isolated off-screen rendering stages, which are not aware of each other and need to bind/unbind their own frame buffers anytime.
To solve this, there should be some global context management introduced to keep track of the stack of bound frame buffers. And that's why all the FrameBuffer
s were wrapped into VfxFrameBuffer
s for the library, to properly manage that state.
For now, there is no proper stack tracking implementation, but rather a less reliable and simplified approach is used.
When you call VfxFrameBuffer#begin()
, it remembers the previously bound frame buffer ID and restores it upon VfxFrameBuffer.end()
call. This approach works for most of the cases where there is a properly organized structure of frame buffers, and begin/end calls happen in order. But this solution falls apart when begin/end calls get mixed and collide. Here's an example:
VfxFrameBuffer buffer0, buffer1;
void render() {
buffer0.begin();
buffer1.begin();
buffer0.end(); // <-- Here we'll get an error as this call will bind the screen buffer instead of buffer1.
buffer1.end();
}
But for now, from my experience, there is no real need to maintain the stack tracking solution, as the case illustrated above is rather a good indication of wrong rendering order, and most likely, you are doing something wrong. But I might be wrong about it, and that's why we are in the beta now, right? The situation about this case is open for discussion.
When you bind a frame buffer, you most likely want to draw into it the same way as to the screen buffer. When you call FrameBuffer#begin()
original implementation changes OpenGL viewport to match its size. This is good, but in order to draw to a proper portion of the screen, you also should update your batch projection matrices.
Also upon FrameBuffer#end()
call, FrameBuffer
sets the OpenGL viewport back to the full screen (assuming you're going to draw to the screen now). And for the nested buffer drawing approach, that's pretty much the same problem as with the bound frame buffer stack tracking.
So to address this issue and for better batch rendering integration, VfxFrameBuffer
introduces Renderer
interface. You can use different implementations of this interface (e.g., BatchRenderAdapter
or ShapeRenderAdapter
) to wrap a specific type of render batcher and register it for the VfxFrameBuffer
. This way, VfxFrameBuffer
will properly coordinate your batch matrices and flush it whenever you call begin/end methods.
Check this out:
SpriteBatch batch;
VfxFrameBuffer buffer;
void create() {
batch = new SpriteBatch(...);
buffer = new VfxFrameBuffer(...);
// Wrap Batch into a Renderer implementation and add to the VfxFrameBuffer.
Renderer batchRenderer = new BatchRendererAdapter(batch);
buffer.addRenderer(batchRenderer );
}
void render() {
// Begin batch drawing.
// Batch has a projection matrix set to match the whole screen.
batch.begin();
// Draw something to the screen.
batch.draw(...);
// Start drawing into the off-screen frame buffer.
// Next important things will happen on this call:
// 1. The batch will be flushed, so all the pending render calls go to the screen buffer.
// 2. The currently bound frame buffer will be changed to the VfxFrameBuffer instance.
// 3. The batch projection matrix will be updated to match the frame buffer's size.
buffer.begin();
// Draw something to the off-screen frame buffer.
batch.draw(...);
// Stop drawing into the off-screen frame buffer.
// Next important things will happen on this call:
// 1. The batch will be flushed, so all the pending render calls go to the off-screen frame buffer.
// 2. The currently bound frame buffer will be changed to the screen frame buffer.
// 3. The batch projection matrix will be updated to match the screen viewport.
buffer.end();
// Draw something to the screen (e.g. off-screen buffer's texture).
batch.draw(...);
// Stop batch drawing and render pending render calls to the screen.
batch.end();
}
There are extra fields in VfxFrameBuffer
that hold additional state. They are added for the matter of convenience and some are publicly exposed through getters/setters (e.g. VfxFrameBuffer.bufferNesting
, VfxFrameBuffer#drawing
, etc).
- Library Overview
- Connect the Library
- Use the Library
- VFX Frame Buffer
- Built-in Effects
- Changelog