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