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