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