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, &current_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, &current_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