1 /** 2 * Copyright (c) 2006-2012 LOVE Development Team 3 * 4 * This software is provided 'as-is', without any express or implied 5 * warranty. In no event will the authors be held liable for any damages 6 * arising from the use of this software. 7 * 8 * Permission is granted to anyone to use this software for any purpose, 9 * including commercial applications, and to alter it and redistribute it 10 * freely, subject to the following restrictions: 11 * 12 * 1. The origin of this software must not be misrepresented; you must not 13 * claim that you wrote the original software. If you use this software 14 * in a product, an acknowledgment in the product documentation would be 15 * appreciated but is not required. 16 * 2. Altered source versions must be plainly marked as such, and must not be 17 * misrepresented as being the original software. 18 * 3. This notice may not be removed or altered from any source distribution. 19 **/ 20 21 #define GL_GLEXT_PROTOTYPES 22 #include "Canvas.h" 23 #include "Graphics.h" 24 #include <common/Matrix.h> 25 26 #include <cstring> // For memcpy 27 28 namespace love 29 { 30 namespace graphics 31 { 32 namespace opengl 33 { 34 35 // strategy for fbo creation, interchangable at runtime: 36 // none, opengl >= 3.0, extensions 37 struct FramebufferStrategy { 38 /// create a new framebuffer, depth_stencil and texture 39 /** 40 * @param[out] framebuffer Framebuffer name 41 * @param[out] depth_stencil Name for packed depth and stencil buffer 42 * @param[out] img Texture name 43 * @param[in] width Width of framebuffer 44 * @param[in] height Height of framebuffer 45 * @return Creation status 46 */ createFBOlove::graphics::opengl::FramebufferStrategy47 virtual GLenum createFBO(GLuint&, GLuint&, GLuint&, int, int) 48 { return GL_FRAMEBUFFER_UNSUPPORTED; } 49 /// remove objects 50 /** 51 * @param[in] framebuffer Framebuffer name 52 * @param[in] depth_stencil Name for packed depth and stencil buffer 53 * @param[in] img Texture name 54 */ deleteFBOlove::graphics::opengl::FramebufferStrategy55 virtual void deleteFBO(GLuint, GLuint, GLuint) {} bindFBOlove::graphics::opengl::FramebufferStrategy56 virtual void bindFBO(GLuint) {} 57 }; 58 59 struct FramebufferStrategyGL3 : public FramebufferStrategy { createFBOlove::graphics::opengl::FramebufferStrategyGL360 virtual GLenum createFBO(GLuint& framebuffer, GLuint& depth_stencil, GLuint& img, int width, int height) 61 { 62 // get currently bound fbo to reset to it later 63 GLint current_fbo; 64 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); 65 66 // create framebuffer 67 glGenFramebuffers(1, &framebuffer); 68 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); 69 70 // create stencil buffer 71 glGenRenderbuffers(1, &depth_stencil); 72 glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil); 73 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height); 74 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 75 GL_RENDERBUFFER, depth_stencil); 76 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 77 GL_RENDERBUFFER, depth_stencil); 78 79 // generate texture save target 80 glGenTextures(1, &img); 81 bindTexture(img); 82 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 83 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 84 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 85 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 86 bindTexture(0); 87 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 88 GL_TEXTURE_2D, img, 0); 89 90 // check status 91 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 92 93 // unbind framebuffer 94 glBindRenderbuffer(GL_RENDERBUFFER, 0); 95 glBindFramebuffer(GL_FRAMEBUFFER, (GLuint)current_fbo); 96 return status; 97 } deleteFBOlove::graphics::opengl::FramebufferStrategyGL398 virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint img) 99 { 100 deleteTexture(img); 101 glDeleteRenderbuffers(1, &depth_stencil); 102 glDeleteFramebuffers(1, &framebuffer); 103 } 104 bindFBOlove::graphics::opengl::FramebufferStrategyGL3105 virtual void bindFBO(GLuint framebuffer) 106 { 107 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); 108 } 109 }; 110 111 struct FramebufferStrategyEXT : public FramebufferStrategy { 112 createFBOlove::graphics::opengl::FramebufferStrategyEXT113 virtual GLenum createFBO(GLuint& framebuffer, GLuint& depth_stencil, GLuint& img, int width, int height) 114 { 115 GLint current_fbo; 116 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, ¤t_fbo); 117 118 // create framebuffer 119 glGenFramebuffersEXT(1, &framebuffer); 120 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); 121 122 // create stencil buffer 123 glGenRenderbuffersEXT(1, &depth_stencil); 124 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_stencil); 125 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, width, height); 126 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, 127 GL_RENDERBUFFER_EXT, depth_stencil); 128 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, 129 GL_RENDERBUFFER_EXT, depth_stencil); 130 131 // generate texture save target 132 glGenTextures(1, &img); 133 bindTexture(img); 134 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 135 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 136 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 137 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 138 bindTexture(0); 139 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 140 GL_TEXTURE_2D, img, 0); 141 142 // check status 143 GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); 144 145 // unbind framebuffer 146 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); 147 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, (GLuint)current_fbo); 148 return status; 149 } 150 deleteFBOlove::graphics::opengl::FramebufferStrategyEXT151 virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint img) 152 { 153 deleteTexture(img); 154 glDeleteRenderbuffersEXT(1, &depth_stencil); 155 glDeleteFramebuffersEXT(1, &framebuffer); 156 } 157 bindFBOlove::graphics::opengl::FramebufferStrategyEXT158 virtual void bindFBO(GLuint framebuffer) 159 { 160 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); 161 } 162 }; 163 164 FramebufferStrategy* strategy = NULL; 165 166 FramebufferStrategy strategyNone; 167 168 FramebufferStrategyGL3 strategyGL3; 169 170 FramebufferStrategyEXT strategyEXT; 171 172 Canvas* Canvas::current = NULL; 173 loadStrategy()174 static void loadStrategy() 175 { 176 if (!strategy) 177 { 178 if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object) 179 strategy = &strategyGL3; 180 else if (GLEE_EXT_framebuffer_object && GLEE_EXT_packed_depth_stencil) 181 strategy = &strategyEXT; 182 else 183 strategy = &strategyNone; 184 } 185 } 186 Canvas(int width,int height)187 Canvas::Canvas(int width, int height) : 188 width(width), height(height) 189 { 190 float w = static_cast<float>(width); 191 float h = static_cast<float>(height); 192 193 // world coordinates 194 vertices[0].x = 0; vertices[0].y = 0; 195 vertices[1].x = 0; vertices[1].y = h; 196 vertices[2].x = w; vertices[2].y = h; 197 vertices[3].x = w; vertices[3].y = 0; 198 199 // texture coordinates 200 vertices[0].s = 0; vertices[0].t = 1; 201 vertices[1].s = 0; vertices[1].t = 0; 202 vertices[2].s = 1; vertices[2].t = 0; 203 vertices[3].s = 1; vertices[3].t = 1; 204 205 loadStrategy(); 206 207 loadVolatile(); 208 } 209 ~Canvas()210 Canvas::~Canvas() 211 { 212 // reset framebuffer if still using this one 213 if (current == this) 214 stopGrab(); 215 216 unloadVolatile(); 217 } 218 isSupported()219 bool Canvas::isSupported() 220 { 221 loadStrategy(); 222 return (strategy != &strategyNone); 223 } 224 bindDefaultCanvas()225 void Canvas::bindDefaultCanvas() 226 { 227 if (current != NULL) 228 current->stopGrab(); 229 } 230 startGrab()231 void Canvas::startGrab() 232 { 233 // already grabbing 234 if (current == this) 235 return; 236 237 // cleanup after previous fbo 238 if (current != NULL) 239 current->stopGrab(); 240 241 // bind buffer and clear screen 242 glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT); 243 strategy->bindFBO(fbo); 244 glViewport(0, 0, width, height); 245 246 // Reset the projection matrix 247 glMatrixMode(GL_PROJECTION); 248 glPushMatrix(); 249 glLoadIdentity(); 250 251 // Set up orthographic view (no depth) 252 glOrtho(0.0, width, height, 0.0, -1.0, 1.0); 253 254 // Switch back to modelview matrix 255 glMatrixMode(GL_MODELVIEW); 256 257 // indicate we are using this fbo 258 current = this; 259 } 260 stopGrab()261 void Canvas::stopGrab() 262 { 263 // i am not grabbing. leave me alone 264 if (current != this) 265 return; 266 267 // bind default 268 strategy->bindFBO( 0 ); 269 glMatrixMode(GL_PROJECTION); 270 glPopMatrix(); 271 glPopAttrib(); 272 current = NULL; 273 } 274 275 clear(const Color & c)276 void Canvas::clear(const Color& c) 277 { 278 GLuint previous = 0; 279 if (current != NULL) 280 previous = current->fbo; 281 282 strategy->bindFBO(fbo); 283 glPushAttrib(GL_COLOR_BUFFER_BIT); 284 glClearColor((float)c.r/255.0f, (float)c.g/255.0f, (float)c.b/255.0f, (float)c.a/255.0f); 285 glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 286 glPopAttrib(); 287 288 strategy->bindFBO(previous); 289 } 290 draw(float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky) const291 void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const 292 { 293 static Matrix t; 294 t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); 295 296 drawv(t, vertices); 297 } 298 drawq(love::graphics::Quad * quad,float x,float y,float angle,float sx,float sy,float ox,float oy,float kx,float ky) const299 void Canvas::drawq(love::graphics::Quad * quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const 300 { 301 static Matrix t; 302 quad->mirror(false, true); 303 const vertex * v = quad->getVertices(); 304 305 t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); 306 drawv(t, v); 307 quad->mirror(false, true); 308 } 309 getImageData(love::image::Image * image)310 love::image::ImageData * Canvas::getImageData(love::image::Image * image) 311 { 312 int row = 4 * width; 313 int size = row * height; 314 GLubyte* pixels = new GLubyte[size]; 315 GLubyte* screenshot = new GLubyte[size]; 316 317 strategy->bindFBO( fbo ); 318 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 319 if (current) 320 strategy->bindFBO( current->fbo ); 321 else 322 strategy->bindFBO( 0 ); 323 324 GLubyte * src = pixels - row; // second line of source image 325 GLubyte * dst = screenshot + size; // last row of destination image 326 327 for (int i = 0; i < height; ++i) 328 memcpy(dst -= row, src += row, row); 329 330 love::image::ImageData* img = image->newImageData(width, height, (void*)screenshot); 331 332 delete[] screenshot; 333 delete[] pixels; 334 335 return img; 336 } 337 setFilter(const Image::Filter & f)338 void Canvas::setFilter(const Image::Filter &f) 339 { 340 GLint gmin = (f.min == Image::FILTER_NEAREST) ? GL_NEAREST : GL_LINEAR; 341 GLint gmag = (f.mag == Image::FILTER_NEAREST) ? GL_NEAREST : GL_LINEAR; 342 343 bindTexture(img); 344 345 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gmin); 346 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gmag); 347 } 348 getFilter() const349 Image::Filter Canvas::getFilter() const 350 { 351 GLint gmin, gmag; 352 353 bindTexture(img); 354 glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &gmin); 355 glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, &gmag); 356 357 Image::Filter f; 358 f.min = (gmin == GL_NEAREST) ? Image::FILTER_NEAREST : Image::FILTER_LINEAR; 359 f.mag = (gmag == GL_NEAREST) ? Image::FILTER_NEAREST : Image::FILTER_LINEAR; 360 return f; 361 } 362 setWrap(const Image::Wrap & w)363 void Canvas::setWrap(const Image::Wrap &w) 364 { 365 GLint wrap_s = (w.s == Image::WRAP_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT; 366 GLint wrap_t = (w.t == Image::WRAP_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT; 367 368 bindTexture(img); 369 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s); 370 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t); 371 } 372 getWrap() const373 Image::Wrap Canvas::getWrap() const 374 { 375 GLint wrap_s, wrap_t; 376 bindTexture(img); 377 glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, &wrap_s); 378 glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, &wrap_t); 379 380 Image::Wrap w; 381 w.s = (wrap_s == GL_CLAMP_TO_EDGE) ? Image::WRAP_CLAMP : Image::WRAP_REPEAT; 382 w.t = (wrap_t == GL_CLAMP_TO_EDGE) ? Image::WRAP_CLAMP : Image::WRAP_REPEAT; 383 384 return w; 385 } 386 loadVolatile()387 bool Canvas::loadVolatile() 388 { 389 status = strategy->createFBO(fbo, depth_stencil, img, width, height); 390 if (status != GL_FRAMEBUFFER_COMPLETE) 391 return false; 392 393 setFilter(settings.filter); 394 setWrap(settings.wrap); 395 Color c; 396 c.r = c.g = c.b = c.a = 0; 397 clear(c); 398 return true; 399 } 400 unloadVolatile()401 void Canvas::unloadVolatile() 402 { 403 settings.filter = getFilter(); 404 settings.wrap = getWrap(); 405 strategy->deleteFBO(fbo, depth_stencil, img); 406 } 407 getWidth()408 int Canvas::getWidth() 409 { 410 return width; 411 } 412 getHeight()413 int Canvas::getHeight() 414 { 415 return height; 416 } 417 drawv(const Matrix & t,const vertex * v) const418 void Canvas::drawv(const Matrix & t, const vertex * v) const 419 { 420 glPushMatrix(); 421 422 glMultMatrixf((const GLfloat*)t.getElements()); 423 424 bindTexture(img); 425 426 glEnableClientState(GL_VERTEX_ARRAY); 427 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 428 glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid*)&v[0].x); 429 glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid*)&v[0].s); 430 glDrawArrays(GL_QUADS, 0, 4); 431 glDisableClientState(GL_TEXTURE_COORD_ARRAY); 432 glDisableClientState(GL_VERTEX_ARRAY); 433 434 glPopMatrix(); 435 } 436 437 } // opengl 438 } // graphics 439 } // love 440