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