1 /*
2  *  Copyright (c) 2012-2014, Bruno Levy
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *
8  *  * Redistributions of source code must retain the above copyright notice,
9  *  this list of conditions and the following disclaimer.
10  *  * Redistributions in binary form must reproduce the above copyright notice,
11  *  this list of conditions and the following disclaimer in the documentation
12  *  and/or other materials provided with the distribution.
13  *  * Neither the name of the ALICE Project-Team nor the names of its
14  *  contributors may be used to endorse or promote products derived from this
15  *  software without specific prior written permission.
16  *
17  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  *  POSSIBILITY OF SUCH DAMAGE.
28  *
29  *  If you modify this software, you should include a notice giving the
30  *  name of the person performing the modification, the date of modification,
31  *  and the reason for such modification.
32  *
33  *  Contact: Bruno Levy
34  *
35  *     Bruno.Levy@inria.fr
36  *     http://www.loria.fr/~levy
37  *
38  *     ALICE Project
39  *     LORIA, INRIA Lorraine,
40  *     Campus Scientifique, BP 239
41  *     54506 VANDOEUVRE LES NANCY CEDEX
42  *     FRANCE
43  *
44  */
45 
46 #include <geogram_gfx/basic/frame_buffer_object.h>
47 #include <geogram_gfx/basic/GLSL.h>
48 #include <geogram/basic/logger.h>
49 #include <string>
50 
51 #ifdef GEO_OS_EMSCRIPTEN
52 #include <emscripten.h>
53 #endif
54 
55 namespace {
56     using namespace GEO;
57 
58     /**
59      * \brief Allocates the currently bound GL_TEXTURE_2D object.
60      * \param[in] width , height size of the texture.
61      * \param[in] internalformat internal format for the texture.
62      * \details Under WebGL2, if a generic internal format is specified,
63      *  then a specific one is chosen (generic internal formats are
64      *  not supported under WebGL2).
65      */
allocate_texture_2D(index_t width,index_t height,GLint internalformat)66     void allocate_texture_2D(
67 	index_t width, index_t height, GLint internalformat
68     ) {
69 
70       // - glTexImage2d in WebGL2 is very picky on what combination of
71       //   internalformat/format/type is accepted, and it needs a full
72       //   specification of internalformat (for instance, GL_RGBA8 instead
73       //   of GL_RGBA)
74       //   See
75       //   https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html
76       //   for the valid combinations
77       //   OpenGL ES 3.0: see:
78       //   https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml
79       //
80       // - not all internalformat defines are available in Emscripten,
81       //   added the ones that I need in geogram_gfx/basic/GL.h
82       //
83       // TODO: initialize the WebGL extension "floating point render target"
84       // if needed
85 
86 	GLenum format=0;
87 	GLenum type=0;
88 
89 	switch(internalformat) {
90 	    case GL_RGBA:
91 		format = GL_RGBA;
92 #if defined(GEO_OS_EMSCRIPTEN) && !defined(GEO_WEBGL2)
93 		type = GL_FLOAT;
94 #else
95 		type = GL_UNSIGNED_BYTE;
96 #endif
97 		break;
98 	    case GL_RGB:
99 		format = GL_RGB;
100 		type = GL_UNSIGNED_BYTE;
101 		break;
102 	    case GL_RED:
103 		format = GL_RED;
104 		type = GL_UNSIGNED_BYTE;
105 		break;
106 	    case GL_R16:
107 		format = GL_RED;
108 		type = GL_SHORT;
109 		break;
110 	    case GL_R32F:
111 		format = GL_RED;
112 		type = GL_FLOAT;
113 		break;
114 	    case GL_R16F:
115 		format = GL_RED;
116 		type = GL_FLOAT;
117 		break;
118 	    case GL_DEPTH_COMPONENT:
119 		format = GL_DEPTH_COMPONENT;
120 		type = GL_UNSIGNED_INT;
121 		break;
122 	}
123 
124 #if (defined(GEO_OS_EMSCRIPTEN) && defined(GEO_WEBGL2)) || defined(GEO_OS_ANDROID)
125 	// Replace generic internal format with specific
126 	// one, as required by WebGL2 and ES3
127 	if(internalformat == GL_RGBA) {
128 	    internalformat = GL_RGBA8;
129 	} else if(internalformat == GL_RGB) {
130 	    internalformat = GL_RGB8;
131 	} else if(internalformat == GL_DEPTH_COMPONENT) {
132 	    internalformat = GL_DEPTH_COMPONENT24;
133 	} else if(internalformat == GL_RED) {
134 	    internalformat = GL_R8;
135 	}
136 #endif
137 
138 	// If the following assertions fail, then
139 	// this means that the internalformat that
140 	// was given is not taken into account (just
141 	// add it in the previous switch() and cascading
142 	// ifs).
143 	geo_assert(format != 0);
144 	geo_assert(type != 0);
145 
146 	glTexImage2D(
147 	    GL_TEXTURE_2D, 0,
148 	    internalformat,
149 	    GLsizei(width), GLsizei(height), 0,
150 	    format,
151 	    type,
152 	    nullptr
153 	);
154     }
155 }
156 
157 namespace GEO {
158 
FrameBufferObject()159     FrameBufferObject::FrameBufferObject() :
160         frame_buffer_id(0),
161         depth_buffer_id(0),
162         offscreen_id(0),
163         width(0),
164         height(0),
165         previous_frame_buffer_id(0)
166     {
167     }
168 
~FrameBufferObject()169     FrameBufferObject::~FrameBufferObject() {
170         if(offscreen_id != 0) {
171             glDeleteTextures(1, &offscreen_id);
172             offscreen_id = 0;
173         }
174         if(depth_buffer_id != 0) {
175             glDeleteTextures(1, &depth_buffer_id);
176             depth_buffer_id = 0;
177         }
178         if(frame_buffer_id != 0) {
179             glBindFramebuffer(GL_FRAMEBUFFER, previous_frame_buffer_id);
180             glDeleteFramebuffers(1, &frame_buffer_id);
181         }
182     }
183 
resize(index_t new_width,index_t new_height)184     void FrameBufferObject::resize(index_t new_width, index_t new_height) {
185         if(width != new_width || height != new_height) {
186             width  = new_width;
187             height = new_height;
188 
189 	    GEO_CHECK_GL();
190             glBindTexture(GL_TEXTURE_2D, offscreen_id);
191 	    allocate_texture_2D(width, height, internal_storage);
192 	    GEO_CHECK_GL();
193             if(depth_buffer_id != 0) {
194                 glBindTexture(GL_TEXTURE_2D, depth_buffer_id);
195 		GEO_CHECK_GL();
196 		allocate_texture_2D(width, height, GL_DEPTH_COMPONENT);
197 		GEO_CHECK_GL();
198             }
199             glBindTexture(GL_TEXTURE_2D, 0);
200         }
201     }
202 
initialize(index_t width_in,index_t height_in,bool with_depth_buffer,GLint internal_storage_in,bool mipmaps)203     bool FrameBufferObject::initialize(
204         index_t width_in, index_t height_in,
205         bool with_depth_buffer, GLint internal_storage_in,
206         bool mipmaps
207     ) {
208 
209 	GEO_CHECK_GL();
210 
211         //  Get the id of the default frame buffer used by the
212         // context. It will be 0 if it is a regular OpenGL context,
213         // or it can be a bound frame buffer object since Qt5.4
214         // QOpenGLWidget uses a frame buffer to implement light weight
215         // OpenGL contexts. Note that it may change when the Qt rendering
216 	// context is resized (see bind_as_framebuffer()).
217 	glGetIntegerv(
218 	    GL_FRAMEBUFFER_BINDING, (GLint*)(&previous_frame_buffer_id)
219 	);
220 
221         width = width_in;
222         height = height_in;
223         internal_storage = internal_storage_in;
224 
225         // Generate frame buffer object then bind it.
226         glGenFramebuffers(1, &frame_buffer_id);
227         glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id);
228 
229 	GEO_CHECK_GL();
230 
231         if(with_depth_buffer) {
232             glGenTextures(1, &depth_buffer_id);
233         }
234 
235 	GEO_CHECK_GL();
236 
237         // Create the texture we will be using to render to.
238         glGenTextures(1, &offscreen_id);
239         glBindTexture(GL_TEXTURE_2D, offscreen_id);
240 
241 	GEO_CHECK_GL();
242 
243 	if(!mipmaps) {
244 	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
245 	    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
246 	}
247 
248 	GEO_CHECK_GL();
249 
250         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
251         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
252 
253 	GEO_CHECK_GL();
254 	allocate_texture_2D(width, height, internal_storage);
255 	GEO_CHECK_GL();
256 
257         // Bind the texture to the frame buffer.
258         glFramebufferTexture2D(
259             GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
260             GL_TEXTURE_2D, offscreen_id, 0
261         );
262 
263 	GEO_CHECK_GL();
264 
265         // Initialize the depth buffer.
266         if(with_depth_buffer) {
267 
268             glBindTexture(GL_TEXTURE_2D, depth_buffer_id);
269 	    allocate_texture_2D(width, height, GL_DEPTH_COMPONENT);
270 	    GEO_CHECK_GL();
271 
272             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
273             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
274             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
275             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
276 
277 	    GEO_CHECK_GL();
278 
279             glFramebufferTexture2D(
280                 GL_FRAMEBUFFER,
281 		GL_DEPTH_ATTACHMENT,
282                 GL_TEXTURE_2D, depth_buffer_id, 0
283             );
284 
285 	    GEO_CHECK_GL();
286         }
287 
288 	GEO_CHECK_GL();
289 
290 //      Heard it said that DrawBuffer and ReadBuffer
291 //      should be set to NONE before checking frame buffer
292 //      status, but it seems to be unnecessary (commented-out
293 //      for now)
294 //
295 //	glDrawBuffer(GL_NONE);
296 //	glReadBuffer(GL_NONE);
297 
298         // Make sure we have not errors.
299         if(
300             glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
301             GL_FRAMEBUFFER_COMPLETE
302         ) {
303 //	    glDrawBuffer(GL_BACK);
304 //	    glReadBuffer(GL_BACK);
305             return false;
306         }
307 
308 	GEO_CHECK_GL();
309 
310 //	glDrawBuffer(GL_BACK);
311 //	glReadBuffer(GL_BACK);
312 
313 	GEO_CHECK_GL();
314 
315         // Restore previously bound frame buffer object.
316         glBindFramebuffer(GL_FRAMEBUFFER, previous_frame_buffer_id);
317 
318 	GEO_CHECK_GL();
319 
320         return true;
321     }
322 
bind_as_texture()323     void FrameBufferObject::bind_as_texture() {
324         glBindTexture(GL_TEXTURE_2D, offscreen_id);
325     }
326 
bind_depth_buffer_as_texture()327     void FrameBufferObject::bind_depth_buffer_as_texture() {
328         glBindTexture(GL_TEXTURE_2D, depth_buffer_id);
329     }
330 
bind_as_framebuffer()331     void FrameBufferObject::bind_as_framebuffer() {
332 	// Current frame buffer ID may have changed,
333 	// for instance under Qt if rendering area was resized.
334 	glGetIntegerv(
335 	    GL_FRAMEBUFFER_BINDING, (GLint*)(&previous_frame_buffer_id)
336 	);
337         glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id);
338     }
339 
unbind()340     void FrameBufferObject::unbind() {
341         glBindFramebuffer(GL_FRAMEBUFFER, previous_frame_buffer_id);
342         glBindTexture(GL_TEXTURE_2D, 0);
343     }
344 
is_bound_as_framebuffer() const345     bool FrameBufferObject::is_bound_as_framebuffer() const {
346 	GLuint current_frame_buffer_id;
347 	glGetIntegerv(
348 	    GL_FRAMEBUFFER_BINDING, (GLint*)(&current_frame_buffer_id)
349 	);
350 	return (current_frame_buffer_id == frame_buffer_id);
351     }
352 }
353 
354