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