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*)(¤t_frame_buffer_id) 349 ); 350 return (current_frame_buffer_id == frame_buffer_id); 351 } 352 } 353 354