1 /**
2  * Copyright (c) 2006-2019 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 #include "Canvas.h"
22 #include "graphics/Graphics.h"
23 #include "Graphics.h"
24 
25 #include <algorithm> // For min/max
26 
27 namespace love
28 {
29 namespace graphics
30 {
31 namespace opengl
32 {
33 
createFBO(GLuint & framebuffer,TextureType texType,PixelFormat format,GLuint texture,int layers,int mips)34 static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, int mips)
35 {
36 	// get currently bound fbo to reset to it later
37 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
38 
39 	glGenFramebuffers(1, &framebuffer);
40 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer);
41 
42 	if (texture != 0)
43 	{
44 		if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0))
45 		{
46 			// glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3.
47 			GLenum none = GL_NONE;
48 			if (GLAD_ES_VERSION_3_0)
49 				glDrawBuffers(1, &none);
50 			else
51 				glDrawBuffer(GL_NONE);
52 			glReadBuffer(GL_NONE);
53 		}
54 
55 		bool unusedSRGB = false;
56 		OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB);
57 
58 		int faces = texType == TEXTURE_CUBE ? 6 : 1;
59 
60 		// Make sure all faces and layers of the texture are initialized to
61 		// transparent black. This is unfortunately probably pretty slow for
62 		// 2D-array and 3D textures with a lot of layers...
63 		for (int mip = mips - 1; mip >= 0; mip--)
64 		{
65 			int nlayers = layers;
66 			if (texType == TEXTURE_VOLUME)
67 				nlayers = std::max(layers >> mip, 1);
68 
69 			for (int layer = nlayers - 1; layer >= 0; layer--)
70 			{
71 				for (int face = faces - 1; face >= 0; face--)
72 				{
73 					for (GLenum attachment : fmt.framebufferAttachments)
74 					{
75 						if (attachment == GL_NONE)
76 							continue;
77 
78 						gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
79 					}
80 
81 					if (isPixelFormatDepthStencil(format))
82 					{
83 						bool hadDepthWrites = gl.hasDepthWrites();
84 						if (!hadDepthWrites) // glDepthMask also affects glClear.
85 							gl.setDepthWrites(true);
86 
87 						gl.clearDepth(1.0);
88 						glClearStencil(0);
89 						glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
90 
91 						if (!hadDepthWrites)
92 							gl.setDepthWrites(hadDepthWrites);
93 					}
94 					else
95 					{
96 						glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
97 						glClear(GL_COLOR_BUFFER_BIT);
98 					}
99 				}
100 			}
101 		}
102 	}
103 
104 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
105 
106 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
107 	return status;
108 }
109 
createRenderbuffer(int width,int height,int & samples,PixelFormat pixelformat,GLuint & buffer)110 static bool createRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer)
111 {
112 	int reqsamples = samples;
113 	bool unusedSRGB = false;
114 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB);
115 
116 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
117 
118 	// Temporary FBO used to clear the renderbuffer.
119 	GLuint fbo = 0;
120 	glGenFramebuffers(1, &fbo);
121 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
122 
123 	if (isPixelFormatDepthStencil(pixelformat) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0))
124 	{
125 		// glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3.
126 		GLenum none = GL_NONE;
127 		if (GLAD_ES_VERSION_3_0)
128 			glDrawBuffers(1, &none);
129 		else
130 			glDrawBuffer(GL_NONE);
131 		glReadBuffer(GL_NONE);
132 	}
133 
134 	glGenRenderbuffers(1, &buffer);
135 	glBindRenderbuffer(GL_RENDERBUFFER, buffer);
136 
137 	if (samples > 1)
138 		glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height);
139 	else
140 		glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height);
141 
142 	for (GLenum attachment : fmt.framebufferAttachments)
143 	{
144 		if (attachment != GL_NONE)
145 			glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffer);
146 	}
147 
148 	if (samples > 1)
149 		glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
150 	else
151 		samples = 0;
152 
153 	glBindRenderbuffer(GL_RENDERBUFFER, 0);
154 
155 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
156 
157 	if (status == GL_FRAMEBUFFER_COMPLETE && (reqsamples <= 1 || samples > 1))
158 	{
159 		if (isPixelFormatDepthStencil(pixelformat))
160 		{
161 			bool hadDepthWrites = gl.hasDepthWrites();
162 			if (!hadDepthWrites) // glDepthMask also affects glClear.
163 				gl.setDepthWrites(true);
164 
165 			gl.clearDepth(1.0);
166 			glClearStencil(0);
167 			glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
168 
169 			if (!hadDepthWrites)
170 				gl.setDepthWrites(hadDepthWrites);
171 		}
172 		else
173 		{
174 			// Initialize the buffer to transparent black.
175 			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
176 			glClear(GL_COLOR_BUFFER_BIT);
177 		}
178 	}
179 	else
180 	{
181 		glDeleteRenderbuffers(1, &buffer);
182 		buffer = 0;
183 		samples = 0;
184 	}
185 
186 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
187 	gl.deleteFramebuffer(fbo);
188 
189 	return status == GL_FRAMEBUFFER_COMPLETE;
190 }
191 
Canvas(const Settings & settings)192 Canvas::Canvas(const Settings &settings)
193 	: love::graphics::Canvas(settings)
194 	, fbo(0)
195 	, texture(0)
196     , renderbuffer(0)
197 	, actualSamples(0)
198 {
199 	format = getSizedFormat(format);
200 
201 	initQuad();
202 	loadVolatile();
203 
204 	if (status != GL_FRAMEBUFFER_COMPLETE)
205 		throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(status));
206 }
207 
~Canvas()208 Canvas::~Canvas()
209 {
210 	unloadVolatile();
211 }
212 
loadVolatile()213 bool Canvas::loadVolatile()
214 {
215 	if (texture != 0)
216 		return true;
217 
218 	OpenGL::TempDebugGroup debuggroup("Canvas load");
219 
220 	fbo = texture = 0;
221 	renderbuffer = 0;
222 	status = GL_FRAMEBUFFER_COMPLETE;
223 
224 	// getMaxRenderbufferSamples will be 0 on systems that don't support
225 	// multisampled renderbuffers / don't export FBO multisample extensions.
226 	actualSamples = std::min(getRequestedMSAA(), gl.getMaxRenderbufferSamples());
227 	actualSamples = std::max(actualSamples, 0);
228 	actualSamples = actualSamples == 1 ? 0 : actualSamples;
229 
230 	if (isReadable())
231 	{
232 		glGenTextures(1, &texture);
233 		gl.bindTextureToUnit(this, 0, false);
234 
235 		GLenum gltype = OpenGL::getGLTextureType(texType);
236 
237 		if (GLAD_ANGLE_texture_usage)
238 			glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
239 
240 		setFilter(filter);
241 		setWrap(wrap);
242 		setMipmapSharpness(mipmapSharpness);
243 		setDepthSampleMode(depthCompareMode);
244 
245 		while (glGetError() != GL_NO_ERROR)
246 			/* Clear the error buffer. */;
247 
248 		bool isSRGB = format == PIXELFORMAT_sRGBA8;
249 		if (!gl.rawTexStorage(texType, mipmapCount, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers))
250 		{
251 			status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
252 			return false;
253 		}
254 
255 		if (glGetError() != GL_NO_ERROR)
256 		{
257 			gl.deleteTexture(texture);
258 			texture = 0;
259 			status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
260 			return false;
261 		}
262 
263 		// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
264 		status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, mipmapCount);
265 
266 		if (status != GL_FRAMEBUFFER_COMPLETE)
267 		{
268 			if (fbo != 0)
269 			{
270 				gl.deleteFramebuffer(fbo);
271 				fbo = 0;
272 			}
273 			return false;
274 		}
275 	}
276 
277 	if (!isReadable() || actualSamples > 0)
278 		createRenderbuffer(pixelWidth, pixelHeight, actualSamples, format, renderbuffer);
279 
280 	int64 memsize = getPixelFormatSize(format) * pixelWidth * pixelHeight;
281 	if (getMipmapCount() > 1)
282 		memsize *= 1.33334;
283 
284 	if (actualSamples > 1 && isReadable())
285 		memsize += getPixelFormatSize(format) * pixelWidth * pixelHeight * actualSamples;
286 	else if (actualSamples > 1)
287 		memsize *= actualSamples;
288 
289 	setGraphicsMemorySize(memsize);
290 
291 	return true;
292 }
293 
unloadVolatile()294 void Canvas::unloadVolatile()
295 {
296 	if (fbo != 0 || renderbuffer != 0 || texture != 0)
297 	{
298 		// This is a bit ugly, but we need some way to destroy the cached FBO
299 		// when this Canvas' texture is destroyed.
300 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
301 		if (gfx != nullptr)
302 			gfx->cleanupCanvas(this);
303 	}
304 
305 	if (fbo != 0)
306 		gl.deleteFramebuffer(fbo);
307 
308 	if (renderbuffer != 0)
309 		glDeleteRenderbuffers(1, &renderbuffer);
310 
311 	if (texture != 0)
312 		gl.deleteTexture(texture);
313 
314 	fbo = 0;
315 	renderbuffer = 0;
316 	texture = 0;
317 
318 	setGraphicsMemorySize(0);
319 }
320 
setFilter(const Texture::Filter & f)321 void Canvas::setFilter(const Texture::Filter &f)
322 {
323 	Texture::setFilter(f);
324 
325 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
326 	{
327 		filter.mag = filter.min = FILTER_NEAREST;
328 
329 		if (filter.mipmap == FILTER_LINEAR)
330 			filter.mipmap = FILTER_NEAREST;
331 	}
332 
333 	gl.bindTextureToUnit(this, 0, false);
334 	gl.setTextureFilter(texType, filter);
335 }
336 
setWrap(const Texture::Wrap & w)337 bool Canvas::setWrap(const Texture::Wrap &w)
338 {
339 	Graphics::flushStreamDrawsGlobal();
340 
341 	bool success = true;
342 	bool forceclamp = texType == TEXTURE_CUBE;
343 	wrap = w;
344 
345 	// If we only have limited NPOT support then the wrap mode must be CLAMP.
346 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
347 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
348 	{
349 		forceclamp = true;
350 	}
351 
352 	if (forceclamp)
353 	{
354 		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
355 			success = false;
356 
357 		wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
358 	}
359 
360 	if (!gl.isClampZeroTextureWrapSupported())
361 	{
362 		if (wrap.s == WRAP_CLAMP_ZERO) wrap.s = WRAP_CLAMP;
363 		if (wrap.t == WRAP_CLAMP_ZERO) wrap.t = WRAP_CLAMP;
364 		if (wrap.r == WRAP_CLAMP_ZERO) wrap.r = WRAP_CLAMP;
365 	}
366 
367 	gl.bindTextureToUnit(this, 0, false);
368 	gl.setTextureWrap(texType, wrap);
369 
370 	return success;
371 }
372 
setMipmapSharpness(float sharpness)373 bool Canvas::setMipmapSharpness(float sharpness)
374 {
375 	if (!gl.isSamplerLODBiasSupported())
376 		return false;
377 
378 	Graphics::flushStreamDrawsGlobal();
379 
380 	float maxbias = gl.getMaxLODBias();
381 	if (maxbias > 0.01f)
382 		maxbias -= 0.0f;
383 
384 	mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias);
385 
386 	gl.bindTextureToUnit(this, 0, false);
387 
388 	// negative bias is sharper
389 	glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
390 
391 	return true;
392 }
393 
setDepthSampleMode(Optional<CompareMode> mode)394 void Canvas::setDepthSampleMode(Optional<CompareMode> mode)
395 {
396 	Texture::setDepthSampleMode(mode);
397 
398 	bool supported = gl.isDepthCompareSampleSupported();
399 
400 	if (mode.hasValue)
401 	{
402 		if (!supported)
403 			throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
404 
405 		Graphics::flushStreamDrawsGlobal();
406 
407 		gl.bindTextureToUnit(texType, texture, 0, false);
408 		GLenum gltextype = OpenGL::getGLTextureType(texType);
409 
410 		// See the comment in depthstencil.h
411 		GLenum glmode = OpenGL::getGLCompareMode(getReversedCompareMode(mode.value));
412 
413 		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
414 		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_FUNC, glmode);
415 
416 	}
417 	else if (isPixelFormatDepth(format) && supported)
418 	{
419 		Graphics::flushStreamDrawsGlobal();
420 
421 		gl.bindTextureToUnit(texType, texture, 0, false);
422 		GLenum gltextype = OpenGL::getGLTextureType(texType);
423 
424 		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_NONE);
425 	}
426 
427 	depthCompareMode = mode;
428 }
429 
getHandle() const430 ptrdiff_t Canvas::getHandle() const
431 {
432 	return texture;
433 }
434 
newImageData(love::image::Image * module,int slice,int mipmap,const Rect & r)435 love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int mipmap, const Rect &r)
436 {
437 	love::image::ImageData *data = love::graphics::Canvas::newImageData(module, slice, mipmap, r);
438 
439 	bool isSRGB = false;
440 	OpenGL::TextureFormat fmt = gl.convertPixelFormat(data->getFormat(), false, isSRGB);
441 
442 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
443 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
444 
445 	if (slice > 0 || mipmap > 0)
446 	{
447 		int layer = texType == TEXTURE_CUBE ? 0 : slice;
448 		int face = texType == TEXTURE_CUBE ? slice : 0;
449 		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, mipmap, layer, face);
450 	}
451 
452 	glReadPixels(r.x, r.y, r.w, r.h, fmt.externalformat, fmt.type, data->getData());
453 
454 	if (slice > 0 || mipmap > 0)
455 		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, texType, texture, 0, 0, 0);
456 
457 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
458 
459 	return data;
460 }
461 
generateMipmaps()462 void Canvas::generateMipmaps()
463 {
464 	if (getMipmapCount() == 1 || getMipmapMode() == MIPMAPS_NONE)
465 		throw love::Exception("generateMipmaps can only be called on a Canvas which was created with mipmaps enabled.");
466 
467 	gl.bindTextureToUnit(this, 0, false);
468 
469 	GLenum gltextype = OpenGL::getGLTextureType(texType);
470 
471 	if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
472 		glEnable(gltextype);
473 
474 	glGenerateMipmap(gltextype);
475 }
476 
getSizedFormat(PixelFormat format)477 PixelFormat Canvas::getSizedFormat(PixelFormat format)
478 {
479 	switch (format)
480 	{
481 	case PIXELFORMAT_NORMAL:
482 		if (isGammaCorrect())
483 			return PIXELFORMAT_sRGBA8;
484 		else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8, true, true, false))
485 			// 32-bit render targets don't have guaranteed support on GLES2.
486 			return PIXELFORMAT_RGBA4;
487 		else
488 			return PIXELFORMAT_RGBA8;
489 	case PIXELFORMAT_HDR:
490 		return PIXELFORMAT_RGBA16F;
491 	default:
492 		return format;
493 	}
494 }
495 
isSupported()496 bool Canvas::isSupported()
497 {
498 	return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object;
499 }
500 
isMultiFormatMultiCanvasSupported()501 bool Canvas::isMultiFormatMultiCanvasSupported()
502 {
503 	return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
504 }
505 
506 Canvas::SupportedFormat Canvas::supportedFormats[] = {};
507 Canvas::SupportedFormat Canvas::checkedFormats[] = {};
508 
isFormatSupported(PixelFormat format)509 bool Canvas::isFormatSupported(PixelFormat format)
510 {
511 	return isFormatSupported(format, !isPixelFormatDepthStencil(format));
512 }
513 
isFormatSupported(PixelFormat format,bool readable)514 bool Canvas::isFormatSupported(PixelFormat format, bool readable)
515 {
516 	if (!isSupported())
517 		return false;
518 
519 	const char *fstr = "?";
520 	love::getConstant(format, fstr);
521 
522 	bool supported = true;
523 	format = getSizedFormat(format);
524 
525 	if (!OpenGL::isPixelFormatSupported(format, true, readable, false))
526 		return false;
527 
528 	if (checkedFormats[format].get(readable))
529 		return supportedFormats[format].get(readable);
530 
531 	// Even though we might have the necessary OpenGL version or extension,
532 	// drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
533 	// a texture to a FBO whose format the driver doesn't like. So we should
534 	// test with an actual FBO.
535 	GLuint texture = 0;
536 	GLuint renderbuffer = 0;
537 
538 	// Avoid the test for depth/stencil formats - not every GL version
539 	// guarantees support for depth/stencil-only render targets (which we would
540 	// need for the test below to work), and we already do some finagling in
541 	// convertPixelFormat to try to use the best-supported internal
542 	// depth/stencil format for a particular driver.
543 	if (isPixelFormatDepthStencil(format))
544 	{
545 		checkedFormats[format].set(readable, true);
546 		supportedFormats[format].set(readable, true);
547 		return true;
548 	}
549 
550 	bool unusedSRGB = false;
551 	OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, unusedSRGB);
552 
553 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
554 
555 	GLuint fbo = 0;
556 	glGenFramebuffers(1, &fbo);
557 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
558 
559 	// Make sure at least something is bound to a color attachment. I believe
560 	// this is required on ES2 but I'm not positive.
561 	if (isPixelFormatDepthStencil(format))
562 		gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0);
563 
564 	if (readable)
565 	{
566 		glGenTextures(1, &texture);
567 		gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
568 
569 		Texture::Filter f;
570 		f.min = f.mag = Texture::FILTER_NEAREST;
571 		gl.setTextureFilter(TEXTURE_2D, f);
572 
573 		Texture::Wrap w;
574 		gl.setTextureWrap(TEXTURE_2D, w);
575 
576 		unusedSRGB = false;
577 		gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 1, 1);
578 	}
579 	else
580 	{
581 		glGenRenderbuffers(1, &renderbuffer);
582 		glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
583 		glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1);
584 	}
585 
586 	for (GLenum attachment : fmt.framebufferAttachments)
587 	{
588 		if (attachment == GL_NONE)
589 			continue;
590 
591 		if (readable)
592 			gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0);
593 		else
594 			glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer);
595 	}
596 
597 	supported = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
598 
599 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
600 	gl.deleteFramebuffer(fbo);
601 
602 	if (texture != 0)
603 		gl.deleteTexture(texture);
604 
605 	if (renderbuffer != 0)
606 		glDeleteRenderbuffers(1, &renderbuffer);
607 
608 	// Cache the result so we don't do this for every isFormatSupported call.
609 	checkedFormats[format].set(readable, true);
610 	supportedFormats[format].set(readable, supported);
611 
612 	return supported;
613 }
614 
resetFormatSupport()615 void Canvas::resetFormatSupport()
616 {
617 	for (int i = 0; i < (int)PIXELFORMAT_MAX_ENUM; i++)
618 	{
619 		checkedFormats[i].readable = false;
620 		checkedFormats[i].nonreadable = false;
621 	}
622 }
623 
624 } // opengl
625 } // graphics
626 } // love
627