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 }