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 "Graphics.h"
23#include "Buffer.h"
24#include "math/MathModule.h"
25#include "data/DataModule.h"
26#include "Polyline.h"
27#include "font/Font.h"
28#include "window/Window.h"
29#include "SpriteBatch.h"
30#include "ParticleSystem.h"
31#include "Font.h"
32#include "Video.h"
33#include "Text.h"
34#include "common/deprecation.h"
35
36// C++
37#include <algorithm>
38#include <stdlib.h>
39
40namespace love
41{
42namespace graphics
43{
44
45static bool gammaCorrect = false;
46static bool debugMode = false;
47static bool debugModeQueried = false;
48
49void setGammaCorrect(bool gammacorrect)
50{
51 gammaCorrect = gammacorrect;
52}
53
54bool isGammaCorrect()
55{
56 return gammaCorrect;
57}
58
59void gammaCorrectColor(Colorf &c)
60{
61 if (isGammaCorrect())
62 {
63 c.r = math::gammaToLinear(c.r);
64 c.g = math::gammaToLinear(c.g);
65 c.b = math::gammaToLinear(c.b);
66 }
67}
68
69Colorf gammaCorrectColor(const Colorf &c)
70{
71 Colorf r = c;
72 gammaCorrectColor(r);
73 return r;
74}
75
76void unGammaCorrectColor(Colorf &c)
77{
78 if (isGammaCorrect())
79 {
80 c.r = math::linearToGamma(c.r);
81 c.g = math::linearToGamma(c.g);
82 c.b = math::linearToGamma(c.b);
83 }
84}
85
86Colorf unGammaCorrectColor(const Colorf &c)
87{
88 Colorf r = c;
89 unGammaCorrectColor(r);
90 return r;
91}
92
93bool isDebugEnabled()
94{
95 if (!debugModeQueried)
96 {
97 const char *debugenv = getenv("LOVE_GRAPHICS_DEBUG");
98 debugMode = debugenv != nullptr && debugenv[0] != '0';
99 debugModeQueried = true;
100 }
101
102 return debugMode;
103}
104
105love::Type Graphics::type("graphics", &Module::type);
106
107Graphics::DefaultShaderCode Graphics::defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
108
109Graphics::Graphics()
110 : width(0)
111 , height(0)
112 , pixelWidth(0)
113 , pixelHeight(0)
114 , created(false)
115 , active(true)
116 , writingToStencil(false)
117 , streamBufferState()
118 , projectionMatrix()
119 , canvasSwitchCount(0)
120 , drawCalls(0)
121 , drawCallsBatched(0)
122 , quadIndexBuffer(nullptr)
123 , capabilities()
124 , cachedShaderStages()
125{
126 transformStack.reserve(16);
127 transformStack.push_back(Matrix4());
128
129 pixelScaleStack.reserve(16);
130 pixelScaleStack.push_back(1);
131
132 states.reserve(10);
133 states.push_back(DisplayState());
134
135 if (!Shader::initialize())
136 throw love::Exception("Shader support failed to initialize!");
137}
138
139Graphics::~Graphics()
140{
141 delete quadIndexBuffer;
142
143 // Clean up standard shaders before the active shader. If we do it after,
144 // the active shader may try to activate a standard shader when deactivating
145 // itself, which will cause problems since it calls Graphics methods in the
146 // Graphics destructor.
147 for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
148 {
149 if (Shader::standardShaders[i])
150 {
151 Shader::standardShaders[i]->release();
152 Shader::standardShaders[i] = nullptr;
153 }
154 }
155
156 states.clear();
157
158 defaultFont.set(nullptr);
159
160 delete streamBufferState.vb[0];
161 delete streamBufferState.vb[1];
162 delete streamBufferState.indexBuffer;
163
164 for (int i = 0; i < (int) ShaderStage::STAGE_MAX_ENUM; i++)
165 cachedShaderStages[i].clear();
166
167 Shader::deinitialize();
168}
169
170void Graphics::createQuadIndexBuffer()
171{
172 if (quadIndexBuffer != nullptr)
173 return;
174
175 size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
176 quadIndexBuffer = newBuffer(size, nullptr, BUFFER_INDEX, vertex::USAGE_STATIC, 0);
177
178 Buffer::Mapper map(*quadIndexBuffer);
179 vertex::fillIndices(vertex::TriangleIndexMode::QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.get());
180}
181
182Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
183{
184 return new Quad(v, sw, sh);
185}
186
187Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &filter)
188{
189 return new Font(data, filter);
190}
191
192Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter)
193{
194 auto fontmodule = Module::getInstance<font::Font>(M_FONT);
195 if (!fontmodule)
196 throw love::Exception("Font module has not been loaded.");
197
198 StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN);
199 return newFont(r.get(), filter);
200}
201
202Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
203{
204 return new Video(this, stream, dpiscale);
205}
206
207love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage)
208{
209 return new SpriteBatch(this, texture, size, usage);
210}
211
212love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
213{
214 return new ParticleSystem(texture, size);
215}
216
217ShaderStage *Graphics::newShaderStage(ShaderStage::StageType stage, const std::string &optsource)
218{
219 if (stage == ShaderStage::STAGE_MAX_ENUM)
220 throw love::Exception("Invalid shader stage.");
221
222 const std::string &source = optsource.empty() ? getCurrentDefaultShaderCode().source[stage] : optsource;
223
224 ShaderStage *s = nullptr;
225 std::string cachekey;
226
227 if (!source.empty())
228 {
229 data::HashFunction::Value hashvalue;
230 data::hash(data::HashFunction::FUNCTION_SHA1, source.c_str(), source.size(), hashvalue);
231
232 cachekey = std::string(hashvalue.data, hashvalue.size);
233
234 auto it = cachedShaderStages[stage].find(cachekey);
235 if (it != cachedShaderStages[stage].end())
236 {
237 s = it->second;
238 s->retain();
239 }
240 }
241
242 if (s == nullptr)
243 {
244 s = newShaderStageInternal(stage, cachekey, source, getRenderer() == RENDERER_OPENGLES);
245 if (!cachekey.empty())
246 cachedShaderStages[stage][cachekey] = s;
247 }
248
249 return s;
250}
251
252Shader *Graphics::newShader(const std::string &vertex, const std::string &pixel)
253{
254 if (vertex.empty() && pixel.empty())
255 throw love::Exception("Error creating shader: no source code!");
256
257 StrongRef<ShaderStage> vertexstage(newShaderStage(ShaderStage::STAGE_VERTEX, vertex), Acquire::NORETAIN);
258 StrongRef<ShaderStage> pixelstage(newShaderStage(ShaderStage::STAGE_PIXEL, pixel), Acquire::NORETAIN);
259
260 return newShaderInternal(vertexstage.get(), pixelstage.get());
261}
262
263Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage)
264{
265 return newMesh(Mesh::getDefaultVertexFormat(), &vertices[0], vertices.size() * sizeof(Vertex), drawmode, usage);
266}
267
268Mesh *Graphics::newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
269{
270 return newMesh(Mesh::getDefaultVertexFormat(), vertexcount, drawmode, usage);
271}
272
273love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
274{
275 return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
276}
277
278love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
279{
280 return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
281}
282
283love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<Font::ColoredString> &text)
284{
285 return new Text(font, text);
286}
287
288void Graphics::cleanupCachedShaderStage(ShaderStage::StageType type, const std::string &hashkey)
289{
290 cachedShaderStages[type].erase(hashkey);
291}
292
293bool Graphics::validateShader(bool gles, const std::string &vertex, const std::string &pixel, std::string &err)
294{
295 if (vertex.empty() && pixel.empty())
296 {
297 err = "Error validating shader: no source code!";
298 return false;
299 }
300
301 StrongRef<ShaderStage> vertexstage;
302 StrongRef<ShaderStage> pixelstage;
303
304 // Don't use cached shader stages, since the gles flag may not match the
305 // current renderer.
306 if (!vertex.empty())
307 vertexstage.set(new ShaderStageForValidation(this, ShaderStage::STAGE_VERTEX, vertex, gles), Acquire::NORETAIN);
308
309 if (!pixel.empty())
310 pixelstage.set(new ShaderStageForValidation(this, ShaderStage::STAGE_PIXEL, pixel, gles), Acquire::NORETAIN);
311
312 return Shader::validate(vertexstage.get(), pixelstage.get(), err);
313}
314
315int Graphics::getWidth() const
316{
317 return width;
318}
319
320int Graphics::getHeight() const
321{
322 return height;
323}
324
325int Graphics::getPixelWidth() const
326{
327 return pixelWidth;
328}
329
330int Graphics::getPixelHeight() const
331{
332 return pixelHeight;
333}
334
335double Graphics::getCurrentDPIScale() const
336{
337 const auto &rt = states.back().renderTargets.getFirstTarget();
338 if (rt.canvas.get())
339 return rt.canvas->getDPIScale();
340
341 return getScreenDPIScale();
342}
343
344double Graphics::getScreenDPIScale() const
345{
346 return (double) getPixelHeight() / (double) getHeight();
347}
348
349bool Graphics::isCreated() const
350{
351 return created;
352}
353
354bool Graphics::isActive() const
355{
356 // The graphics module is only completely 'active' if there's a window, a
357 // context, and the active variable is set.
358 auto window = getInstance<love::window::Window>(M_WINDOW);
359 return active && isCreated() && window != nullptr && window->isOpen();
360}
361
362void Graphics::reset()
363{
364 DisplayState s;
365 stopDrawToStencilBuffer();
366 restoreState(s);
367 origin();
368}
369
370/**
371 * State functions.
372 **/
373
374void Graphics::restoreState(const DisplayState &s)
375{
376 setColor(s.color);
377 setBackgroundColor(s.backgroundColor);
378
379 setBlendMode(s.blendMode, s.blendAlphaMode);
380
381 setLineWidth(s.lineWidth);
382 setLineStyle(s.lineStyle);
383 setLineJoin(s.lineJoin);
384
385 setPointSize(s.pointSize);
386
387 if (s.scissor)
388 setScissor(s.scissorRect);
389 else
390 setScissor();
391
392 setStencilTest(s.stencilCompare, s.stencilTestValue);
393 setDepthMode(s.depthTest, s.depthWrite);
394
395 setMeshCullMode(s.meshCullMode);
396 setFrontFaceWinding(s.winding);
397
398 setFont(s.font.get());
399 setShader(s.shader.get());
400 setCanvas(s.renderTargets);
401
402 setColorMask(s.colorMask);
403 setWireframe(s.wireframe);
404
405 setDefaultFilter(s.defaultFilter);
406 setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness);
407}
408
409void Graphics::restoreStateChecked(const DisplayState &s)
410{
411 const DisplayState &cur = states.back();
412
413 if (s.color != cur.color)
414 setColor(s.color);
415
416 setBackgroundColor(s.backgroundColor);
417
418 if (s.blendMode != cur.blendMode || s.blendAlphaMode != cur.blendAlphaMode)
419 setBlendMode(s.blendMode, s.blendAlphaMode);
420
421 // These are just simple assignments.
422 setLineWidth(s.lineWidth);
423 setLineStyle(s.lineStyle);
424 setLineJoin(s.lineJoin);
425
426 if (s.pointSize != cur.pointSize)
427 setPointSize(s.pointSize);
428
429 if (s.scissor != cur.scissor || (s.scissor && !(s.scissorRect == cur.scissorRect)))
430 {
431 if (s.scissor)
432 setScissor(s.scissorRect);
433 else
434 setScissor();
435 }
436
437 if (s.stencilCompare != cur.stencilCompare || s.stencilTestValue != cur.stencilTestValue)
438 setStencilTest(s.stencilCompare, s.stencilTestValue);
439
440 if (s.depthTest != cur.depthTest || s.depthWrite != cur.depthWrite)
441 setDepthMode(s.depthTest, s.depthWrite);
442
443 setMeshCullMode(s.meshCullMode);
444
445 if (s.winding != cur.winding)
446 setFrontFaceWinding(s.winding);
447
448 setFont(s.font.get());
449 setShader(s.shader.get());
450
451 const auto &sRTs = s.renderTargets;
452 const auto &curRTs = cur.renderTargets;
453
454 bool canvaseschanged = sRTs.colors.size() != curRTs.colors.size();
455 if (!canvaseschanged)
456 {
457 for (size_t i = 0; i < sRTs.colors.size() && i < curRTs.colors.size(); i++)
458 {
459 if (sRTs.colors[i] != curRTs.colors[i])
460 {
461 canvaseschanged = true;
462 break;
463 }
464 }
465
466 if (!canvaseschanged && sRTs.depthStencil != curRTs.depthStencil)
467 canvaseschanged = true;
468
469 if (sRTs.temporaryRTFlags != curRTs.temporaryRTFlags)
470 canvaseschanged = true;
471 }
472
473 if (canvaseschanged)
474 setCanvas(s.renderTargets);
475
476 if (s.colorMask != cur.colorMask)
477 setColorMask(s.colorMask);
478
479 if (s.wireframe != cur.wireframe)
480 setWireframe(s.wireframe);
481
482 setDefaultFilter(s.defaultFilter);
483 setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness);
484}
485
486Colorf Graphics::getColor() const
487{
488 return states.back().color;
489}
490
491void Graphics::setBackgroundColor(Colorf c)
492{
493 states.back().backgroundColor = c;
494}
495
496Colorf Graphics::getBackgroundColor() const
497{
498 return states.back().backgroundColor;
499}
500
501void Graphics::checkSetDefaultFont()
502{
503 // We don't create or set the default Font if an existing font is in use.
504 if (states.back().font.get() != nullptr)
505 return;
506
507 // Create a new default font if we don't have one yet.
508 if (!defaultFont.get())
509 defaultFont.set(newDefaultFont(12, font::TrueTypeRasterizer::HINTING_NORMAL), Acquire::NORETAIN);
510
511 states.back().font.set(defaultFont.get());
512}
513
514void Graphics::setFont(love::graphics::Font *font)
515{
516 // We don't need to set a default font here if null is passed in, since we
517 // only care about the default font in getFont and print.
518 DisplayState &state = states.back();
519 state.font.set(font);
520}
521
522love::graphics::Font *Graphics::getFont()
523{
524 checkSetDefaultFont();
525 return states.back().font.get();
526}
527
528void Graphics::setShader(love::graphics::Shader *shader)
529{
530 if (shader == nullptr)
531 return setShader();
532
533 shader->attach();
534 states.back().shader.set(shader);
535}
536
537void Graphics::setShader()
538{
539 Shader::attachDefault(Shader::STANDARD_DEFAULT);
540 states.back().shader.set(nullptr);
541}
542
543love::graphics::Shader *Graphics::getShader() const
544{
545 return states.back().shader.get();
546}
547
548void Graphics::setCanvas(RenderTarget rt, uint32 temporaryRTFlags)
549{
550 if (rt.canvas == nullptr)
551 return setCanvas();
552
553 RenderTargets rts;
554 rts.colors.push_back(rt);
555 rts.temporaryRTFlags = temporaryRTFlags;
556
557 setCanvas(rts);
558}
559
560void Graphics::setCanvas(const RenderTargetsStrongRef &rts)
561{
562 RenderTargets targets;
563 targets.colors.reserve(rts.colors.size());
564
565 for (const auto &rt : rts.colors)
566 targets.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap);
567
568 targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice, rts.depthStencil.mipmap);
569 targets.temporaryRTFlags = rts.temporaryRTFlags;
570
571 return setCanvas(targets);
572}
573
574void Graphics::setCanvas(const RenderTargets &rts)
575{
576 DisplayState &state = states.back();
577 int ncanvases = (int) rts.colors.size();
578
579 RenderTarget firsttarget = rts.getFirstTarget();
580 love::graphics::Canvas *firstcanvas = firsttarget.canvas;
581
582 if (firstcanvas == nullptr)
583 return setCanvas();
584
585 const auto &prevRTs = state.renderTargets;
586
587 if (ncanvases == (int) prevRTs.colors.size())
588 {
589 bool modified = false;
590
591 for (int i = 0; i < ncanvases; i++)
592 {
593 if (rts.colors[i] != prevRTs.colors[i])
594 {
595 modified = true;
596 break;
597 }
598 }
599
600 if (!modified && rts.depthStencil != prevRTs.depthStencil)
601 modified = true;
602
603 if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
604 modified = true;
605
606 if (!modified)
607 return;
608 }
609
610 if (ncanvases > capabilities.limits[LIMIT_MULTI_CANVAS])
611 throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
612
613 bool multiformatsupported = capabilities.features[FEATURE_MULTI_CANVAS_FORMATS];
614
615 PixelFormat firstcolorformat = PIXELFORMAT_UNKNOWN;
616 if (!rts.colors.empty())
617 firstcolorformat = rts.colors[0].canvas->getPixelFormat();
618
619 if (isPixelFormatDepthStencil(firstcolorformat))
620 throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
621
622 if (firsttarget.mipmap < 0 || firsttarget.mipmap >= firstcanvas->getMipmapCount())
623 throw love::Exception("Invalid mipmap level %d.", firsttarget.mipmap + 1);
624
625 if (!firstcanvas->isValidSlice(firsttarget.slice))
626 throw love::Exception("Invalid slice index: %d.", firsttarget.slice + 1);
627
628 bool hasSRGBcanvas = firstcolorformat == PIXELFORMAT_sRGBA8;
629 int pixelw = firstcanvas->getPixelWidth(firsttarget.mipmap);
630 int pixelh = firstcanvas->getPixelHeight(firsttarget.mipmap);
631 int reqmsaa = firstcanvas->getRequestedMSAA();
632
633 for (int i = 1; i < ncanvases; i++)
634 {
635 love::graphics::Canvas *c = rts.colors[i].canvas;
636 PixelFormat format = c->getPixelFormat();
637 int mip = rts.colors[i].mipmap;
638 int slice = rts.colors[i].slice;
639
640 if (mip < 0 || mip >= c->getMipmapCount())
641 throw love::Exception("Invalid mipmap level %d.", mip + 1);
642
643 if (!c->isValidSlice(slice))
644 throw love::Exception("Invalid slice index: %d.", slice + 1);
645
646 if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
647 throw love::Exception("All canvases must have the same pixel dimensions.");
648
649 if (!multiformatsupported && format != firstcolorformat)
650 throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
651
652 if (c->getRequestedMSAA() != reqmsaa)
653 throw love::Exception("All Canvases must have the same MSAA value.");
654
655 if (isPixelFormatDepthStencil(format))
656 throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
657
658 if (format == PIXELFORMAT_sRGBA8)
659 hasSRGBcanvas = true;
660 }
661
662 if (rts.depthStencil.canvas != nullptr)
663 {
664 love::graphics::Canvas *c = rts.depthStencil.canvas;
665 int mip = rts.depthStencil.mipmap;
666 int slice = rts.depthStencil.slice;
667
668 if (!isPixelFormatDepthStencil(c->getPixelFormat()))
669 throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
670
671 if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
672 throw love::Exception("All canvases must have the same pixel dimensions.");
673
674 if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
675 throw love::Exception("All Canvases must have the same MSAA value.");
676
677 if (mip < 0 || mip >= c->getMipmapCount())
678 throw love::Exception("Invalid mipmap level %d.", mip + 1);
679
680 if (!c->isValidSlice(slice))
681 throw love::Exception("Invalid slice index: %d.", slice + 1);
682 }
683
684 int w = firstcanvas->getWidth(firsttarget.mipmap);
685 int h = firstcanvas->getHeight(firsttarget.mipmap);
686
687 flushStreamDraws();
688
689 if (rts.depthStencil.canvas == nullptr && rts.temporaryRTFlags != 0)
690 {
691 bool wantsdepth = (rts.temporaryRTFlags & TEMPORARY_RT_DEPTH) != 0;
692 bool wantsstencil = (rts.temporaryRTFlags & TEMPORARY_RT_STENCIL) != 0;
693
694 PixelFormat dsformat = PIXELFORMAT_STENCIL8;
695 if (wantsdepth && wantsstencil)
696 dsformat = PIXELFORMAT_DEPTH24_STENCIL8;
697 else if (wantsdepth && isCanvasFormatSupported(PIXELFORMAT_DEPTH24, false))
698 dsformat = PIXELFORMAT_DEPTH24;
699 else if (wantsdepth)
700 dsformat = PIXELFORMAT_DEPTH16;
701 else if (wantsstencil)
702 dsformat = PIXELFORMAT_STENCIL8;
703
704 // We want setCanvasInternal to have a pointer to the temporary RT, but
705 // we don't want to directly store it in the main graphics state.
706 RenderTargets realRTs = rts;
707
708 realRTs.depthStencil.canvas = getTemporaryCanvas(dsformat, pixelw, pixelh, reqmsaa);
709 realRTs.depthStencil.slice = 0;
710
711 setCanvasInternal(realRTs, w, h, pixelw, pixelh, hasSRGBcanvas);
712 }
713 else
714 setCanvasInternal(rts, w, h, pixelw, pixelh, hasSRGBcanvas);
715
716 RenderTargetsStrongRef refs;
717 refs.colors.reserve(rts.colors.size());
718
719 for (auto c : rts.colors)
720 refs.colors.emplace_back(c.canvas, c.slice, c.mipmap);
721
722 refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
723 refs.temporaryRTFlags = rts.temporaryRTFlags;
724
725 std::swap(state.renderTargets, refs);
726
727 canvasSwitchCount++;
728}
729
730void Graphics::setCanvas()
731{
732 DisplayState &state = states.back();
733
734 if (state.renderTargets.colors.empty() && state.renderTargets.depthStencil.canvas == nullptr)
735 return;
736
737 flushStreamDraws();
738 setCanvasInternal(RenderTargets(), width, height, pixelWidth, pixelHeight, isGammaCorrect());
739
740 state.renderTargets = RenderTargetsStrongRef();
741 canvasSwitchCount++;
742}
743
744Graphics::RenderTargets Graphics::getCanvas() const
745{
746 const auto &curRTs = states.back().renderTargets;
747
748 RenderTargets rts;
749 rts.colors.reserve(curRTs.colors.size());
750
751 for (const auto &rt : curRTs.colors)
752 rts.colors.emplace_back(rt.canvas.get(), rt.slice, rt.mipmap);
753
754 rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice, curRTs.depthStencil.mipmap);
755 rts.temporaryRTFlags = curRTs.temporaryRTFlags;
756
757 return rts;
758}
759
760bool Graphics::isCanvasActive() const
761{
762 const auto &rts = states.back().renderTargets;
763 return !rts.colors.empty() || rts.depthStencil.canvas != nullptr;
764}
765
766bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const
767{
768 const auto &rts = states.back().renderTargets;
769
770 for (const auto &rt : rts.colors)
771 {
772 if (rt.canvas.get() == canvas)
773 return true;
774 }
775
776 if (rts.depthStencil.canvas.get() == canvas)
777 return true;
778
779 return false;
780}
781
782bool Graphics::isCanvasActive(Canvas *canvas, int slice) const
783{
784 const auto &rts = states.back().renderTargets;
785
786 for (const auto &rt : rts.colors)
787 {
788 if (rt.canvas.get() == canvas && rt.slice == slice)
789 return true;
790 }
791
792 if (rts.depthStencil.canvas.get() == canvas && rts.depthStencil.slice == slice)
793 return true;
794
795 return false;
796}
797
798Canvas *Graphics::getTemporaryCanvas(PixelFormat format, int w, int h, int samples)
799{
800 love::graphics::Canvas *canvas = nullptr;
801
802 for (TemporaryCanvas &temp : temporaryCanvases)
803 {
804 Canvas *c = temp.canvas;
805 if (c->getPixelFormat() == format && c->getPixelWidth() == w
806 && c->getPixelHeight() == h && c->getRequestedMSAA() == samples)
807 {
808 canvas = c;
809 temp.framesSinceUse = 0;
810 break;
811 }
812 }
813
814 if (canvas == nullptr)
815 {
816 Canvas::Settings settings;
817 settings.format = format;
818 settings.width = w;
819 settings.height = h;
820 settings.msaa = samples;
821
822 canvas = newCanvas(settings);
823
824 temporaryCanvases.emplace_back(canvas);
825 }
826
827 return canvas;
828}
829
830void Graphics::intersectScissor(const Rect &rect)
831{
832 Rect currect = states.back().scissorRect;
833
834 if (!states.back().scissor)
835 {
836 currect.x = 0;
837 currect.y = 0;
838 currect.w = std::numeric_limits<int>::max();
839 currect.h = std::numeric_limits<int>::max();
840 }
841
842 int x1 = std::max(currect.x, rect.x);
843 int y1 = std::max(currect.y, rect.y);
844
845 int x2 = std::min(currect.x + currect.w, rect.x + rect.w);
846 int y2 = std::min(currect.y + currect.h, rect.y + rect.h);
847
848 Rect newrect = {x1, y1, std::max(0, x2 - x1), std::max(0, y2 - y1)};
849 setScissor(newrect);
850}
851
852bool Graphics::getScissor(Rect &rect) const
853{
854 const DisplayState &state = states.back();
855 rect = state.scissorRect;
856 return state.scissor;
857}
858
859void Graphics::setStencilTest()
860{
861 setStencilTest(COMPARE_ALWAYS, 0);
862}
863
864void Graphics::getStencilTest(CompareMode &compare, int &value) const
865{
866 const DisplayState &state = states.back();
867 compare = state.stencilCompare;
868 value = state.stencilTestValue;
869}
870
871void Graphics::setDepthMode()
872{
873 setDepthMode(COMPARE_ALWAYS, false);
874}
875
876void Graphics::getDepthMode(CompareMode &compare, bool &write) const
877{
878 const DisplayState &state = states.back();
879 compare = state.depthTest;
880 write = state.depthWrite;
881}
882
883void Graphics::setMeshCullMode(CullMode cull)
884{
885 // Handled inside the draw() graphics API implementations.
886 states.back().meshCullMode = cull;
887}
888
889CullMode Graphics::getMeshCullMode() const
890{
891 return states.back().meshCullMode;
892}
893
894vertex::Winding Graphics::getFrontFaceWinding() const
895{
896 return states.back().winding;
897}
898
899Graphics::ColorMask Graphics::getColorMask() const
900{
901 return states.back().colorMask;
902}
903
904Graphics::BlendMode Graphics::getBlendMode(BlendAlpha &alphamode) const
905{
906 alphamode = states.back().blendAlphaMode;
907 return states.back().blendMode;
908}
909
910void Graphics::setDefaultFilter(const Texture::Filter &f)
911{
912 Texture::defaultFilter = f;
913 states.back().defaultFilter = f;
914}
915
916const Texture::Filter &Graphics::getDefaultFilter() const
917{
918 return Texture::defaultFilter;
919}
920
921void Graphics::setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness)
922{
923 Texture::defaultMipmapFilter = filter;
924 Texture::defaultMipmapSharpness = sharpness;
925
926 states.back().defaultMipmapFilter = filter;
927 states.back().defaultMipmapSharpness = sharpness;
928}
929
930void Graphics::getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const
931{
932 *filter = Texture::defaultMipmapFilter;
933 *sharpness = Texture::defaultMipmapSharpness;
934}
935
936void Graphics::setLineWidth(float width)
937{
938 states.back().lineWidth = width;
939}
940
941void Graphics::setLineStyle(Graphics::LineStyle style)
942{
943 states.back().lineStyle = style;
944}
945
946void Graphics::setLineJoin(Graphics::LineJoin join)
947{
948 states.back().lineJoin = join;
949}
950
951float Graphics::getLineWidth() const
952{
953 return states.back().lineWidth;
954}
955
956Graphics::LineStyle Graphics::getLineStyle() const
957{
958 return states.back().lineStyle;
959}
960
961Graphics::LineJoin Graphics::getLineJoin() const
962{
963 return states.back().lineJoin;
964}
965
966float Graphics::getPointSize() const
967{
968 return states.back().pointSize;
969}
970
971bool Graphics::isWireframe() const
972{
973 return states.back().wireframe;
974}
975
976void Graphics::captureScreenshot(const ScreenshotInfo &info)
977{
978 pendingScreenshotCallbacks.push_back(info);
979}
980
981Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawCommand &cmd)
982{
983 using namespace vertex;
984
985 StreamBufferState &state = streamBufferState;
986
987 bool shouldflush = false;
988 bool shouldresize = false;
989
990 if (cmd.primitiveMode != state.primitiveMode
991 || cmd.formats[0] != state.formats[0] || cmd.formats[1] != state.formats[1]
992 || ((cmd.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
993 || cmd.texture != state.texture
994 || cmd.standardShaderType != state.standardShaderType)
995 {
996 shouldflush = true;
997 }
998
999 int totalvertices = state.vertexCount + cmd.vertexCount;
1000
1001 // We only support uint16 index buffers for now.
1002 if (totalvertices > LOVE_UINT16_MAX && cmd.indexMode != TriangleIndexMode::NONE)
1003 shouldflush = true;
1004
1005 int reqIndexCount = getIndexCount(cmd.indexMode, cmd.vertexCount);
1006 size_t reqIndexSize = reqIndexCount * sizeof(uint16);
1007
1008 size_t newdatasizes[2] = {0, 0};
1009 size_t buffersizes[3] = {0, 0, 0};
1010
1011 for (int i = 0; i < 2; i++)
1012 {
1013 if (cmd.formats[i] == CommonFormat::NONE)
1014 continue;
1015
1016 size_t stride = getFormatStride(cmd.formats[i]);
1017 size_t datasize = stride * totalvertices;
1018
1019 if (state.vbMap[i].data != nullptr && datasize > state.vbMap[i].size)
1020 shouldflush = true;
1021
1022 if (datasize > state.vb[i]->getUsableSize())
1023 {
1024 buffersizes[i] = std::max(datasize, state.vb[i]->getSize() * 2);
1025 shouldresize = true;
1026 }
1027
1028 newdatasizes[i] = stride * cmd.vertexCount;
1029 }
1030
1031 if (cmd.indexMode != TriangleIndexMode::NONE)
1032 {
1033 size_t datasize = (state.indexCount + reqIndexCount) * sizeof(uint16);
1034
1035 if (state.indexBufferMap.data != nullptr && datasize > state.indexBufferMap.size)
1036 shouldflush = true;
1037
1038 if (datasize > state.indexBuffer->getUsableSize())
1039 {
1040 buffersizes[2] = std::max(datasize, state.indexBuffer->getSize() * 2);
1041 shouldresize = true;
1042 }
1043 }
1044
1045 if (shouldflush || shouldresize)
1046 {
1047 flushStreamDraws();
1048
1049 state.primitiveMode = cmd.primitiveMode;
1050 state.formats[0] = cmd.formats[0];
1051 state.formats[1] = cmd.formats[1];
1052 state.texture = cmd.texture;
1053 state.standardShaderType = cmd.standardShaderType;
1054 }
1055
1056 if (state.vertexCount == 0 && Shader::isDefaultActive())
1057 Shader::attachDefault(state.standardShaderType);
1058
1059 if (state.vertexCount == 0 && Shader::current != nullptr && cmd.texture != nullptr)
1060 Shader::current->checkMainTexture(cmd.texture);
1061
1062 if (shouldresize)
1063 {
1064 for (int i = 0; i < 2; i++)
1065 {
1066 if (state.vb[i]->getSize() < buffersizes[i])
1067 {
1068 delete state.vb[i];
1069 state.vb[i] = newStreamBuffer(BUFFER_VERTEX, buffersizes[i]);
1070 }
1071 }
1072
1073 if (state.indexBuffer->getSize() < buffersizes[2])
1074 {
1075 delete state.indexBuffer;
1076 state.indexBuffer = newStreamBuffer(BUFFER_INDEX, buffersizes[2]);
1077 }
1078 }
1079
1080 if (cmd.indexMode != TriangleIndexMode::NONE)
1081 {
1082 if (state.indexBufferMap.data == nullptr)
1083 state.indexBufferMap = state.indexBuffer->map(reqIndexSize);
1084
1085 uint16 *indices = (uint16 *) state.indexBufferMap.data;
1086 fillIndices(cmd.indexMode, state.vertexCount, cmd.vertexCount, indices);
1087
1088 state.indexBufferMap.data += reqIndexSize;
1089 }
1090
1091 StreamVertexData d;
1092
1093 for (int i = 0; i < 2; i++)
1094 {
1095 if (newdatasizes[i] > 0)
1096 {
1097 if (state.vbMap[i].data == nullptr)
1098 state.vbMap[i] = state.vb[i]->map(newdatasizes[i]);
1099
1100 d.stream[i] = state.vbMap[i].data;
1101
1102 state.vbMap[i].data += newdatasizes[i];
1103 }
1104 }
1105
1106 if (state.vertexCount > 0)
1107 drawCallsBatched++;
1108
1109 state.vertexCount += cmd.vertexCount;
1110 state.indexCount += reqIndexCount;
1111
1112 return d;
1113}
1114
1115void Graphics::flushStreamDraws()
1116{
1117 using namespace vertex;
1118
1119 auto &sbstate = streamBufferState;
1120
1121 if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
1122 return;
1123
1124 Attributes attributes;
1125 BufferBindings buffers;
1126
1127 size_t usedsizes[3] = {0, 0, 0};
1128
1129 for (int i = 0; i < 2; i++)
1130 {
1131 if (sbstate.formats[i] == CommonFormat::NONE)
1132 continue;
1133
1134 attributes.setCommonFormat(sbstate.formats[i], (uint8) i);
1135
1136 usedsizes[i] = getFormatStride(sbstate.formats[i]) * sbstate.vertexCount;
1137
1138 size_t offset = sbstate.vb[i]->unmap(usedsizes[i]);
1139 buffers.set(i, sbstate.vb[i], offset);
1140 sbstate.vbMap[i] = StreamBuffer::MapInfo();
1141 }
1142
1143 if (attributes.enableBits == 0)
1144 return;
1145
1146 Colorf nc = getColor();
1147 if (attributes.isEnabled(ATTRIB_COLOR))
1148 setColor(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
1149
1150 pushIdentityTransform();
1151
1152 if (sbstate.indexCount > 0)
1153 {
1154 usedsizes[2] = sizeof(uint16) * sbstate.indexCount;
1155
1156 DrawIndexedCommand cmd(&attributes, &buffers, sbstate.indexBuffer);
1157 cmd.primitiveType = sbstate.primitiveMode;
1158 cmd.indexCount = sbstate.indexCount;
1159 cmd.indexType = INDEX_UINT16;
1160 cmd.indexBufferOffset = sbstate.indexBuffer->unmap(usedsizes[2]);
1161 cmd.texture = sbstate.texture;
1162 draw(cmd);
1163
1164 sbstate.indexBufferMap = StreamBuffer::MapInfo();
1165 }
1166 else
1167 {
1168 DrawCommand cmd(&attributes, &buffers);
1169 cmd.primitiveType = sbstate.primitiveMode;
1170 cmd.vertexStart = 0;
1171 cmd.vertexCount = sbstate.vertexCount;
1172 cmd.texture = sbstate.texture;
1173 draw(cmd);
1174 }
1175
1176 for (int i = 0; i < 2; i++)
1177 {
1178 if (usedsizes[i] > 0)
1179 sbstate.vb[i]->markUsed(usedsizes[i]);
1180 }
1181
1182 if (usedsizes[2] > 0)
1183 sbstate.indexBuffer->markUsed(usedsizes[2]);
1184
1185 popTransform();
1186
1187 if (attributes.isEnabled(ATTRIB_COLOR))
1188 setColor(nc);
1189
1190 streamBufferState.vertexCount = 0;
1191 streamBufferState.indexCount = 0;
1192}
1193
1194void Graphics::flushStreamDrawsGlobal()
1195{
1196 Graphics *instance = getInstance<Graphics>(M_GRAPHICS);
1197 if (instance != nullptr)
1198 instance->flushStreamDraws();
1199}
1200
1201/**
1202 * Drawing
1203 **/
1204
1205void Graphics::draw(Drawable *drawable, const Matrix4 &m)
1206{
1207 drawable->draw(this, m);
1208}
1209
1210void Graphics::draw(Texture *texture, Quad *quad, const Matrix4 &m)
1211{
1212 texture->draw(this, quad, m);
1213}
1214
1215void Graphics::drawLayer(Texture *texture, int layer, const Matrix4 &m)
1216{
1217 texture->drawLayer(this, layer, m);
1218}
1219
1220void Graphics::drawLayer(Texture *texture, int layer, Quad *quad, const Matrix4 &m)
1221{
1222 texture->drawLayer(this, layer, quad, m);
1223}
1224
1225void Graphics::drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount)
1226{
1227 mesh->drawInstanced(this, m, instancecount);
1228}
1229
1230void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4 &m)
1231{
1232 checkSetDefaultFont();
1233
1234 if (states.back().font.get() != nullptr)
1235 print(str, states.back().font.get(), m);
1236}
1237
1238void Graphics::print(const std::vector<Font::ColoredString> &str, Font *font, const Matrix4 &m)
1239{
1240 font->print(this, str, m, states.back().color);
1241}
1242
1243void Graphics::printf(const std::vector<Font::ColoredString> &str, float wrap, Font::AlignMode align, const Matrix4 &m)
1244{
1245 checkSetDefaultFont();
1246
1247 if (states.back().font.get() != nullptr)
1248 printf(str, states.back().font.get(), wrap, align, m);
1249}
1250
1251void Graphics::printf(const std::vector<Font::ColoredString> &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m)
1252{
1253 font->printf(this, str, wrap, align, m, states.back().color);
1254}
1255
1256/**
1257 * Primitives (points, shapes, lines).
1258 **/
1259
1260void Graphics::points(const Vector2 *positions, const Colorf *colors, size_t numpoints)
1261{
1262 const Matrix4 &t = getTransform();
1263 bool is2D = t.isAffine2DTransform();
1264
1265 StreamDrawCommand cmd;
1266 cmd.primitiveMode = PRIMITIVE_POINTS;
1267 cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
1268 cmd.formats[1] = vertex::CommonFormat::RGBAub;
1269 cmd.vertexCount = (int) numpoints;
1270
1271 StreamVertexData data = requestStreamDraw(cmd);
1272
1273 if (is2D)
1274 t.transformXY((Vector2 *) data.stream[0], positions, cmd.vertexCount);
1275 else
1276 t.transformXY0((Vector3 *) data.stream[0], positions, cmd.vertexCount);
1277
1278 Color32 *colordata = (Color32 *) data.stream[1];
1279
1280 if (colors)
1281 {
1282 Colorf nc = getColor();
1283 gammaCorrectColor(nc);
1284
1285 if (isGammaCorrect())
1286 {
1287 for (int i = 0; i < cmd.vertexCount; i++)
1288 {
1289 Colorf ci = colors[i];
1290 gammaCorrectColor(ci);
1291 ci *= nc;
1292 unGammaCorrectColor(ci);
1293 colordata[i] = toColor32(ci);
1294 }
1295 }
1296 else
1297 {
1298 for (int i = 0; i < cmd.vertexCount; i++)
1299 colordata[i] = toColor32(nc * colors[i]);
1300 }
1301 }
1302 else
1303 {
1304 Color32 c = toColor32(getColor());
1305
1306 for (int i = 0; i < cmd.vertexCount; i++)
1307 colordata[i] = c;
1308 }
1309}
1310
1311int Graphics::calculateEllipsePoints(float rx, float ry) const
1312{
1313 int points = (int) sqrtf(((rx + ry) / 2.0f) * 20.0f * (float) pixelScaleStack.back());
1314 return std::max(points, 8);
1315}
1316
1317void Graphics::polyline(const Vector2 *vertices, size_t count)
1318{
1319 float halfwidth = getLineWidth() * 0.5f;
1320 LineJoin linejoin = getLineJoin();
1321 LineStyle linestyle = getLineStyle();
1322
1323 float pixelsize = 1.0f / std::max((float) pixelScaleStack.back(), 0.000001f);
1324
1325 if (linejoin == LINE_JOIN_NONE)
1326 {
1327 NoneJoinPolyline line;
1328 line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
1329 line.draw(this);
1330 }
1331 else if (linejoin == LINE_JOIN_BEVEL)
1332 {
1333 BevelJoinPolyline line;
1334 line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
1335 line.draw(this);
1336 }
1337 else if (linejoin == LINE_JOIN_MITER)
1338 {
1339 MiterJoinPolyline line;
1340 line.render(vertices, count, halfwidth, pixelsize, linestyle == LINE_SMOOTH);
1341 line.draw(this);
1342 }
1343}
1344
1345void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h)
1346{
1347 Vector2 coords[] = {Vector2(x,y), Vector2(x,y+h), Vector2(x+w,y+h), Vector2(x+w,y), Vector2(x,y)};
1348 polygon(mode, coords, 5);
1349}
1350
1351void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points)
1352{
1353 if (rx <= 0 || ry <= 0)
1354 {
1355 rectangle(mode, x, y, w, h);
1356 return;
1357 }
1358
1359 // Radius values that are more than half the rectangle's size aren't handled
1360 // correctly (for now)...
1361 if (w >= 0.02f)
1362 rx = std::min(rx, w / 2.0f - 0.01f);
1363 if (h >= 0.02f)
1364 ry = std::min(ry, h / 2.0f - 0.01f);
1365
1366 points = std::max(points / 4, 1);
1367
1368 const float half_pi = static_cast<float>(LOVE_M_PI / 2);
1369 float angle_shift = half_pi / ((float) points + 1.0f);
1370
1371 int num_coords = (points + 2) * 4;
1372 Vector2 *coords = getScratchBuffer<Vector2>(num_coords + 1);
1373 float phi = .0f;
1374
1375 for (int i = 0; i <= points + 2; ++i, phi += angle_shift)
1376 {
1377 coords[i].x = x + rx * (1 - cosf(phi));
1378 coords[i].y = y + ry * (1 - sinf(phi));
1379 }
1380
1381 phi = half_pi;
1382
1383 for (int i = points + 2; i <= 2 * (points + 2); ++i, phi += angle_shift)
1384 {
1385 coords[i].x = x + w - rx * (1 + cosf(phi));
1386 coords[i].y = y + ry * (1 - sinf(phi));
1387 }
1388
1389 phi = 2 * half_pi;
1390
1391 for (int i = 2 * (points + 2); i <= 3 * (points + 2); ++i, phi += angle_shift)
1392 {
1393 coords[i].x = x + w - rx * (1 + cosf(phi));
1394 coords[i].y = y + h - ry * (1 + sinf(phi));
1395 }
1396
1397 phi = 3 * half_pi;
1398
1399 for (int i = 3 * (points + 2); i <= 4 * (points + 2); ++i, phi += angle_shift)
1400 {
1401 coords[i].x = x + rx * (1 - cosf(phi));
1402 coords[i].y = y + h - ry * (1 + sinf(phi));
1403 }
1404
1405 coords[num_coords] = coords[0];
1406
1407 polygon(mode, coords, num_coords + 1);
1408}
1409
1410void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry)
1411{
1412 int points = calculateEllipsePoints(std::min(rx, std::abs(w/2)), std::min(ry, std::abs(h/2)));
1413 rectangle(mode, x, y, w, h, rx, ry, points);
1414}
1415
1416void Graphics::circle(DrawMode mode, float x, float y, float radius, int points)
1417{
1418 ellipse(mode, x, y, radius, radius, points);
1419}
1420
1421void Graphics::circle(DrawMode mode, float x, float y, float radius)
1422{
1423 ellipse(mode, x, y, radius, radius);
1424}
1425
1426void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points)
1427{
1428 float two_pi = (float) (LOVE_M_PI * 2);
1429 if (points <= 0) points = 1;
1430 float angle_shift = (two_pi / points);
1431 float phi = .0f;
1432
1433 // 1 extra point at the end for a closed loop, and 1 extra point at the
1434 // start in filled mode for the vertex in the center of the ellipse.
1435 int extrapoints = 1 + (mode == DRAW_FILL ? 1 : 0);
1436
1437 Vector2 *polygoncoords = getScratchBuffer<Vector2>(points + extrapoints);
1438 Vector2 *coords = polygoncoords;
1439
1440 if (mode == DRAW_FILL)
1441 {
1442 coords[0].x = x;
1443 coords[0].y = y;
1444 coords++;
1445 }
1446
1447 for (int i = 0; i < points; ++i, phi += angle_shift)
1448 {
1449 coords[i].x = x + a * cosf(phi);
1450 coords[i].y = y + b * sinf(phi);
1451 }
1452
1453 coords[points] = coords[0];
1454
1455 // Last argument to polygon(): don't skip the last vertex in fill mode.
1456 polygon(mode, polygoncoords, points + extrapoints, false);
1457}
1458
1459void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b)
1460{
1461 ellipse(mode, x, y, a, b, calculateEllipsePoints(a, b));
1462}
1463
1464void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2, int points)
1465{
1466 // Nothing to display with no points or equal angles. (Or is there with line mode?)
1467 if (points <= 0 || angle1 == angle2)
1468 return;
1469
1470 // Oh, you want to draw a circle?
1471 if (fabs(angle1 - angle2) >= 2.0f * (float) LOVE_M_PI)
1472 {
1473 circle(drawmode, x, y, radius, points);
1474 return;
1475 }
1476
1477 float angle_shift = (angle2 - angle1) / points;
1478 // Bail on precision issues.
1479 if (angle_shift == 0.0)
1480 return;
1481
1482 // Prevent the connecting line from being drawn if a closed line arc has a
1483 // small angle. Avoids some visual issues when connected lines are at sharp
1484 // angles, due to the miter line join drawing code.
1485 if (drawmode == DRAW_LINE && arcmode == ARC_CLOSED && fabsf(angle1 - angle2) < LOVE_TORAD(4))
1486 arcmode = ARC_OPEN;
1487
1488 // Quick fix for the last part of a filled open arc not being drawn (because
1489 // polygon(DRAW_FILL, ...) doesn't work without a closed loop of vertices.)
1490 if (drawmode == DRAW_FILL && arcmode == ARC_OPEN)
1491 arcmode = ARC_CLOSED;
1492
1493 float phi = angle1;
1494
1495 Vector2 *coords = nullptr;
1496 int num_coords = 0;
1497
1498 const auto createPoints = [&](Vector2 *coordinates)
1499 {
1500 for (int i = 0; i <= points; ++i, phi += angle_shift)
1501 {
1502 coordinates[i].x = x + radius * cosf(phi);
1503 coordinates[i].y = y + radius * sinf(phi);
1504 }
1505 };
1506
1507 if (arcmode == ARC_PIE)
1508 {
1509 num_coords = points + 3;
1510 coords = getScratchBuffer<Vector2>(num_coords);
1511
1512 coords[0] = coords[num_coords - 1] = Vector2(x, y);
1513
1514 createPoints(coords + 1);
1515 }
1516 else if (arcmode == ARC_OPEN)
1517 {
1518 num_coords = points + 1;
1519 coords = getScratchBuffer<Vector2>(num_coords);
1520
1521 createPoints(coords);
1522 }
1523 else // ARC_CLOSED
1524 {
1525 num_coords = points + 2;
1526 coords = getScratchBuffer<Vector2>(num_coords);
1527
1528 createPoints(coords);
1529
1530 // Connect the ends of the arc.
1531 coords[num_coords - 1] = coords[0];
1532 }
1533
1534 polygon(drawmode, coords, num_coords);
1535}
1536
1537void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2)
1538{
1539 float points = (float) calculateEllipsePoints(radius, radius);
1540
1541 // The amount of points is based on the fraction of the circle created by the arc.
1542 float angle = fabsf(angle1 - angle2);
1543 if (angle < 2.0f * (float) LOVE_M_PI)
1544 points *= angle / (2.0f * (float) LOVE_M_PI);
1545
1546 arc(drawmode, arcmode, x, y, radius, angle1, angle2, (int) (points + 0.5f));
1547}
1548
1549void Graphics::polygon(DrawMode mode, const Vector2 *coords, size_t count, bool skipLastFilledVertex)
1550{
1551 // coords is an array of a closed loop of vertices, i.e.
1552 // coords[count-1] == coords[0]
1553 if (mode == DRAW_LINE)
1554 {
1555 polyline(coords, count);
1556 }
1557 else
1558 {
1559 const Matrix4 &t = getTransform();
1560 bool is2D = t.isAffine2DTransform();
1561
1562 StreamDrawCommand cmd;
1563 cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
1564 cmd.formats[1] = vertex::CommonFormat::RGBAub;
1565 cmd.indexMode = vertex::TriangleIndexMode::FAN;
1566 cmd.vertexCount = (int)count - (skipLastFilledVertex ? 1 : 0);
1567
1568 StreamVertexData data = requestStreamDraw(cmd);
1569
1570 if (is2D)
1571 t.transformXY((Vector2 *) data.stream[0], coords, cmd.vertexCount);
1572 else
1573 t.transformXY0((Vector3 *) data.stream[0], coords, cmd.vertexCount);
1574
1575 Color32 c = toColor32(getColor());
1576 Color32 *colordata = (Color32 *) data.stream[1];
1577 for (int i = 0; i < cmd.vertexCount; i++)
1578 colordata[i] = c;
1579 }
1580}
1581
1582const Graphics::Capabilities &Graphics::getCapabilities() const
1583{
1584 return capabilities;
1585}
1586
1587Graphics::Stats Graphics::getStats() const
1588{
1589 Stats stats;
1590
1591 getAPIStats(stats.shaderSwitches);
1592
1593 stats.drawCalls = drawCalls;
1594 if (streamBufferState.vertexCount > 0)
1595 stats.drawCalls++;
1596
1597 stats.canvasSwitches = canvasSwitchCount;
1598 stats.drawCallsBatched = drawCallsBatched;
1599 stats.canvases = Canvas::canvasCount;
1600 stats.images = Image::imageCount;
1601 stats.fonts = Font::fontCount;
1602 stats.textureMemory = Texture::totalGraphicsMemory;
1603
1604 return stats;
1605}
1606
1607size_t Graphics::getStackDepth() const
1608{
1609 return stackTypeStack.size();
1610}
1611
1612void Graphics::push(StackType type)
1613{
1614 if (stackTypeStack.size() == MAX_USER_STACK_DEPTH)
1615 throw Exception("Maximum stack depth reached (more pushes than pops?)");
1616
1617 pushTransform();
1618
1619 pixelScaleStack.push_back(pixelScaleStack.back());
1620
1621 if (type == STACK_ALL)
1622 states.push_back(states.back());
1623
1624 stackTypeStack.push_back(type);
1625}
1626
1627void Graphics::pop()
1628{
1629 if (stackTypeStack.size() < 1)
1630 throw Exception("Minimum stack depth reached (more pops than pushes?)");
1631
1632 popTransform();
1633 pixelScaleStack.pop_back();
1634
1635 if (stackTypeStack.back() == STACK_ALL)
1636 {
1637 DisplayState &newstate = states[states.size() - 2];
1638
1639 restoreStateChecked(newstate);
1640
1641 // The last two states in the stack should be equal now.
1642 states.pop_back();
1643 }
1644
1645 stackTypeStack.pop_back();
1646}
1647
1648/**
1649 * Transform and stack functions.
1650 **/
1651
1652const Matrix4 &Graphics::getTransform() const
1653{
1654 return transformStack.back();
1655}
1656
1657const Matrix4 &Graphics::getProjection() const
1658{
1659 return projectionMatrix;
1660}
1661
1662void Graphics::pushTransform()
1663{
1664 transformStack.push_back(transformStack.back());
1665}
1666
1667void Graphics::pushIdentityTransform()
1668{
1669 transformStack.push_back(Matrix4());
1670}
1671
1672void Graphics::popTransform()
1673{
1674 transformStack.pop_back();
1675}
1676
1677void Graphics::rotate(float r)
1678{
1679 transformStack.back().rotate(r);
1680}
1681
1682void Graphics::scale(float x, float y)
1683{
1684 transformStack.back().scale(x, y);
1685 pixelScaleStack.back() *= (fabs(x) + fabs(y)) / 2.0;
1686}
1687
1688void Graphics::translate(float x, float y)
1689{
1690 transformStack.back().translate(x, y);
1691}
1692
1693void Graphics::shear(float kx, float ky)
1694{
1695 transformStack.back().shear(kx, ky);
1696}
1697
1698void Graphics::origin()
1699{
1700 transformStack.back().setIdentity();
1701 pixelScaleStack.back() = 1;
1702}
1703
1704void Graphics::applyTransform(love::math::Transform *transform)
1705{
1706 Matrix4 &m = transformStack.back();
1707 m *= transform->getMatrix();
1708
1709 float sx, sy;
1710 m.getApproximateScale(sx, sy);
1711 pixelScaleStack.back() = (sx + sy) / 2.0;
1712}
1713
1714void Graphics::replaceTransform(love::math::Transform *transform)
1715{
1716 const Matrix4 &m = transform->getMatrix();
1717 transformStack.back() = m;
1718
1719 float sx, sy;
1720 m.getApproximateScale(sx, sy);
1721 pixelScaleStack.back() = (sx + sy) / 2.0;
1722}
1723
1724Vector2 Graphics::transformPoint(Vector2 point)
1725{
1726 Vector2 p;
1727 transformStack.back().transformXY(&p, &point, 1);
1728 return p;
1729}
1730
1731Vector2 Graphics::inverseTransformPoint(Vector2 point)
1732{
1733 Vector2 p;
1734 // TODO: We should probably cache the inverse transform so we don't have to
1735 // re-calculate it every time this is called.
1736 transformStack.back().inverse().transformXY(&p, &point, 1);
1737 return p;
1738}
1739
1740const Graphics::DefaultShaderCode &Graphics::getCurrentDefaultShaderCode() const
1741{
1742 int languageindex = (int) getShaderLanguageTarget();
1743 int gammaindex = isGammaCorrect() ? 1 : 0;
1744
1745 return defaultShaderCode[Shader::STANDARD_DEFAULT][languageindex][gammaindex];
1746}
1747
1748/**
1749 * Constants.
1750 **/
1751
1752bool Graphics::getConstant(const char *in, DrawMode &out)
1753{
1754 return drawModes.find(in, out);
1755}
1756
1757bool Graphics::getConstant(DrawMode in, const char *&out)
1758{
1759 return drawModes.find(in, out);
1760}
1761
1762std::vector<std::string> Graphics::getConstants(DrawMode)
1763{
1764 return drawModes.getNames();
1765}
1766
1767bool Graphics::getConstant(const char *in, ArcMode &out)
1768{
1769 return arcModes.find(in, out);
1770}
1771
1772bool Graphics::getConstant(ArcMode in, const char *&out)
1773{
1774 return arcModes.find(in, out);
1775}
1776
1777std::vector<std::string> Graphics::getConstants(ArcMode)
1778{
1779 return arcModes.getNames();
1780}
1781
1782bool Graphics::getConstant(const char *in, BlendMode &out)
1783{
1784 return blendModes.find(in, out);
1785}
1786
1787bool Graphics::getConstant(BlendMode in, const char *&out)
1788{
1789 return blendModes.find(in, out);
1790}
1791
1792std::vector<std::string> Graphics::getConstants(BlendMode)
1793{
1794 return blendModes.getNames();
1795}
1796
1797bool Graphics::getConstant(const char *in, BlendAlpha &out)
1798{
1799 return blendAlphaModes.find(in, out);
1800}
1801
1802bool Graphics::getConstant(BlendAlpha in, const char *&out)
1803{
1804 return blendAlphaModes.find(in, out);
1805}
1806
1807std::vector<std::string> Graphics::getConstants(BlendAlpha)
1808{
1809 return blendAlphaModes.getNames();
1810}
1811
1812bool Graphics::getConstant(const char *in, LineStyle &out)
1813{
1814 return lineStyles.find(in, out);
1815}
1816
1817bool Graphics::getConstant(LineStyle in, const char *&out)
1818{
1819 return lineStyles.find(in, out);
1820}
1821
1822std::vector<std::string> Graphics::getConstants(LineStyle)
1823{
1824 return lineStyles.getNames();
1825}
1826
1827bool Graphics::getConstant(const char *in, LineJoin &out)
1828{
1829 return lineJoins.find(in, out);
1830}
1831
1832bool Graphics::getConstant(LineJoin in, const char *&out)
1833{
1834 return lineJoins.find(in, out);
1835}
1836
1837std::vector<std::string> Graphics::getConstants(LineJoin)
1838{
1839 return lineJoins.getNames();
1840}
1841
1842bool Graphics::getConstant(const char *in, Feature &out)
1843{
1844 return features.find(in, out);
1845}
1846
1847bool Graphics::getConstant(Feature in, const char *&out)
1848{
1849 return features.find(in, out);
1850}
1851
1852bool Graphics::getConstant(const char *in, SystemLimit &out)
1853{
1854 return systemLimits.find(in, out);
1855}
1856
1857bool Graphics::getConstant(SystemLimit in, const char *&out)
1858{
1859 return systemLimits.find(in, out);
1860}
1861
1862bool Graphics::getConstant(const char *in, StackType &out)
1863{
1864 return stackTypes.find(in, out);
1865}
1866
1867bool Graphics::getConstant(StackType in, const char *&out)
1868{
1869 return stackTypes.find(in, out);
1870}
1871
1872std::vector<std::string> Graphics::getConstants(StackType)
1873{
1874 return stackTypes.getNames();
1875}
1876
1877StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM>::Entry Graphics::drawModeEntries[] =
1878{
1879 { "line", DRAW_LINE },
1880 { "fill", DRAW_FILL },
1881};
1882
1883StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM> Graphics::drawModes(Graphics::drawModeEntries, sizeof(Graphics::drawModeEntries));
1884
1885StringMap<Graphics::ArcMode, Graphics::ARC_MAX_ENUM>::Entry Graphics::arcModeEntries[] =
1886{
1887 { "open", ARC_OPEN },
1888 { "closed", ARC_CLOSED },
1889 { "pie", ARC_PIE },
1890};
1891
1892StringMap<Graphics::ArcMode, Graphics::ARC_MAX_ENUM> Graphics::arcModes(Graphics::arcModeEntries, sizeof(Graphics::arcModeEntries));
1893
1894StringMap<Graphics::BlendMode, Graphics::BLEND_MAX_ENUM>::Entry Graphics::blendModeEntries[] =
1895{
1896 { "alpha", BLEND_ALPHA },
1897 { "add", BLEND_ADD },
1898 { "subtract", BLEND_SUBTRACT },
1899 { "multiply", BLEND_MULTIPLY },
1900 { "lighten", BLEND_LIGHTEN },
1901 { "darken", BLEND_DARKEN },
1902 { "screen", BLEND_SCREEN },
1903 { "replace", BLEND_REPLACE },
1904 { "none", BLEND_NONE },
1905};
1906
1907StringMap<Graphics::BlendMode, Graphics::BLEND_MAX_ENUM> Graphics::blendModes(Graphics::blendModeEntries, sizeof(Graphics::blendModeEntries));
1908
1909StringMap<Graphics::BlendAlpha, Graphics::BLENDALPHA_MAX_ENUM>::Entry Graphics::blendAlphaEntries[] =
1910{
1911 { "alphamultiply", BLENDALPHA_MULTIPLY },
1912 { "premultiplied", BLENDALPHA_PREMULTIPLIED },
1913};
1914
1915StringMap<Graphics::BlendAlpha, Graphics::BLENDALPHA_MAX_ENUM> Graphics::blendAlphaModes(Graphics::blendAlphaEntries, sizeof(Graphics::blendAlphaEntries));
1916
1917StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM>::Entry Graphics::lineStyleEntries[] =
1918{
1919 { "smooth", LINE_SMOOTH },
1920 { "rough", LINE_ROUGH }
1921};
1922
1923StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM> Graphics::lineStyles(Graphics::lineStyleEntries, sizeof(Graphics::lineStyleEntries));
1924
1925StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM>::Entry Graphics::lineJoinEntries[] =
1926{
1927 { "none", LINE_JOIN_NONE },
1928 { "miter", LINE_JOIN_MITER },
1929 { "bevel", LINE_JOIN_BEVEL }
1930};
1931
1932StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(Graphics::lineJoinEntries, sizeof(Graphics::lineJoinEntries));
1933
1934StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featureEntries[] =
1935{
1936 { "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
1937 { "clampzero", FEATURE_CLAMP_ZERO },
1938 { "lighten", FEATURE_LIGHTEN },
1939 { "fullnpot", FEATURE_FULL_NPOT },
1940 { "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP },
1941 { "shaderderivatives", FEATURE_SHADER_DERIVATIVES },
1942 { "glsl3", FEATURE_GLSL3 },
1943 { "instancing", FEATURE_INSTANCING },
1944};
1945
1946StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));
1947
1948StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::systemLimitEntries[] =
1949{
1950 { "pointsize", LIMIT_POINT_SIZE },
1951 { "texturesize", LIMIT_TEXTURE_SIZE },
1952 { "texturelayers", LIMIT_TEXTURE_LAYERS },
1953 { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE },
1954 { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE },
1955 { "multicanvas", LIMIT_MULTI_CANVAS },
1956 { "canvasmsaa", LIMIT_CANVAS_MSAA },
1957 { "anisotropy", LIMIT_ANISOTROPY },
1958};
1959
1960StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
1961
1962StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM>::Entry Graphics::stackTypeEntries[] =
1963{
1964 { "all", STACK_ALL },
1965 { "transform", STACK_TRANSFORM },
1966};
1967
1968StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM> Graphics::stackTypes(Graphics::stackTypeEntries, sizeof(Graphics::stackTypeEntries));
1969
1970} // graphics
1971} // love
1972