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