Home | History | Annotate | Download | only in glutils
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.graphics.glutils;
     18 
     19 import java.nio.ByteBuffer;
     20 import java.nio.ByteOrder;
     21 import java.nio.IntBuffer;
     22 import java.util.HashMap;
     23 import java.util.Map;
     24 
     25 import com.badlogic.gdx.Application;
     26 import com.badlogic.gdx.Application.ApplicationType;
     27 import com.badlogic.gdx.Gdx;
     28 import com.badlogic.gdx.graphics.GL20;
     29 import com.badlogic.gdx.graphics.GLTexture;
     30 import com.badlogic.gdx.graphics.Pixmap;
     31 import com.badlogic.gdx.utils.Array;
     32 import com.badlogic.gdx.utils.Disposable;
     33 
     34 /** <p>
     35  * Encapsulates OpenGL ES 2.0 frame buffer objects. This is a simple helper class which should cover most FBO uses. It will
     36  * automatically create a gltexture for the color attachment and a renderbuffer for the depth buffer. You can get a hold of the
     37  * gltexture by {@link GLFrameBuffer#getColorBufferTexture()}. This class will only work with OpenGL ES 2.0.
     38  * </p>
     39  *
     40  * <p>
     41  * FrameBuffers are managed. In case of an OpenGL context loss, which only happens on Android when a user switches to another
     42  * application or receives an incoming call, the framebuffer will be automatically recreated.
     43  * </p>
     44  *
     45  * <p>
     46  * A FrameBuffer must be disposed if it is no longer needed
     47  * </p>
     48  *
     49  * @author mzechner, realitix */
     50 public abstract class GLFrameBuffer<T extends GLTexture> implements Disposable {
     51 	/** the frame buffers **/
     52 	private final static Map<Application, Array<GLFrameBuffer>> buffers = new HashMap<Application, Array<GLFrameBuffer>>();
     53 
     54 	private final static int GL_DEPTH24_STENCIL8_OES = 0x88F0;
     55 
     56 	/** the color buffer texture **/
     57 	protected T colorTexture;
     58 
     59 	/** the default framebuffer handle, a.k.a screen. */
     60 	private static int defaultFramebufferHandle;
     61 	/** true if we have polled for the default handle already. */
     62 	private static boolean defaultFramebufferHandleInitialized = false;
     63 
     64 	/** the framebuffer handle **/
     65 	private int framebufferHandle;
     66 
     67 	/** the depthbuffer render object handle **/
     68 	private int depthbufferHandle;
     69 
     70 	/** the stencilbuffer render object handle **/
     71 	private int stencilbufferHandle;
     72 
     73 	/** the depth stencil packed render buffer object handle **/
     74 	private int depthStencilPackedBufferHandle;
     75 
     76 	/** width **/
     77 	protected final int width;
     78 
     79 	/** height **/
     80 	protected final int height;
     81 
     82 	/** depth **/
     83 	protected final boolean hasDepth;
     84 
     85 	/** stencil **/
     86 	protected final boolean hasStencil;
     87 
     88 	/** if has depth stencil packed buffer **/
     89 	private boolean hasDepthStencilPackedBuffer;
     90 
     91 	/** format **/
     92 	protected final Pixmap.Format format;
     93 
     94 	/** Creates a new FrameBuffer having the given dimensions and potentially a depth buffer attached.
     95 	 *
     96 	 * @param format
     97 	 * @param width
     98 	 * @param height
     99 	 * @param hasDepth */
    100 	public GLFrameBuffer (Pixmap.Format format, int width, int height, boolean hasDepth) {
    101 		this(format, width, height, hasDepth, false);
    102 	}
    103 
    104 	/** Creates a new FrameBuffer having the given dimensions and potentially a depth and a stencil buffer attached.
    105 	 *
    106 	 * @param format the format of the color buffer; according to the OpenGL ES 2.0 spec, only RGB565, RGBA4444 and RGB5_A1 are
    107 	 *           color-renderable
    108 	 * @param width the width of the framebuffer in pixels
    109 	 * @param height the height of the framebuffer in pixels
    110 	 * @param hasDepth whether to attach a depth buffer
    111 	 * @throws com.badlogic.gdx.utils.GdxRuntimeException in case the FrameBuffer could not be created */
    112 	public GLFrameBuffer (Pixmap.Format format, int width, int height, boolean hasDepth, boolean hasStencil) {
    113 		this.width = width;
    114 		this.height = height;
    115 		this.format = format;
    116 		this.hasDepth = hasDepth;
    117 		this.hasStencil = hasStencil;
    118 		build();
    119 
    120 		addManagedFrameBuffer(Gdx.app, this);
    121 	}
    122 
    123 	/** Override this method in a derived class to set up the backing texture as you like. */
    124 	protected abstract T createColorTexture ();
    125 
    126 	/** Override this method in a derived class to dispose the backing texture as you like. */
    127 	protected abstract void disposeColorTexture (T colorTexture);
    128 
    129 	private void build () {
    130 		GL20 gl = Gdx.gl20;
    131 
    132 		// iOS uses a different framebuffer handle! (not necessarily 0)
    133 		if (!defaultFramebufferHandleInitialized) {
    134 			defaultFramebufferHandleInitialized = true;
    135 			if (Gdx.app.getType() == ApplicationType.iOS) {
    136 				IntBuffer intbuf = ByteBuffer.allocateDirect(16 * Integer.SIZE / 8).order(ByteOrder.nativeOrder()).asIntBuffer();
    137 				gl.glGetIntegerv(GL20.GL_FRAMEBUFFER_BINDING, intbuf);
    138 				defaultFramebufferHandle = intbuf.get(0);
    139 			} else {
    140 				defaultFramebufferHandle = 0;
    141 			}
    142 		}
    143 
    144 		colorTexture = createColorTexture();
    145 
    146 		framebufferHandle = gl.glGenFramebuffer();
    147 
    148 		if (hasDepth) {
    149 			depthbufferHandle = gl.glGenRenderbuffer();
    150 		}
    151 
    152 		if (hasStencil) {
    153 			stencilbufferHandle = gl.glGenRenderbuffer();
    154 		}
    155 
    156 		gl.glBindTexture(GL20.GL_TEXTURE_2D, colorTexture.getTextureObjectHandle());
    157 
    158 		if (hasDepth) {
    159 			gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthbufferHandle);
    160 			gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL20.GL_DEPTH_COMPONENT16, colorTexture.getWidth(),
    161 				colorTexture.getHeight());
    162 		}
    163 
    164 		if (hasStencil) {
    165 			gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, stencilbufferHandle);
    166 			gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL20.GL_STENCIL_INDEX8, colorTexture.getWidth(), colorTexture.getHeight());
    167 		}
    168 
    169 		gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebufferHandle);
    170 		gl.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, GL20.GL_COLOR_ATTACHMENT0, GL20.GL_TEXTURE_2D,
    171 			colorTexture.getTextureObjectHandle(), 0);
    172 		if (hasDepth) {
    173 			gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT, GL20.GL_RENDERBUFFER, depthbufferHandle);
    174 		}
    175 
    176 		if (hasStencil) {
    177 			gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_STENCIL_ATTACHMENT, GL20.GL_RENDERBUFFER, stencilbufferHandle);
    178 		}
    179 
    180 		gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
    181 		gl.glBindTexture(GL20.GL_TEXTURE_2D, 0);
    182 
    183 		int result = gl.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER);
    184 
    185 		if (result == GL20.GL_FRAMEBUFFER_UNSUPPORTED && hasDepth && hasStencil
    186 			&& (Gdx.graphics.supportsExtension("GL_OES_packed_depth_stencil") ||
    187 				Gdx.graphics.supportsExtension("GL_EXT_packed_depth_stencil"))) {
    188 			if (hasDepth) {
    189 				gl.glDeleteRenderbuffer(depthbufferHandle);
    190 				depthbufferHandle = 0;
    191 			}
    192 			if (hasStencil) {
    193 				gl.glDeleteRenderbuffer(stencilbufferHandle);
    194 				stencilbufferHandle = 0;
    195 			}
    196 
    197 			depthStencilPackedBufferHandle = gl.glGenRenderbuffer();
    198 			hasDepthStencilPackedBuffer = true;
    199 			gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
    200 			gl.glRenderbufferStorage(GL20.GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, colorTexture.getWidth(), colorTexture.getHeight());
    201 			gl.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
    202 
    203 			gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_DEPTH_ATTACHMENT, GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
    204 			gl.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, GL20.GL_STENCIL_ATTACHMENT, GL20.GL_RENDERBUFFER, depthStencilPackedBufferHandle);
    205 			result = gl.glCheckFramebufferStatus(GL20.GL_FRAMEBUFFER);
    206 		}
    207 
    208 		gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, defaultFramebufferHandle);
    209 
    210 		if (result != GL20.GL_FRAMEBUFFER_COMPLETE) {
    211 			disposeColorTexture(colorTexture);
    212 
    213 			if (hasDepthStencilPackedBuffer) {
    214 				gl.glDeleteBuffer(depthStencilPackedBufferHandle);
    215 			} else {
    216 				if (hasDepth) gl.glDeleteRenderbuffer(depthbufferHandle);
    217 				if (hasStencil) gl.glDeleteRenderbuffer(stencilbufferHandle);
    218 			}
    219 
    220 			gl.glDeleteFramebuffer(framebufferHandle);
    221 
    222 			if (result == GL20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
    223 				throw new IllegalStateException("frame buffer couldn't be constructed: incomplete attachment");
    224 			if (result == GL20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS)
    225 				throw new IllegalStateException("frame buffer couldn't be constructed: incomplete dimensions");
    226 			if (result == GL20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
    227 				throw new IllegalStateException("frame buffer couldn't be constructed: missing attachment");
    228 			if (result == GL20.GL_FRAMEBUFFER_UNSUPPORTED)
    229 				throw new IllegalStateException("frame buffer couldn't be constructed: unsupported combination of formats");
    230 			throw new IllegalStateException("frame buffer couldn't be constructed: unknown error " + result);
    231 		}
    232 	}
    233 
    234 	/** Releases all resources associated with the FrameBuffer. */
    235 	@Override
    236 	public void dispose () {
    237 		GL20 gl = Gdx.gl20;
    238 
    239 		disposeColorTexture(colorTexture);
    240 
    241 		if (hasDepthStencilPackedBuffer) {
    242 			gl.glDeleteRenderbuffer(depthStencilPackedBufferHandle);
    243 		} else {
    244 			if (hasDepth) gl.glDeleteRenderbuffer(depthbufferHandle);
    245 			if (hasStencil) gl.glDeleteRenderbuffer(stencilbufferHandle);
    246 		}
    247 
    248 		gl.glDeleteFramebuffer(framebufferHandle);
    249 
    250 		if (buffers.get(Gdx.app) != null) buffers.get(Gdx.app).removeValue(this, true);
    251 	}
    252 
    253 	/** Makes the frame buffer current so everything gets drawn to it. */
    254 	public void bind () {
    255 		Gdx.gl20.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebufferHandle);
    256 	}
    257 
    258 	/** Unbinds the framebuffer, all drawing will be performed to the normal framebuffer from here on. */
    259 	public static void unbind () {
    260 		Gdx.gl20.glBindFramebuffer(GL20.GL_FRAMEBUFFER, defaultFramebufferHandle);
    261 	}
    262 
    263 	/** Binds the frame buffer and sets the viewport accordingly, so everything gets drawn to it. */
    264 	public void begin () {
    265 		bind();
    266 		setFrameBufferViewport();
    267 	}
    268 
    269 	/** Sets viewport to the dimensions of framebuffer. Called by {@link #begin()}. */
    270 	protected void setFrameBufferViewport () {
    271 		Gdx.gl20.glViewport(0, 0, colorTexture.getWidth(), colorTexture.getHeight());
    272 	}
    273 
    274 	/** Unbinds the framebuffer, all drawing will be performed to the normal framebuffer from here on. */
    275 	public void end () {
    276 		end(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight());
    277 	}
    278 
    279 	/** Unbinds the framebuffer and sets viewport sizes, all drawing will be performed to the normal framebuffer from here on.
    280 	 *
    281 	 * @param x the x-axis position of the viewport in pixels
    282 	 * @param y the y-asis position of the viewport in pixels
    283 	 * @param width the width of the viewport in pixels
    284 	 * @param height the height of the viewport in pixels */
    285 	public void end (int x, int y, int width, int height) {
    286 		unbind();
    287 		Gdx.gl20.glViewport(x, y, width, height);
    288 	}
    289 
    290 	/** @return the gl texture */
    291 	public T getColorBufferTexture () {
    292 		return colorTexture;
    293 	}
    294 
    295 	/** @return The OpenGL handle of the framebuffer (see {@link GL20#glGenFramebuffer()}) */
    296 	public int getFramebufferHandle () {
    297 		return framebufferHandle;
    298 	}
    299 
    300 	/** @return The OpenGL handle of the (optional) depth buffer (see {@link GL20#glGenRenderbuffer()}). May return 0 even if depth buffer enabled */
    301 	public int getDepthBufferHandle () {
    302 		return depthbufferHandle;
    303 	}
    304 
    305 	/** @return The OpenGL handle of the (optional) stencil buffer (see {@link GL20#glGenRenderbuffer()}). May return 0 even if stencil buffer enabled */
    306 	public int getStencilBufferHandle () {
    307 		return stencilbufferHandle;
    308 	}
    309 
    310 	/** @return The OpenGL handle of the packed depth & stencil buffer (GL_DEPTH24_STENCIL8_OES) or 0 if not used. **/
    311 	protected int getDepthStencilPackedBuffer () {
    312 		return depthStencilPackedBufferHandle;
    313 	}
    314 
    315 	/** @return the height of the framebuffer in pixels */
    316 	public int getHeight () {
    317 		return colorTexture.getHeight();
    318 	}
    319 
    320 	/** @return the width of the framebuffer in pixels */
    321 	public int getWidth () {
    322 		return colorTexture.getWidth();
    323 	}
    324 
    325 	/** @return the depth of the framebuffer in pixels (if applicable) */
    326 	public int getDepth () {
    327 		return colorTexture.getDepth();
    328 	}
    329 
    330 	private static void addManagedFrameBuffer (Application app, GLFrameBuffer frameBuffer) {
    331 		Array<GLFrameBuffer> managedResources = buffers.get(app);
    332 		if (managedResources == null) managedResources = new Array<GLFrameBuffer>();
    333 		managedResources.add(frameBuffer);
    334 		buffers.put(app, managedResources);
    335 	}
    336 
    337 	/** Invalidates all frame buffers. This can be used when the OpenGL context is lost to rebuild all managed frame buffers. This
    338 	 * assumes that the texture attached to this buffer has already been rebuild! Use with care. */
    339 	public static void invalidateAllFrameBuffers (Application app) {
    340 		if (Gdx.gl20 == null) return;
    341 
    342 		Array<GLFrameBuffer> bufferArray = buffers.get(app);
    343 		if (bufferArray == null) return;
    344 		for (int i = 0; i < bufferArray.size; i++) {
    345 			bufferArray.get(i).build();
    346 		}
    347 	}
    348 
    349 	public static void clearAllFrameBuffers (Application app) {
    350 		buffers.remove(app);
    351 	}
    352 
    353 	public static StringBuilder getManagedStatus (final StringBuilder builder) {
    354 		builder.append("Managed buffers/app: { ");
    355 		for (Application app : buffers.keySet()) {
    356 			builder.append(buffers.get(app).size);
    357 			builder.append(" ");
    358 		}
    359 		builder.append("}");
    360 		return builder;
    361 	}
    362 
    363 	public static String getManagedStatus () {
    364 		return getManagedStatus(new StringBuilder()).toString();
    365 	}
    366 }
    367