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