Canvas.cpp 17 KB

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