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// LOVE
22#include "common/config.h"
23#include "common/math.h"
24#include "common/Vector.h"
25
26#include "Graphics.h"
27#include "font/Font.h"
28#include "StreamBuffer.h"
29#include "math/MathModule.h"
30#include "window/Window.h"
31#include "Buffer.h"
32#include "ShaderStage.h"
33
34#include "libraries/xxHash/xxhash.h"
35
36// C++
37#include <vector>
38#include <sstream>
39#include <algorithm>
40#include <iterator>
41
42// C
43#include <cmath>
44#include <cstdio>
45
46#ifdef LOVE_IOS
47#include <SDL_syswm.h>
48#endif
49
50namespace love
51{
52namespace graphics
53{
54namespace opengl
55{
56
57Graphics::Graphics()
58 : windowHasStencil(false)
59 , mainVAO(0)
60{
61 gl = OpenGL();
62 Canvas::resetFormatSupport();
63
64 auto window = getInstance<love::window::Window>(M_WINDOW);
65
66 if (window != nullptr)
67 {
68 window->setGraphics(this);
69
70 if (window->isOpen())
71 {
72 int w, h;
73 love::window::WindowSettings settings;
74 window->getWindow(w, h, settings);
75
76 double dpiW = w;
77 double dpiH = h;
78 window->windowToDPICoords(&dpiW, &dpiH);
79
80 setMode((int) dpiW, (int) dpiH, window->getPixelWidth(), window->getPixelHeight(), settings.stencil);
81 }
82 }
83}
84
85Graphics::~Graphics()
86{
87}
88
89const char *Graphics::getName() const
90{
91 return "love.graphics.opengl";
92}
93
94love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t size)
95{
96 return CreateStreamBuffer(type, size);
97}
98
99love::graphics::Image *Graphics::newImage(const Image::Slices &data, const Image::Settings &settings)
100{
101 return new Image(data, settings);
102}
103
104love::graphics::Image *Graphics::newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings)
105{
106 return new Image(textype, format, width, height, slices, settings);
107}
108
109love::graphics::Canvas *Graphics::newCanvas(const Canvas::Settings &settings)
110{
111 return new Canvas(settings);
112}
113
114love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles)
115{
116 return new ShaderStage(this, stage, source, gles, cachekey);
117}
118
119love::graphics::Shader *Graphics::newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel)
120{
121 return new Shader(vertex, pixel);
122}
123
124love::graphics::Buffer *Graphics::newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags)
125{
126 return new Buffer(size, data, type, usage, mapflags);
127}
128
129void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
130{
131 this->width = width;
132 this->height = height;
133 this->pixelWidth = pixelwidth;
134 this->pixelHeight = pixelheight;
135
136 if (!isCanvasActive())
137 {
138 // Set the viewport to top-left corner.
139 gl.setViewport({0, 0, pixelwidth, pixelheight});
140
141 // Re-apply the scissor if it was active, since the rectangle passed to
142 // glScissor is affected by the viewport dimensions.
143 if (states.back().scissor)
144 setScissor(states.back().scissorRect);
145
146 // Set up the projection matrix
147 projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0, -10.0f, 10.0f);
148 }
149}
150
151bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil)
152{
153 this->width = width;
154 this->height = height;
155
156 this->windowHasStencil = windowhasstencil;
157
158 // Okay, setup OpenGL.
159 gl.initContext();
160
161 if (gl.isCoreProfile())
162 {
163 glGenVertexArrays(1, &mainVAO);
164 glBindVertexArray(mainVAO);
165 }
166
167 gl.setupContext();
168
169 created = true;
170 initCapabilities();
171
172 setViewportSize(width, height, pixelwidth, pixelheight);
173
174 // Enable blending
175 glEnable(GL_BLEND);
176
177 // Auto-generated mipmaps should be the best quality possible
178 if (!gl.isCoreProfile())
179 glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
180
181 if (!GLAD_ES_VERSION_2_0 && !gl.isCoreProfile())
182 {
183 // Make sure antialiasing works when set elsewhere
184 glEnable(GL_MULTISAMPLE);
185
186 // Enable texturing
187 glEnable(GL_TEXTURE_2D);
188 }
189
190 gl.setTextureUnit(0);
191
192 // Set pixel row alignment
193 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
194 glPixelStorei(GL_PACK_ALIGNMENT, 1);
195
196 // Always enable seamless cubemap filtering when possible.
197 if (GLAD_VERSION_3_2 || GLAD_ARB_seamless_cube_map)
198 glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
199
200 // Set whether drawing converts input from linear -> sRGB colorspace.
201 if (!gl.bugs.brokenSRGB && (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB
202 || GLAD_EXT_framebuffer_sRGB || GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB))
203 {
204 if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
205 gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, isGammaCorrect());
206 }
207 else
208 setGammaCorrect(false);
209
210 setDebug(isDebugEnabled());
211
212 if (streamBufferState.vb[0] == nullptr)
213 {
214 // Initial sizes that should be good enough for most cases. It will
215 // resize to fit if needed, later.
216 streamBufferState.vb[0] = CreateStreamBuffer(BUFFER_VERTEX, 1024 * 1024 * 1);
217 streamBufferState.vb[1] = CreateStreamBuffer(BUFFER_VERTEX, 256 * 1024 * 1);
218 streamBufferState.indexBuffer = CreateStreamBuffer(BUFFER_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
219 }
220
221 // Reload all volatile objects.
222 if (!Volatile::loadAll())
223 ::printf("Could not reload all volatile objects.\n");
224
225 createQuadIndexBuffer();
226
227 // Restore the graphics state.
228 restoreState(states.back());
229
230 int gammacorrect = isGammaCorrect() ? 1 : 0;
231 Shader::Language target = getShaderLanguageTarget();
232
233 // We always need a default shader.
234 for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
235 {
236 if (i == Shader::STANDARD_ARRAY && !capabilities.textureTypes[TEXTURE_2D_ARRAY])
237 continue;
238
239 // Apparently some intel GMA drivers on windows fail to compile shaders
240 // which use array textures despite claiming support for the extension.
241 try
242 {
243 if (!Shader::standardShaders[i])
244 {
245 const auto &code = defaultShaderCode[i][target][gammacorrect];
246 Shader::standardShaders[i] = love::graphics::Graphics::newShader(code.source[ShaderStage::STAGE_VERTEX], code.source[ShaderStage::STAGE_PIXEL]);
247 }
248 }
249 catch (love::Exception &)
250 {
251 if (i == Shader::STANDARD_ARRAY)
252 capabilities.textureTypes[TEXTURE_2D_ARRAY] = false;
253 else
254 throw;
255 }
256 }
257
258 // A shader should always be active, but the default shader shouldn't be
259 // returned by getShader(), so we don't do setShader(defaultShader).
260 if (!Shader::current)
261 Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
262
263 return true;
264}
265
266void Graphics::unSetMode()
267{
268 if (!isCreated())
269 return;
270
271 flushStreamDraws();
272
273 // Unload all volatile objects. These must be reloaded after the display
274 // mode change.
275 Volatile::unloadAll();
276
277 for (const auto &pair : framebufferObjects)
278 gl.deleteFramebuffer(pair.second);
279
280 for (auto temp : temporaryCanvases)
281 temp.canvas->release();
282
283 framebufferObjects.clear();
284 temporaryCanvases.clear();
285
286 if (mainVAO != 0)
287 {
288 glDeleteVertexArrays(1, &mainVAO);
289 mainVAO = 0;
290 }
291
292 gl.deInitContext();
293
294 created = false;
295}
296
297void Graphics::setActive(bool enable)
298{
299 flushStreamDraws();
300
301 // Make sure all pending OpenGL commands have fully executed before
302 // returning, when going from active to inactive. This is required on iOS.
303 if (isCreated() && this->active && !enable)
304 glFinish();
305
306 active = enable;
307}
308
309void Graphics::draw(const DrawCommand &cmd)
310{
311 gl.prepareDraw();
312 gl.setVertexAttributes(*cmd.attributes, *cmd.buffers);
313 gl.bindTextureToUnit(cmd.texture, 0, false);
314 gl.setCullMode(cmd.cullMode);
315
316 GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
317
318 if (cmd.instanceCount > 1)
319 glDrawArraysInstanced(glprimitivetype, cmd.vertexStart, cmd.vertexCount, cmd.instanceCount);
320 else
321 glDrawArrays(glprimitivetype, cmd.vertexStart, cmd.vertexCount);
322
323 ++drawCalls;
324}
325
326void Graphics::draw(const DrawIndexedCommand &cmd)
327{
328 gl.prepareDraw();
329 gl.setVertexAttributes(*cmd.attributes, *cmd.buffers);
330 gl.bindTextureToUnit(cmd.texture, 0, false);
331 gl.setCullMode(cmd.cullMode);
332
333 const void *gloffset = BUFFER_OFFSET(cmd.indexBufferOffset);
334 GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
335 GLenum gldatatype = OpenGL::getGLIndexDataType(cmd.indexType);
336
337 gl.bindBuffer(BUFFER_INDEX, cmd.indexBuffer->getHandle());
338
339 if (cmd.instanceCount > 1)
340 glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount);
341 else
342 glDrawElements(glprimitivetype, cmd.indexCount, gldatatype, gloffset);
343
344 ++drawCalls;
345}
346
347static inline void advanceVertexOffsets(const vertex::Attributes &attributes, vertex::BufferBindings &buffers, int vertexcount)
348{
349 // TODO: Figure out a better way to avoid touching the same buffer multiple
350 // times, if multiple attributes share the buffer.
351 uint32 touchedbuffers = 0;
352
353 for (unsigned int i = 0; i < vertex::Attributes::MAX; i++)
354 {
355 if (!attributes.isEnabled(i))
356 continue;
357
358 auto &attrib = attributes.attribs[i];
359
360 uint32 bufferbit = 1u << attrib.bufferIndex;
361 if ((touchedbuffers & bufferbit) == 0)
362 {
363 touchedbuffers |= bufferbit;
364 const auto &layout = attributes.bufferLayouts[attrib.bufferIndex];
365 buffers.info[attrib.bufferIndex].offset += layout.stride * vertexcount;
366 }
367 }
368}
369
370void Graphics::drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture)
371{
372 const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
373 const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4;
374
375 gl.prepareDraw();
376 gl.bindTextureToUnit(texture, 0, false);
377 gl.setCullMode(CULL_NONE);
378
379 gl.bindBuffer(BUFFER_INDEX, quadIndexBuffer->getHandle());
380
381 if (gl.isBaseVertexSupported())
382 {
383 gl.setVertexAttributes(attributes, buffers);
384
385 int basevertex = start * 4;
386
387 for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW)
388 {
389 int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex);
390
391 glDrawElementsBaseVertex(GL_TRIANGLES, quadcount * 6, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0), basevertex);
392 ++drawCalls;
393
394 basevertex += quadcount * 4;
395 }
396 }
397 else
398 {
399 vertex::BufferBindings bufferscopy = buffers;
400 if (start > 0)
401 advanceVertexOffsets(attributes, bufferscopy, start * 4);
402
403 for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW)
404 {
405 gl.setVertexAttributes(attributes, bufferscopy);
406
407 int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex);
408
409 glDrawElements(GL_TRIANGLES, quadcount * 6, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
410 ++drawCalls;
411
412 if (count > MAX_QUADS_PER_DRAW)
413 advanceVertexOffsets(attributes, bufferscopy, quadcount * 4);
414 }
415 }
416}
417
418static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
419{
420 // Human-readable strings for the debug info.
421 const char *sourceStr = OpenGL::debugSourceString(source);
422 const char *typeStr = OpenGL::debugTypeString(type);
423 const char *severityStr = OpenGL::debugSeverityString(severity);
424
425 const char *fmt = "OpenGL: %s [source=%s, type=%s, severity=%s, id=%d]\n";
426 printf(fmt, msg, sourceStr, typeStr, severityStr, id);
427}
428
429void Graphics::setDebug(bool enable)
430{
431 // Make sure debug output is supported. The AMD ext. is a bit different
432 // so we don't make use of it, since AMD drivers now support KHR_debug.
433 if (!(GLAD_VERSION_4_3 || GLAD_KHR_debug || GLAD_ARB_debug_output))
434 return;
435
436 // TODO: We don't support GL_KHR_debug in GLES yet.
437 if (GLAD_ES_VERSION_2_0)
438 return;
439
440 // Ugly hack to reduce code duplication.
441 if (GLAD_ARB_debug_output && !(GLAD_VERSION_4_3 || GLAD_KHR_debug))
442 {
443 fp_glDebugMessageCallback = (pfn_glDebugMessageCallback) fp_glDebugMessageCallbackARB;
444 fp_glDebugMessageControl = (pfn_glDebugMessageControl) fp_glDebugMessageControlARB;
445 }
446
447 if (!enable)
448 {
449 // Disable the debug callback function.
450 glDebugMessageCallback(nullptr, nullptr);
451
452 // We can disable debug output entirely with KHR_debug.
453 if (GLAD_VERSION_4_3 || GLAD_KHR_debug)
454 glDisable(GL_DEBUG_OUTPUT);
455
456 return;
457 }
458
459 // We don't want asynchronous debug output.
460 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
461
462 glDebugMessageCallback(debugCB, nullptr);
463
464 // Initially, enable everything.
465 glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
466
467 // Disable messages about deprecated OpenGL functionality.
468 glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
469 glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE);
470
471 if (GLAD_VERSION_4_3 || GLAD_KHR_debug)
472 glEnable(GL_DEBUG_OUTPUT);
473
474 ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
475}
476
477void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas)
478{
479 const DisplayState &state = states.back();
480
481 OpenGL::TempDebugGroup debuggroup("setCanvas");
482
483 flushStreamDraws();
484 endPass();
485
486 bool iswindow = rts.getFirstTarget().canvas == nullptr;
487 vertex::Winding vertexwinding = state.winding;
488
489 if (iswindow)
490 {
491 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
492
493 // The projection matrix is flipped compared to rendering to a canvas, due
494 // to OpenGL considering (0,0) bottom-left instead of top-left.
495 projectionMatrix = Matrix4::ortho(0.0, (float) w, (float) h, 0.0, -10.0f, 10.0f);
496 }
497 else
498 {
499 bindCachedFBO(rts);
500
501 projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h, -10.0f, 10.0f);
502
503 // Flip front face winding when rendering to a canvas, since our
504 // projection matrix is flipped.
505 vertexwinding = vertexwinding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
506 }
507
508 glFrontFace(vertexwinding == vertex::WINDING_CW ? GL_CW : GL_CCW);
509
510 gl.setViewport({0, 0, pixelw, pixelh});
511
512 // Re-apply the scissor if it was active, since the rectangle passed to
513 // glScissor is affected by the viewport dimensions.
514 if (state.scissor)
515 setScissor(state.scissorRect);
516
517 // Make sure the correct sRGB setting is used when drawing to the canvases.
518 if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
519 {
520 if (hasSRGBcanvas != gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB))
521 gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, hasSRGBcanvas);
522 }
523}
524
525void Graphics::endPass()
526{
527 auto &rts = states.back().renderTargets;
528 love::graphics::Canvas *depthstencil = rts.depthStencil.canvas.get();
529
530 // Discard the depth/stencil buffer if we're using an internal cached one.
531 if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0)
532 discard({}, true);
533
534 // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we
535 // don't have to worry about resolving to slices.
536 if (rts.colors.size() > 0 && rts.colors[0].canvas->getMSAA() > 1)
537 {
538 int mip = rts.colors[0].mipmap;
539 int w = rts.colors[0].canvas->getPixelWidth(mip);
540 int h = rts.colors[0].canvas->getPixelHeight(mip);
541
542 for (int i = 0; i < (int) rts.colors.size(); i++)
543 {
544 Canvas *c = (Canvas *) rts.colors[i].canvas.get();
545
546 if (!c->isReadable())
547 continue;
548
549 glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
550
551 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, c->getFBO());
552
553 if (GLAD_APPLE_framebuffer_multisample)
554 glResolveMultisampleFramebufferAPPLE();
555 else
556 glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
557 }
558 }
559
560 if (depthstencil != nullptr && depthstencil->getMSAA() > 1 && depthstencil->isReadable())
561 {
562 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, ((Canvas *) depthstencil)->getFBO());
563
564 if (GLAD_APPLE_framebuffer_multisample)
565 glResolveMultisampleFramebufferAPPLE();
566 else
567 {
568 int mip = rts.depthStencil.mipmap;
569 int w = depthstencil->getPixelWidth(mip);
570 int h = depthstencil->getPixelHeight(mip);
571 PixelFormat format = depthstencil->getPixelFormat();
572
573 GLbitfield mask = 0;
574
575 if (isPixelFormatDepth(format))
576 mask |= GL_DEPTH_BUFFER_BIT;
577 if (isPixelFormatStencil(format))
578 mask |= GL_STENCIL_BUFFER_BIT;
579
580 if (mask != 0)
581 glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST);
582 }
583 }
584
585 for (const auto &rt : rts.colors)
586 {
587 if (rt.canvas->getMipmapMode() == Canvas::MIPMAPS_AUTO && rt.mipmap == 0)
588 rt.canvas->generateMipmaps();
589 }
590}
591
592void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth)
593{
594 if (c.hasValue || stencil.hasValue || depth.hasValue)
595 flushStreamDraws();
596
597 GLbitfield flags = 0;
598
599 if (c.hasValue)
600 {
601 gammaCorrectColor(c.value);
602 glClearColor(c.value.r, c.value.g, c.value.b, c.value.a);
603 flags |= GL_COLOR_BUFFER_BIT;
604 }
605
606 if (stencil.hasValue)
607 {
608 glClearStencil(stencil.value);
609 flags |= GL_STENCIL_BUFFER_BIT;
610 }
611
612 bool hadDepthWrites = gl.hasDepthWrites();
613
614 if (depth.hasValue)
615 {
616 if (!hadDepthWrites) // glDepthMask also affects glClear.
617 gl.setDepthWrites(true);
618
619 gl.clearDepth(depth.value);
620 flags |= GL_DEPTH_BUFFER_BIT;
621 }
622
623 if (flags != 0)
624 glClear(flags);
625
626 if (depth.hasValue && !hadDepthWrites)
627 gl.setDepthWrites(hadDepthWrites);
628
629 if (c.hasValue && gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
630 {
631 // This seems to be enough to fix the bug for me. Other methods I've
632 // tried (e.g. dummy draws) don't work in all cases.
633 gl.useProgram(0);
634 gl.useProgram((GLuint) Shader::current->getHandle());
635 }
636}
637
638void Graphics::clear(const std::vector<OptionalColorf> &colors, OptionalInt stencil, OptionalDouble depth)
639{
640 if (colors.size() == 0 && !stencil.hasValue && !depth.hasValue)
641 return;
642
643 int ncolorcanvases = (int) states.back().renderTargets.colors.size();
644 int ncolors = (int) colors.size();
645
646 if (ncolors <= 1 && ncolorcanvases <= 1)
647 {
648 clear(ncolors > 0 ? colors[0] : OptionalColorf(), stencil, depth);
649 return;
650 }
651
652 flushStreamDraws();
653
654 bool drawbuffersmodified = false;
655 ncolors = std::min(ncolors, ncolorcanvases);
656
657 for (int i = 0; i < ncolors; i++)
658 {
659 if (!colors[i].hasValue)
660 continue;
661
662 Colorf c = colors[i].value;
663 gammaCorrectColor(c);
664
665 if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0)
666 {
667 const GLfloat carray[] = {c.r, c.g, c.b, c.a};
668 glClearBufferfv(GL_COLOR, i, carray);
669 }
670 else
671 {
672 glDrawBuffer(GL_COLOR_ATTACHMENT0 + i);
673 glClearColor(c.r, c.g, c.b, c.a);
674 glClear(GL_COLOR_BUFFER_BIT);
675
676 drawbuffersmodified = true;
677 }
678 }
679
680 // Revert to the expected draw buffers once we're done, if glClearBuffer
681 // wasn't supported.
682 if (drawbuffersmodified)
683 {
684 GLenum bufs[MAX_COLOR_RENDER_TARGETS];
685
686 for (int i = 0; i < ncolorcanvases; i++)
687 bufs[i] = GL_COLOR_ATTACHMENT0 + i;
688
689 glDrawBuffers(ncolorcanvases, bufs);
690 }
691
692 GLbitfield flags = 0;
693
694 if (stencil.hasValue)
695 {
696 glClearStencil(stencil.value);
697 flags |= GL_STENCIL_BUFFER_BIT;
698 }
699
700 bool hadDepthWrites = gl.hasDepthWrites();
701
702 if (depth.hasValue)
703 {
704 if (!hadDepthWrites) // glDepthMask also affects glClear.
705 gl.setDepthWrites(true);
706
707 gl.clearDepth(depth.value);
708 flags |= GL_DEPTH_BUFFER_BIT;
709 }
710
711 if (flags != 0)
712 glClear(flags);
713
714 if (depth.hasValue && !hadDepthWrites)
715 gl.setDepthWrites(hadDepthWrites);
716
717 if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
718 {
719 // This seems to be enough to fix the bug for me. Other methods I've
720 // tried (e.g. dummy draws) don't work in all cases.
721 gl.useProgram(0);
722 gl.useProgram((GLuint) Shader::current->getHandle());
723 }
724}
725
726void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
727{
728 flushStreamDraws();
729 discard(OpenGL::FRAMEBUFFER_ALL, colorbuffers, depthstencil);
730}
731
732void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil)
733{
734 if (!(GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0 || GLAD_EXT_discard_framebuffer))
735 return;
736
737 GLenum gltarget = GL_FRAMEBUFFER;
738 if (target == OpenGL::FRAMEBUFFER_READ)
739 gltarget = GL_READ_FRAMEBUFFER;
740 else if (target == OpenGL::FRAMEBUFFER_DRAW)
741 gltarget = GL_DRAW_FRAMEBUFFER;
742
743 std::vector<GLenum> attachments;
744 attachments.reserve(colorbuffers.size());
745
746 // glDiscardFramebuffer uses different attachment enums for the default FBO.
747 if (!isCanvasActive() && gl.getDefaultFBO() == 0)
748 {
749 if (colorbuffers.size() > 0 && colorbuffers[0])
750 attachments.push_back(GL_COLOR);
751
752 if (depthstencil)
753 {
754 attachments.push_back(GL_STENCIL);
755 attachments.push_back(GL_DEPTH);
756 }
757 }
758 else
759 {
760 int rendertargetcount = std::max((int) states.back().renderTargets.colors.size(), 1);
761
762 for (int i = 0; i < (int) colorbuffers.size(); i++)
763 {
764 if (colorbuffers[i] && i < rendertargetcount)
765 attachments.push_back(GL_COLOR_ATTACHMENT0 + i);
766 }
767
768 if (depthstencil)
769 {
770 attachments.push_back(GL_STENCIL_ATTACHMENT);
771 attachments.push_back(GL_DEPTH_ATTACHMENT);
772 }
773 }
774
775 // Hint for the driver that it doesn't need to save these buffers.
776 if (GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0)
777 glInvalidateFramebuffer(gltarget, (GLint) attachments.size(), &attachments[0]);
778 else if (GLAD_EXT_discard_framebuffer)
779 glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]);
780}
781
782void Graphics::cleanupCanvas(Canvas *canvas)
783{
784 for (auto it = framebufferObjects.begin(); it != framebufferObjects.end(); /**/)
785 {
786 bool hascanvas = false;
787 const auto &rts = it->first;
788
789 for (const RenderTarget &rt : rts.colors)
790 {
791 if (rt.canvas == canvas)
792 {
793 hascanvas = true;
794 break;
795 }
796 }
797
798 hascanvas = hascanvas || rts.depthStencil.canvas == canvas;
799
800 if (hascanvas)
801 {
802 if (isCreated())
803 gl.deleteFramebuffer(it->second);
804 it = framebufferObjects.erase(it);
805 }
806 else
807 ++it;
808 }
809}
810
811void Graphics::bindCachedFBO(const RenderTargets &targets)
812{
813 GLuint fbo = framebufferObjects[targets];
814
815 if (fbo != 0)
816 {
817 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
818 }
819 else
820 {
821 int msaa = targets.getFirstTarget().canvas->getMSAA();
822 bool hasDS = targets.depthStencil.canvas != nullptr;
823
824 glGenFramebuffers(1, &fbo);
825 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
826
827 int ncolortargets = 0;
828 GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS];
829
830 auto attachCanvas = [&](const RenderTarget &rt)
831 {
832 bool renderbuffer = msaa > 1 || !rt.canvas->isReadable();
833 bool srgb = false;
834 OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.canvas->getPixelFormat(), renderbuffer, srgb);
835
836 if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0)
837 {
838 fmt.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0 + ncolortargets;
839 drawbuffers[ncolortargets] = fmt.framebufferAttachments[0];
840 ncolortargets++;
841 }
842
843 GLuint handle = (GLuint) rt.canvas->getRenderTargetHandle();
844
845 for (GLenum attachment : fmt.framebufferAttachments)
846 {
847 if (attachment == GL_NONE)
848 continue;
849 else if (renderbuffer)
850 glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, handle);
851 else
852 {
853 TextureType textype = rt.canvas->getTextureType();
854
855 int layer = textype == TEXTURE_CUBE ? 0 : rt.slice;
856 int face = textype == TEXTURE_CUBE ? rt.slice : 0;
857 int level = rt.mipmap;
858
859 gl.framebufferTexture(attachment, textype, handle, level, layer, face);
860 }
861 }
862 };
863
864 for (const auto &rt : targets.colors)
865 attachCanvas(rt);
866
867 if (hasDS)
868 attachCanvas(targets.depthStencil);
869
870 if (ncolortargets > 1)
871 glDrawBuffers(ncolortargets, drawbuffers);
872 else if (ncolortargets == 0 && hasDS && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0))
873 {
874 // glDrawBuffers is an ext in GL2. glDrawBuffer doesn't exist in ES3.
875 GLenum none = GL_NONE;
876 if (GLAD_ES_VERSION_3_0)
877 glDrawBuffers(1, &none);
878 else
879 glDrawBuffer(GL_NONE);
880 glReadBuffer(GL_NONE);
881 }
882
883 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
884
885 if (status != GL_FRAMEBUFFER_COMPLETE)
886 {
887 gl.deleteFramebuffer(fbo);
888 const char *sstr = OpenGL::framebufferStatusString(status);
889 throw love::Exception("Could not create Framebuffer Object! %s", sstr);
890 }
891
892 framebufferObjects[targets] = fbo;
893 }
894}
895
896void Graphics::present(void *screenshotCallbackData)
897{
898 if (!isActive())
899 return;
900
901 if (isCanvasActive())
902 throw love::Exception("present cannot be called while a Canvas is active.");
903
904 deprecations.draw(this);
905
906 flushStreamDraws();
907 endPass();
908
909 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
910
911 if (!pendingScreenshotCallbacks.empty())
912 {
913 int w = getPixelWidth();
914 int h = getPixelHeight();
915
916 size_t row = 4 * w;
917 size_t size = row * h;
918
919 GLubyte *pixels = nullptr;
920 GLubyte *screenshot = nullptr;
921
922 try
923 {
924 pixels = new GLubyte[size];
925 screenshot = new GLubyte[size];
926 }
927 catch (std::exception &)
928 {
929 delete[] pixels;
930 delete[] screenshot;
931 throw love::Exception("Out of memory.");
932 }
933
934#ifdef LOVE_IOS
935 SDL_SysWMinfo info = {};
936 SDL_VERSION(&info.version);
937 SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info);
938
939 if (info.info.uikit.resolveFramebuffer != 0)
940 {
941 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer);
942
943 // We need to do an explicit MSAA resolve on iOS, because it uses
944 // GLES FBOs rather than a system framebuffer.
945 if (GLAD_ES_VERSION_3_0)
946 glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
947 else if (GLAD_APPLE_framebuffer_multisample)
948 glResolveMultisampleFramebufferAPPLE();
949
950 gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, info.info.uikit.resolveFramebuffer);
951 }
952#endif
953
954 glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
955
956 // Replace alpha values with full opacity.
957 for (size_t i = 3; i < size; i += 4)
958 pixels[i] = 255;
959
960 // OpenGL sucks and reads pixels from the lower-left. Let's fix that.
961 GLubyte *src = pixels - row;
962 GLubyte *dst = screenshot + size;
963
964 for (int i = 0; i < h; ++i)
965 memcpy(dst-=row, src+=row, row);
966
967 delete[] pixels;
968
969 auto imagemodule = Module::getInstance<love::image::Image>(M_IMAGE);
970
971 for (int i = 0; i < (int) pendingScreenshotCallbacks.size(); i++)
972 {
973 const auto &info = pendingScreenshotCallbacks[i];
974 image::ImageData *img = nullptr;
975
976 try
977 {
978 img = imagemodule->newImageData(w, h, PIXELFORMAT_RGBA8, screenshot);
979 }
980 catch (love::Exception &)
981 {
982 delete[] screenshot;
983 info.callback(&info, nullptr, nullptr);
984 for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++)
985 {
986 const auto &ninfo = pendingScreenshotCallbacks[j];
987 ninfo.callback(&ninfo, nullptr, nullptr);
988 }
989 pendingScreenshotCallbacks.clear();
990 throw;
991 }
992
993 info.callback(&info, img, screenshotCallbackData);
994 img->release();
995 }
996
997 delete[] screenshot;
998 pendingScreenshotCallbacks.clear();
999 }
1000
1001#ifdef LOVE_IOS
1002 // Hack: SDL's color renderbuffer must be bound when swapBuffers is called.
1003 SDL_SysWMinfo info = {};
1004 SDL_VERSION(&info.version);
1005 SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info);
1006 glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer);
1007#endif
1008
1009 for (StreamBuffer *buffer : streamBufferState.vb)
1010 buffer->nextFrame();
1011 streamBufferState.indexBuffer->nextFrame();
1012
1013 auto window = getInstance<love::window::Window>(M_WINDOW);
1014 if (window != nullptr)
1015 window->swapBuffers();
1016
1017 // Reset the per-frame stat counts.
1018 drawCalls = 0;
1019 gl.stats.shaderSwitches = 0;
1020 canvasSwitchCount = 0;
1021 drawCallsBatched = 0;
1022
1023 // This assumes temporary canvases will only be used within a render pass.
1024 for (int i = (int) temporaryCanvases.size() - 1; i >= 0; i--)
1025 {
1026 if (temporaryCanvases[i].framesSinceUse >= MAX_TEMPORARY_CANVAS_UNUSED_FRAMES)
1027 {
1028 temporaryCanvases[i].canvas->release();
1029 temporaryCanvases[i] = temporaryCanvases.back();
1030 temporaryCanvases.pop_back();
1031 }
1032 else
1033 temporaryCanvases[i].framesSinceUse++;
1034 }
1035}
1036
1037void Graphics::setScissor(const Rect &rect)
1038{
1039 flushStreamDraws();
1040
1041 DisplayState &state = states.back();
1042
1043 if (!gl.isStateEnabled(OpenGL::ENABLE_SCISSOR_TEST))
1044 gl.setEnableState(OpenGL::ENABLE_SCISSOR_TEST, true);
1045
1046 double dpiscale = getCurrentDPIScale();
1047
1048 Rect glrect;
1049 glrect.x = (int) (rect.x * dpiscale);
1050 glrect.y = (int) (rect.y * dpiscale);
1051 glrect.w = (int) (rect.w * dpiscale);
1052 glrect.h = (int) (rect.h * dpiscale);
1053
1054 // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
1055 gl.setScissor(glrect, isCanvasActive());
1056
1057 state.scissor = true;
1058 state.scissorRect = rect;
1059}
1060
1061void Graphics::setScissor()
1062{
1063 if (states.back().scissor)
1064 flushStreamDraws();
1065
1066 states.back().scissor = false;
1067
1068 if (gl.isStateEnabled(OpenGL::ENABLE_SCISSOR_TEST))
1069 gl.setEnableState(OpenGL::ENABLE_SCISSOR_TEST, false);
1070}
1071
1072void Graphics::drawToStencilBuffer(StencilAction action, int value)
1073{
1074 const auto &rts = states.back().renderTargets;
1075 love::graphics::Canvas *dscanvas = rts.depthStencil.canvas.get();
1076
1077 if (!isCanvasActive() && !windowHasStencil)
1078 throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer.");
1079 else if (isCanvasActive() && (rts.temporaryRTFlags & TEMPORARY_RT_STENCIL) == 0 && (dscanvas == nullptr || !isPixelFormatStencil(dscanvas->getPixelFormat())))
1080 throw love::Exception("Drawing to the stencil buffer with a Canvas active requires either stencil=true or a custom stencil-type Canvas to be used, in setCanvas.");
1081
1082 flushStreamDraws();
1083
1084 writingToStencil = true;
1085
1086 // Disable color writes but don't save the state for it.
1087 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1088
1089 GLenum glaction = GL_REPLACE;
1090
1091 switch (action)
1092 {
1093 case STENCIL_REPLACE:
1094 default:
1095 glaction = GL_REPLACE;
1096 break;
1097 case STENCIL_INCREMENT:
1098 glaction = GL_INCR;
1099 break;
1100 case STENCIL_DECREMENT:
1101 glaction = GL_DECR;
1102 break;
1103 case STENCIL_INCREMENT_WRAP:
1104 glaction = GL_INCR_WRAP;
1105 break;
1106 case STENCIL_DECREMENT_WRAP:
1107 glaction = GL_DECR_WRAP;
1108 break;
1109 case STENCIL_INVERT:
1110 glaction = GL_INVERT;
1111 break;
1112 }
1113
1114 // The stencil test must be enabled in order to write to the stencil buffer.
1115 if (!gl.isStateEnabled(OpenGL::ENABLE_STENCIL_TEST))
1116 gl.setEnableState(OpenGL::ENABLE_STENCIL_TEST, true);
1117
1118 glStencilFunc(GL_ALWAYS, value, 0xFFFFFFFF);
1119 glStencilOp(GL_KEEP, GL_KEEP, glaction);
1120}
1121
1122void Graphics::stopDrawToStencilBuffer()
1123{
1124 if (!writingToStencil)
1125 return;
1126
1127 flushStreamDraws();
1128
1129 writingToStencil = false;
1130
1131 const DisplayState &state = states.back();
1132
1133 // Revert the color write mask.
1134 setColorMask(state.colorMask);
1135
1136 // Use the user-set stencil test state when writes are disabled.
1137 setStencilTest(state.stencilCompare, state.stencilTestValue);
1138}
1139
1140void Graphics::setStencilTest(CompareMode compare, int value)
1141{
1142 DisplayState &state = states.back();
1143
1144 if (state.stencilCompare != compare || state.stencilTestValue != value)
1145 flushStreamDraws();
1146
1147 state.stencilCompare = compare;
1148 state.stencilTestValue = value;
1149
1150 if (writingToStencil)
1151 return;
1152
1153 if (compare == COMPARE_ALWAYS)
1154 {
1155 if (gl.isStateEnabled(OpenGL::ENABLE_STENCIL_TEST))
1156 gl.setEnableState(OpenGL::ENABLE_STENCIL_TEST, false);
1157 return;
1158 }
1159
1160 /**
1161 * OpenGL / GPUs do the comparison in the opposite way that makes sense
1162 * for this API. For example, if the compare function is GL_GREATER then the
1163 * stencil test will pass if the reference value is greater than the value
1164 * in the stencil buffer. With our API it's more intuitive to assume that
1165 * setStencilTest(COMPARE_GREATER, 4) will make it pass if the stencil
1166 * buffer has a value greater than 4.
1167 **/
1168 GLenum glcompare = OpenGL::getGLCompareMode(getReversedCompareMode(compare));
1169
1170 if (!gl.isStateEnabled(OpenGL::ENABLE_STENCIL_TEST))
1171 gl.setEnableState(OpenGL::ENABLE_STENCIL_TEST, true);
1172
1173 glStencilFunc(glcompare, value, 0xFFFFFFFF);
1174 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
1175}
1176
1177void Graphics::setDepthMode(CompareMode compare, bool write)
1178{
1179 DisplayState &state = states.back();
1180
1181 if (state.depthTest != compare || state.depthWrite != write)
1182 flushStreamDraws();
1183
1184 state.depthTest = compare;
1185 state.depthWrite = write;
1186
1187 bool depthenable = compare != COMPARE_ALWAYS || write;
1188
1189 if (depthenable != gl.isStateEnabled(OpenGL::ENABLE_DEPTH_TEST))
1190 gl.setEnableState(OpenGL::ENABLE_DEPTH_TEST, depthenable);
1191
1192 if (depthenable)
1193 {
1194 glDepthFunc(OpenGL::getGLCompareMode(compare));
1195 gl.setDepthWrites(write);
1196 }
1197}
1198
1199void Graphics::setFrontFaceWinding(vertex::Winding winding)
1200{
1201 DisplayState &state = states.back();
1202
1203 if (state.winding != winding)
1204 flushStreamDraws();
1205
1206 state.winding = winding;
1207
1208 if (isCanvasActive())
1209 winding = winding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
1210
1211 glFrontFace(winding == vertex::WINDING_CW ? GL_CW : GL_CCW);
1212}
1213
1214void Graphics::setColor(Colorf c)
1215{
1216 c.r = std::min(std::max(c.r, 0.0f), 1.0f);
1217 c.g = std::min(std::max(c.g, 0.0f), 1.0f);
1218 c.b = std::min(std::max(c.b, 0.0f), 1.0f);
1219 c.a = std::min(std::max(c.a, 0.0f), 1.0f);
1220
1221 gl.setConstantColor(c);
1222
1223 states.back().color = c;
1224}
1225
1226void Graphics::setColorMask(ColorMask mask)
1227{
1228 flushStreamDraws();
1229
1230 glColorMask(mask.r, mask.g, mask.b, mask.a);
1231 states.back().colorMask = mask;
1232}
1233
1234void Graphics::setBlendMode(BlendMode mode, BlendAlpha alphamode)
1235{
1236 if (mode != states.back().blendMode || alphamode != states.back().blendAlphaMode)
1237 flushStreamDraws();
1238
1239 if (mode == BLEND_LIGHTEN || mode == BLEND_DARKEN)
1240 {
1241 if (!capabilities.features[FEATURE_LIGHTEN])
1242 throw love::Exception("The 'lighten' and 'darken' blend modes are not supported on this system.");
1243 }
1244
1245 if (alphamode != BLENDALPHA_PREMULTIPLIED)
1246 {
1247 const char *modestr = "unknown";
1248 switch (mode)
1249 {
1250 case BLEND_LIGHTEN:
1251 case BLEND_DARKEN:
1252 case BLEND_MULTIPLY:
1253 getConstant(mode, modestr);
1254 throw love::Exception("The '%s' blend mode must be used with premultiplied alpha.", modestr);
1255 break;
1256 default:
1257 break;
1258 }
1259 }
1260
1261 GLenum func = GL_FUNC_ADD;
1262 GLenum srcRGB = GL_ONE;
1263 GLenum srcA = GL_ONE;
1264 GLenum dstRGB = GL_ZERO;
1265 GLenum dstA = GL_ZERO;
1266
1267 switch (mode)
1268 {
1269 case BLEND_ALPHA:
1270 srcRGB = srcA = GL_ONE;
1271 dstRGB = dstA = GL_ONE_MINUS_SRC_ALPHA;
1272 break;
1273 case BLEND_MULTIPLY:
1274 srcRGB = srcA = GL_DST_COLOR;
1275 dstRGB = dstA = GL_ZERO;
1276 break;
1277 case BLEND_SUBTRACT:
1278 func = GL_FUNC_REVERSE_SUBTRACT;
1279 case BLEND_ADD:
1280 srcRGB = GL_ONE;
1281 srcA = GL_ZERO;
1282 dstRGB = dstA = GL_ONE;
1283 break;
1284 case BLEND_LIGHTEN:
1285 func = GL_MAX;
1286 break;
1287 case BLEND_DARKEN:
1288 func = GL_MIN;
1289 break;
1290 case BLEND_SCREEN:
1291 srcRGB = srcA = GL_ONE;
1292 dstRGB = dstA = GL_ONE_MINUS_SRC_COLOR;
1293 break;
1294 case BLEND_REPLACE:
1295 case BLEND_NONE:
1296 default:
1297 srcRGB = srcA = GL_ONE;
1298 dstRGB = dstA = GL_ZERO;
1299 break;
1300 }
1301
1302 // We can only do alpha-multiplication when srcRGB would have been unmodified.
1303 if (srcRGB == GL_ONE && alphamode == BLENDALPHA_MULTIPLY && mode != BLEND_NONE)
1304 srcRGB = GL_SRC_ALPHA;
1305
1306 glBlendEquation(func);
1307 glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
1308
1309 states.back().blendMode = mode;
1310 states.back().blendAlphaMode = alphamode;
1311}
1312
1313void Graphics::setPointSize(float size)
1314{
1315 if (streamBufferState.primitiveMode == PRIMITIVE_POINTS)
1316 flushStreamDraws();
1317
1318 gl.setPointSize(size * getCurrentDPIScale());
1319 states.back().pointSize = size;
1320}
1321
1322void Graphics::setWireframe(bool enable)
1323{
1324 // Not supported in OpenGL ES.
1325 if (GLAD_ES_VERSION_2_0)
1326 return;
1327
1328 flushStreamDraws();
1329
1330 glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
1331 states.back().wireframe = enable;
1332}
1333
1334Graphics::Renderer Graphics::getRenderer() const
1335{
1336 return GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL;
1337}
1338
1339Graphics::RendererInfo Graphics::getRendererInfo() const
1340{
1341 RendererInfo info;
1342
1343 if (GLAD_ES_VERSION_2_0)
1344 info.name = "OpenGL ES";
1345 else
1346 info.name = "OpenGL";
1347
1348 const char *str = (const char *) glGetString(GL_VERSION);
1349 if (str)
1350 info.version = str;
1351 else
1352 throw love::Exception("Cannot retrieve renderer version information.");
1353
1354 str = (const char *) glGetString(GL_VENDOR);
1355 if (str)
1356 info.vendor = str;
1357 else
1358 throw love::Exception("Cannot retrieve renderer vendor information.");
1359
1360 str = (const char *) glGetString(GL_RENDERER);
1361 if (str)
1362 info.device = str;
1363 else
1364 throw love::Exception("Cannot retrieve renderer device information.");
1365
1366 return info;
1367}
1368
1369void Graphics::getAPIStats(int &shaderswitches) const
1370{
1371 shaderswitches = gl.stats.shaderSwitches;
1372}
1373
1374void Graphics::initCapabilities()
1375{
1376 capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported();
1377 capabilities.features[FEATURE_CLAMP_ZERO] = gl.isClampZeroTextureWrapSupported();
1378 capabilities.features[FEATURE_LIGHTEN] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax;
1379 capabilities.features[FEATURE_FULL_NPOT] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
1380 capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = gl.isPixelShaderHighpSupported();
1381 capabilities.features[FEATURE_SHADER_DERIVATIVES] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_standard_derivatives;
1382 capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
1383 capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
1384 static_assert(FEATURE_MAX_ENUM == 8, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
1385
1386 capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
1387 capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
1388 capabilities.limits[LIMIT_TEXTURE_LAYERS] = gl.getMaxTextureLayers();
1389 capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = gl.getMax3DTextureSize();
1390 capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = gl.getMaxCubeTextureSize();
1391 capabilities.limits[LIMIT_MULTI_CANVAS] = gl.getMaxRenderTargets();
1392 capabilities.limits[LIMIT_CANVAS_MSAA] = gl.getMaxRenderbufferSamples();
1393 capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy();
1394 static_assert(LIMIT_MAX_ENUM == 8, "Graphics::initCapabilities must be updated when adding a new system limit!");
1395
1396 for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
1397 capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i);
1398}
1399
1400bool Graphics::isCanvasFormatSupported(PixelFormat format) const
1401{
1402 return Canvas::isFormatSupported(format);
1403}
1404
1405bool Graphics::isCanvasFormatSupported(PixelFormat format, bool readable) const
1406{
1407 return Canvas::isFormatSupported(format, readable);
1408}
1409
1410bool Graphics::isImageFormatSupported(PixelFormat format, bool sRGB) const
1411{
1412 return Image::isFormatSupported(format, sRGB);
1413}
1414
1415Shader::Language Graphics::getShaderLanguageTarget() const
1416{
1417 if (gl.isCoreProfile())
1418 return Shader::LANGUAGE_GLSL3;
1419 else if (GLAD_ES_VERSION_3_0)
1420 return Shader::LANGUAGE_ESSL3;
1421 else if (GLAD_ES_VERSION_2_0)
1422 return Shader::LANGUAGE_ESSL1;
1423 else
1424 return Shader::LANGUAGE_GLSL1;
1425}
1426
1427} // opengl
1428} // graphics
1429} // love
1430