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