1 module gfm.opengl.fbo; 2 3 import std.string; 4 5 import derelict.opengl3.gl3; 6 7 import std.logger; 8 9 import gfm.opengl.opengl, 10 gfm.opengl.texture, 11 gfm.opengl.renderbuffer; 12 13 /// OpenGL FrameBuffer Object wrapper. 14 final class GLFBO 15 { 16 public 17 { 18 /// FBO usage. 19 enum Usage 20 { 21 DRAW, /// This FBO will be used for drawing. 22 READ /// This FBO will be used for reading. 23 } 24 25 /// Creates one FBO, with specified usage. OpenGL must have been loaded. 26 /// $(D EXT_framebuffer_object) is used as a fallback if $(D ARB_framebuffer_object) is missing. 27 /// Throws: $(D OpenGLException) on error. 28 this(OpenGL gl, Usage usage = Usage.DRAW) 29 { 30 if (glGenFramebuffers !is null) 31 _useEXTFallback = false; 32 else if (EXT_framebuffer_object()) 33 { 34 gl._logger.warning("ARB_framebuffer_object missing, using EXT_framebuffer_object as fallback"); 35 _useEXTFallback = true; 36 } 37 else 38 throw new OpenGLException("Neither ARB_framebuffer_object nor EXT_framebuffer_object are supported"); 39 40 if (_useEXTFallback && usage == Usage.READ) 41 throw new OpenGLException("Unable to read FBO using EXT_framebuffer_object"); 42 43 _gl = gl; 44 if (_useEXTFallback) 45 glGenFramebuffersEXT(1, &_handle); 46 else 47 glGenFramebuffers(1, &_handle); 48 _gl.runtimeCheck(); 49 50 _colors.length = _gl.maxColorAttachments(); 51 for(int i = 0; i < _colors.length; ++i) 52 _colors[i] = new GLFBOAttachment(this, GL_COLOR_ATTACHMENT0 + i); 53 54 _depth = new GLFBOAttachment(this, GL_DEPTH_ATTACHMENT); 55 _stencil = new GLFBOAttachment(this, GL_STENCIL_ATTACHMENT); 56 _depthStencil = new GLFBOAttachment(this, GL_DEPTH_STENCIL_ATTACHMENT); 57 58 _usage = usage; 59 final switch(usage) 60 { 61 case Usage.DRAW: 62 _target = GL_DRAW_FRAMEBUFFER; 63 break; 64 case Usage.READ: 65 _target = GL_READ_FRAMEBUFFER; 66 } 67 _initialized = true; 68 _isBound = false; 69 } 70 71 ~this() 72 { 73 close(); 74 } 75 76 /// Releases the OpenGL FBO resource. 77 void close() 78 { 79 if (_initialized) 80 { 81 // detach all 82 for(int i = 0; i < _colors.length; ++i) 83 _colors[i].close(); 84 85 _depth.close(); 86 _stencil.close(); 87 88 if (_useEXTFallback) 89 glDeleteFramebuffersEXT(1, &_handle); 90 else 91 glDeleteFramebuffers(1, &_handle); 92 _initialized = false; 93 } 94 } 95 96 /// Binds this FBO. 97 /// Throws: $(D OpenGLException) on error. 98 void use() 99 { 100 if (_useEXTFallback) 101 glBindFramebufferEXT(_target, _handle); 102 else 103 glBindFramebuffer(_target, _handle); 104 105 _gl.runtimeCheck(); 106 _isBound = true; 107 108 for(int i = 0; i < _colors.length; ++i) 109 _colors[i].updateAttachment(); 110 } 111 112 /// Unbinds this FBO. 113 /// Throws: $(D OpenGLException) on error. 114 void unuse() 115 { 116 _isBound = false; 117 if (_useEXTFallback) 118 glBindFramebufferEXT(_target, 0); 119 else 120 glBindFramebuffer(_target, 0); 121 122 _gl.runtimeCheck(); 123 } 124 125 /// Returns: A FBO color attachment. 126 /// Params: 127 /// i = Index of color attachment. 128 GLFBOAttachment color(int i) 129 { 130 return _colors[i]; 131 } 132 133 /// Returns: FBO depth attachment. 134 GLFBOAttachment depth() 135 { 136 return _depth; 137 } 138 139 /// Returns: FBO stencil attachment. 140 GLFBOAttachment stencil() 141 { 142 return _stencil; 143 } 144 } 145 146 private 147 { 148 OpenGL _gl; 149 GLuint _handle; 150 bool _initialized, _isBound; 151 bool _useEXTFallback; 152 153 // attachements 154 GLFBOAttachment[] _colors; 155 GLFBOAttachment _depth, _stencil, _depthStencil; 156 157 Usage _usage; 158 GLenum _target; // redundant 159 160 void checkStatus() 161 { 162 GLenum status = void; 163 if (_useEXTFallback) 164 status = glCheckFramebufferStatusEXT(_target); 165 else 166 status = glCheckFramebufferStatus(_target); 167 168 switch(status) 169 { 170 case GL_FRAMEBUFFER_COMPLETE: 171 return; 172 173 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: 174 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); 175 176 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: 177 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); 178 179 case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: 180 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT"); 181 182 case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: 183 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT"); 184 185 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: 186 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); 187 188 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: 189 throw new OpenGLException("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); 190 191 case GL_FRAMEBUFFER_UNSUPPORTED: 192 throw new OpenGLException("GL_FRAMEBUFFER_UNSUPPORTED"); 193 194 default: throw new OpenGLException("Unknown FBO error"); 195 } 196 } 197 } 198 } 199 200 /// Defines one FBO attachment. 201 class GLFBOAttachment 202 { 203 public 204 { 205 /// Attaches a 1D texture to the FBO. 206 /// Throws: $(D OpenGLException) on error. 207 void attach(GLTexture1D tex, int level = 0) 208 { 209 _newCall = Call(this, Call.Type.TEXTURE_1D, tex, null, level, 0); 210 updateAttachment(); 211 } 212 213 /// Attaches a 2D texture to the FBO. 214 /// Throws: $(D OpenGLException) on error. 215 void attach(GLTexture2D tex, int level = 0) 216 { 217 _newCall = Call(this, Call.Type.TEXTURE_2D, tex, null, level, 0); 218 updateAttachment(); 219 } 220 221 /// Attaches a 3D texture to the FBO. 222 /// Throws: $(D OpenGLException) on error. 223 void attach(GLTexture3D tex, int layer, int level) 224 { 225 _newCall = Call(this, Call.Type.TEXTURE_3D, tex, null, level, layer); 226 updateAttachment(); 227 } 228 229 /// Attaches a 1D texture array to the FBO. 230 /// Throws: $(D OpenGLException) on error. 231 void attach(GLTexture1DArray tex, int layer) 232 { 233 _newCall = Call(this, Call.Type.TEXTURE_3D, tex, null, 0, layer); 234 updateAttachment(); 235 } 236 237 /// Attaches a 2D texture array to the FBO. 238 /// Throws: $(D OpenGLException) on error. 239 void attach(GLTexture2DArray tex, int layer) 240 { 241 _newCall = Call(this, Call.Type.TEXTURE_3D, tex, null, 0, layer); 242 updateAttachment(); 243 } 244 245 /// Attaches a rectangle texture to the FBO. 246 /// Throws: $(D OpenGLException) on error. 247 void attach(GLTextureRectangle tex) 248 { 249 _newCall = Call(this, Call.Type.TEXTURE_2D, tex, null, 0, 0); 250 updateAttachment(); 251 } 252 253 /// Attaches a multisampled 2D texture to the FBO. 254 /// Throws: $(D OpenGLException) on error. 255 void attach(GLTexture2DMultisample tex) 256 { 257 _newCall = Call(this, Call.Type.TEXTURE_2D, tex, null, 0, 0); 258 updateAttachment(); 259 } 260 261 /// Attaches a multisampled 2D texture array to the FBO. 262 /// Throws: $(D OpenGLException) on error. 263 void attach(GLTexture2DMultisampleArray tex, int layer) 264 { 265 _newCall = Call(this, Call.Type.TEXTURE_3D, tex, null, 0, layer); 266 updateAttachment(); 267 } 268 269 /// Attaches a renderbuffer to the FBO. 270 /// Throws: $(D OpenGLException) on error. 271 void attach(GLRenderBuffer buffer) 272 { 273 _newCall = Call(this, Call.Type.RENDERBUFFER, null, buffer, 0, 0); 274 updateAttachment(); 275 } 276 } 277 278 private 279 { 280 this(GLFBO fbo, GLenum attachment) 281 { 282 _fbo = fbo; 283 _gl = fbo._gl; 284 _attachment = attachment; 285 _lastCall = _newCall = Call(this, Call.Type.DISABLED, null, null, 0, 0); 286 } 287 288 // guaranteed to be called once 289 void close() 290 { 291 _lastCall.detach(_fbo._useEXTFallback); 292 } 293 294 OpenGL _gl; 295 GLFBO _fbo; 296 GLenum _attachment; 297 Call _lastCall; 298 Call _newCall; 299 300 void updateAttachment() 301 { 302 if (_newCall != _lastCall && _fbo._isBound) 303 { 304 try 305 { 306 // trying to detach existing attachment 307 // would that help? 308 _lastCall.detach(_fbo._useEXTFallback); 309 } 310 catch(OpenGLException e) 311 { 312 // ignoring errors here 313 } 314 315 _newCall.attach(_fbo._useEXTFallback); 316 _lastCall = _newCall; 317 } 318 } 319 320 struct Call 321 { 322 public 323 { 324 enum Type 325 { 326 DISABLED, 327 TEXTURE_1D, 328 TEXTURE_2D, 329 TEXTURE_3D, 330 RENDERBUFFER 331 } 332 333 GLFBOAttachment _outer; 334 Type _type; 335 GLTexture _texture; 336 GLRenderBuffer _renderbuffer; 337 GLint _level; 338 GLint _layer; 339 340 void attach(bool useEXTFallback) 341 { 342 attachOrDetach(useEXTFallback, _texture.handle(), _renderbuffer._handle); 343 } 344 345 void detach(bool useEXTFallback) 346 { 347 attachOrDetach(useEXTFallback, 0, 0); 348 } 349 350 void attachOrDetach(bool useEXTFallback, GLuint textureHandle, GLuint renderBufferHandle) 351 { 352 final switch(_type) 353 { 354 case Type.DISABLED: 355 return; // do nothing 356 357 case Type.TEXTURE_1D: 358 if (useEXTFallback) 359 glFramebufferTexture1DEXT(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level); 360 else 361 glFramebufferTexture1D(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level); 362 break; 363 364 case Type.TEXTURE_2D: 365 if (useEXTFallback) 366 glFramebufferTexture2DEXT(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level); 367 else 368 glFramebufferTexture2D(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level); 369 break; 370 371 case Type.TEXTURE_3D: 372 if (useEXTFallback) 373 glFramebufferTexture3DEXT(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level, _layer); 374 else 375 glFramebufferTexture3D(_outer._fbo._target, _outer._attachment, _texture._target, textureHandle, _level, _layer); 376 break; 377 378 case Type.RENDERBUFFER: 379 if (useEXTFallback) 380 glFramebufferRenderbufferEXT(_outer._fbo._target, _outer._attachment, GL_RENDERBUFFER, renderBufferHandle); 381 else 382 glFramebufferRenderbuffer(_outer._fbo._target, _outer._attachment, GL_RENDERBUFFER, renderBufferHandle); 383 break; 384 } 385 _outer._gl.runtimeCheck(); 386 } 387 } 388 } 389 } 390 }