Skip to content

Commit

Permalink
Adds support for nested buffers; closes #2.
Browse files Browse the repository at this point in the history
  • Loading branch information
crykn committed Mar 3, 2020
1 parent 575d3bc commit 85aba41
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 5 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ public class MyGdxGame extends ManagedGame {

**Some additional notes:**

* Screens and transitions are intended as classes that are instantiated _once_ at the beginning. Their state can be reset in `Screen#show()`/`ScreenTransition#reset()`, if needed.
* The screen's/transitions's `create()` method is either called when they are first shown or when they are manually initialized. The latter is mostly done by a loading screen after the screen's assets have been loaded.
* Input processors have to be added in a screen via `ManagedScreen#addInputProcessor(...)`. This is needed so the input processors can be automatically registered/unregistered when the screen is shown/hidden.
* After a screen was pushed, the actual change of the screen happens in the first `game.render(...)` call after that. This ensures that `Screen#show()` and `Screen#hide()` are only called on the rendering thread.
* If there is a transition still going on while a new one is pushed, the new one is queued until the current one is finished.


## How the library works in detail

The life-cycle of a screen that is pushed is detailed [here](https://github.com/crykn/libgdx-screenmanager/wiki/A-screen's-lifecycle).
The following wiki entries detail some features of the library:

- The [life-cycle of a screen](https://github.com/crykn/libgdx-screenmanager/wiki/A-screen's-lifecycle) that is pushed
- The [custom FrameBuffer implementation](https://github.com/crykn/libgdx-screenmanager/wiki/Custom-FrameBuffer-implementation) that allows nested fbos
- Where to [initialize the screens & transitions](https://github.com/crykn/libgdx-screenmanager/wiki/Where-to-initialize-screens-and-transitions)
5 changes: 5 additions & 0 deletions src/main/java/de/eskalon/commons/screen/ScreenManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import de.eskalon.commons.input.BasicInputMultiplexer;
import de.eskalon.commons.screen.transition.ScreenTransition;
import de.eskalon.commons.utils.Tuple;
import de.eskalon.commons.utils.graphics.NestableFrameBuffer;

/**
* A screen manager that handles the different screens of a game and their
Expand All @@ -51,6 +52,10 @@
* {@link #addScreen(String, ManagedScreen)} and
* {@link #addScreenTransition(String, ScreenTransition)}. To actually show a
* screen, push it via {@link #pushScreen(String, String)}.
* <p>
* As the screen manager is using framebuffers internally, screens and
* transitions have to use a {@link NestableFrameBuffer} if they want to use
* framebuffers as well.
*
* @author damios
*
Expand Down
78 changes: 78 additions & 0 deletions src/main/java/de/eskalon/commons/utils/graphics/GLUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2020 damios
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package de.eskalon.commons.utils.graphics;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.utils.GdxRuntimeException;

/**
* OpenGL utilities.
*
* @author damios
*/
public class GLUtils {

/**
* The buffer used. A size of 64 bytes is required as at most 16 integer
* elements can be returned.
*/
private static final IntBuffer USED_INT_BUFF = ByteBuffer
.allocateDirect(16 * Integer.BYTES).order(ByteOrder.nativeOrder())
.asIntBuffer();

/**
* Returns the name of the currently bound framebuffer
* ({@code GL_FRAMEBUFFER_BINDING}).
* <p>
* Doesn't work in WebGL!
*
* @return returns a single value, the name of the currently bound
* framebuffer. The initial value is {@code 0}, indicating the
* default framebuffer.
*
* @see @see <a href= "https://github.com/libgdx/libgdx/issues/4688">The
* libGDX issue detailing the WebGL problems</a>
*/
public static synchronized int getBoundFboHandle() {
if (Gdx.app.getType() == ApplicationType.WebGL)
throw new GdxRuntimeException(
"This operation is not supported on WebGL.");

IntBuffer intBuf = USED_INT_BUFF;
Gdx.gl.glGetIntegerv(GL20.GL_FRAMEBUFFER_BINDING, intBuf);
return intBuf.get(0);
}

/**
* @return the current gl viewport ({@code GL_VIEWPORT}) as an array,
* containing four values: the x and y window coordinates of the
* viewport, followed by its width and height.
*/
public static synchronized int[] getViewport() {
IntBuffer intBuf = USED_INT_BUFF;
Gdx.gl.glGetIntegerv(GL20.GL_VIEWPORT, intBuf);

return new int[] { intBuf.get(0), intBuf.get(1), intBuf.get(2),
intBuf.get(3) };
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2020 damios
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package de.eskalon.commons.utils.graphics;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.GLFrameBuffer;
import com.badlogic.gdx.graphics.glutils.GLFrameBuffer.FrameBufferBuilder;

/**
* An implementation of the libGDX {@link FrameBuffer} that supports nested
* framebuffers. This allows using multiple framebuffers inside each other:
*
* <pre>
* {@code
* fbo0.begin();
* // Stuff is rendered into fbo0
* fbo1.begin();
* // Stuff is rendered into fbo1
* fbo1.end();
* // Stuff is rendered into fbo0 again
* // this is where the default FrameBuffer implementation would break
* fbo0.end();
* }
* </pre>
*
* @author damios
* @see <a href=
* "https://github.com/crykn/libgdx-screenmanager/wiki/Custom-FrameBuffer-implementation">The
* wiki entry detailing the reasoning behind the implementation</a>
*/
public class NestableFrameBuffer extends FrameBuffer {

private int previousFBOHandle = -1;
private int[] previousViewport = new int[4];
private boolean isActive = false;

/**
* Creates a new NestableFrameBuffer having the given dimensions and
* potentially a depth buffer attached.
*
* @param format
* the format of the color buffer; according to the OpenGL ES 2.0
* spec, only {@link Format#RGB565}, {@link Format#RGBA4444} and
* {@link Format#RGB5_A1} are color-renderable
* @param width
* the width of the framebuffer in pixels
* @param height
* the height of the framebuffer in pixels
* @param hasDepth
* whether to attach a depth buffer
*/
public NestableFrameBuffer(Pixmap.Format format, int width, int height,
boolean hasDepth) {
super(format, width, height, hasDepth, false);
}

protected NestableFrameBuffer(
GLFrameBufferBuilder<? extends GLFrameBuffer<Texture>> bufferBuilder) {
super(bufferBuilder);
}

/**
* Binds the frame buffer and sets the viewport accordingly, so everything
* gets drawn to it.
*/
@Override
public void begin() {
if (isActive)
throw new IllegalStateException(
"end() has to be called before another draw can begin!");
isActive = true;

previousFBOHandle = GLUtils.getBoundFboHandle();
bind();

previousViewport = GLUtils.getViewport();
setFrameBufferViewport();
}

/**
* Unbinds the framebuffer, all drawing will be performed to the
* {@linkplain #previousFBOHandle previous framebuffer} (usually the normal
* one) from here on.
*/
@Override
public void end() {
end(previousViewport[0], previousViewport[1], previousViewport[2],
previousViewport[3]);
}

/**
* Unbinds the framebuffer and sets viewport sizes, all drawing will be
* performed to the {@linkplain #previousFBOHandle previous framebuffer}
* (usually the normal one) from here on.
*
* @param x
* the x-axis position of the viewport in pixels
* @param y
* the y-asis position of the viewport in pixels
* @param width
* the width of the viewport in pixels
* @param height
* the height of the viewport in pixels
*/
@Override
public void end(int x, int y, int width, int height) {
if (!isActive)
throw new IllegalStateException("begin() has to be called first!");
isActive = false;

if (GLUtils.getBoundFboHandle() != framebufferHandle) {
throw new IllegalStateException(
"The currently bound framebuffer doesn't match this one. Make sure the nested framebuffers are closed in the same order they were opened in! ");
}

Gdx.gl20.glBindFramebuffer(GL20.GL_FRAMEBUFFER, previousFBOHandle);
Gdx.gl20.glViewport(x, y, width, height);
}

public static class NestableFrameBufferBuilder extends FrameBufferBuilder {
public NestableFrameBufferBuilder(int width, int height) {
super(width, height);
}

@Override
public FrameBuffer build() {
return new NestableFrameBuffer(this);
}
}

}

0 comments on commit 85aba41

Please sign in to comment.