123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 |
- /**
- * Copyright (c) 2006-2015 LOVE Development Team
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- **/
- #include "Canvas.h"
- #include "Image.h"
- #include "Graphics.h"
- #include "common/Matrix.h"
- #include <cstring> // For memcpy
- #include <algorithm> // For min/max
- namespace love
- {
- namespace graphics
- {
- namespace opengl
- {
- static GLenum createFBO(GLuint &framebuffer, GLuint texture)
- {
- // get currently bound fbo to reset to it later
- GLint current_fbo;
- glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
- glGenFramebuffers(1, &framebuffer);
- gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer);
- if (texture != 0)
- {
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
- // Initialize the texture to transparent black.
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- }
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- // unbind framebuffer
- gl.bindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo);
- return status;
- }
- static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer)
- {
- glGenRenderbuffers(1, &buffer);
- glBindRenderbuffer(GL_RENDERBUFFER, buffer);
- glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, iformat, width, height);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
- glBindRenderbuffer(GL_RENDERBUFFER, 0);
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (status == GL_FRAMEBUFFER_COMPLETE)
- {
- // Initialize the buffer to transparent black.
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- }
- else
- {
- glDeleteRenderbuffers(1, &buffer);
- buffer = 0;
- }
- return status;
- }
- Canvas *Canvas::current = nullptr;
- OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport();
- bool Canvas::screenHasSRGB = false;
- int Canvas::canvasCount = 0;
- Canvas::Canvas(int width, int height, Format format, int msaa)
- : fbo(0)
- , resolve_fbo(0)
- , texture(0)
- , msaa_buffer(0)
- , depth_stencil(0)
- , format(format)
- , requested_samples(msaa)
- , actual_samples(0)
- , texture_memory(0)
- {
- this->width = width;
- this->height = height;
- float w = static_cast<float>(width);
- float h = static_cast<float>(height);
- // Vertices are ordered for use with triangle strips:
- // 0----2
- // | / |
- // | / |
- // 1----3
- // world coordinates
- vertices[0].x = 0;
- vertices[0].y = 0;
- vertices[1].x = 0;
- vertices[1].y = h;
- vertices[2].x = w;
- vertices[2].y = 0;
- vertices[3].x = w;
- vertices[3].y = h;
- // texture coordinates
- vertices[0].s = 0;
- vertices[0].t = 0;
- vertices[1].s = 0;
- vertices[1].t = 1;
- vertices[2].s = 1;
- vertices[2].t = 0;
- vertices[3].s = 1;
- vertices[3].t = 1;
- loadVolatile();
- ++canvasCount;
- }
- Canvas::~Canvas()
- {
- --canvasCount;
- // reset framebuffer if still using this one
- if (current == this)
- stopGrab();
- unloadVolatile();
- }
- bool Canvas::createMSAAFBO(GLenum internalformat)
- {
- actual_samples = requested_samples;
- if (actual_samples <= 1)
- {
- actual_samples = 0;
- return false;
- }
- // Create our FBO without a texture.
- status = createFBO(fbo, 0);
- GLuint previous = gl.getDefaultFBO();
- if (current != this)
- {
- if (current != nullptr)
- previous = current->fbo;
- gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
- }
- // Create and attach the MSAA buffer for our FBO.
- status = createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer);
- // Create the FBO used for the MSAA resolve, and attach the texture.
- if (status == GL_FRAMEBUFFER_COMPLETE)
- status = createFBO(resolve_fbo, texture);
- if (status != GL_FRAMEBUFFER_COMPLETE)
- {
- // Clean up.
- glDeleteFramebuffers(1, &fbo);
- glDeleteFramebuffers(1, &resolve_fbo);
- glDeleteRenderbuffers(1, &msaa_buffer);
- fbo = msaa_buffer = resolve_fbo = 0;
- actual_samples = 0;
- }
- if (current != this)
- gl.bindFramebuffer(GL_FRAMEBUFFER, previous);
- return status == GL_FRAMEBUFFER_COMPLETE;
- }
- bool Canvas::loadVolatile()
- {
- fbo = depth_stencil = texture = 0;
- resolve_fbo = msaa_buffer = 0;
- status = GL_FRAMEBUFFER_COMPLETE;
- // glTexImage2D is guaranteed to error in this case.
- if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
- {
- status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
- return false;
- }
- // getMaxRenderbufferSamples will be 0 on systems that don't support
- // multisampled renderbuffers / don't export FBO multisample extensions.
- requested_samples = std::min(requested_samples, gl.getMaxRenderbufferSamples());
- requested_samples = std::max(requested_samples, 0);
- glGenTextures(1, &texture);
- gl.bindTexture(texture);
- if (GLAD_ANGLE_texture_usage)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
- setFilter(filter);
- setWrap(wrap);
- GLenum internalformat = GL_RGBA;
- GLenum externalformat = GL_RGBA;
- GLenum textype = GL_UNSIGNED_BYTE;
- convertFormat(format, internalformat, externalformat, textype);
- // in GLES2, the internalformat and format params of TexImage have to match.
- GLint iformat = (GLint) internalformat;
- if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
- iformat = (GLint) externalformat;
- while (glGetError() != GL_NO_ERROR)
- /* Clear the error buffer. */;
- glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0,
- externalformat, textype, nullptr);
- if (glGetError() != GL_NO_ERROR)
- {
- gl.deleteTexture(texture);
- texture = 0;
- status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
- return false;
- }
- // Try to create a MSAA FBO if requested. On failure (or no requested MSAA),
- // fall back to a regular FBO.
- if (!createMSAAFBO(internalformat))
- status = createFBO(fbo, texture);
- if (status != GL_FRAMEBUFFER_COMPLETE)
- {
- if (fbo != 0)
- {
- glDeleteFramebuffers(1, &fbo);
- fbo = 0;
- }
- return false;
- }
- size_t prevmemsize = texture_memory;
- texture_memory = (getFormatBitsPerPixel(format) * width * height) / 8;
- if (msaa_buffer != 0)
- texture_memory += (texture_memory * actual_samples);
- gl.updateTextureMemorySize(prevmemsize, texture_memory);
- return true;
- }
- void Canvas::unloadVolatile()
- {
- glDeleteFramebuffers(1, &fbo);
- glDeleteFramebuffers(1, &resolve_fbo);
- glDeleteRenderbuffers(1, &depth_stencil);
- glDeleteRenderbuffers(1, &msaa_buffer);
- gl.deleteTexture(texture);
- fbo = 0;
- resolve_fbo = 0;
- depth_stencil = 0;
- msaa_buffer = 0;
- texture = 0;
- attachedCanvases.clear();
- gl.updateTextureMemorySize(texture_memory, 0);
- texture_memory = 0;
- }
- void Canvas::drawv(const Matrix &t, const Vertex *v)
- {
- OpenGL::TempTransform transform(gl);
- transform.get() *= t;
- gl.bindTexture(texture);
- glEnableVertexAttribArray(ATTRIB_POS);
- glEnableVertexAttribArray(ATTRIB_TEXCOORD);
- glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].x);
- glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].s);
- gl.prepareDraw();
- gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
- glDisableVertexAttribArray(ATTRIB_TEXCOORD);
- glDisableVertexAttribArray(ATTRIB_POS);
- }
- void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
- {
- static Matrix t;
- t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
- drawv(t, vertices);
- }
- void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
- {
- static Matrix t;
- t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
- const Vertex *v = quad->getVertices();
- drawv(t, v);
- }
- void Canvas::setFilter(const Texture::Filter &f)
- {
- if (!validateFilter(f, false))
- throw love::Exception("Invalid texture filter.");
- filter = f;
- gl.bindTexture(texture);
- gl.setTextureFilter(filter);
- }
- bool Canvas::setWrap(const Texture::Wrap &w)
- {
- bool success = true;
- wrap = w;
- if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
- && (width != next_p2(width) || height != next_p2(height)))
- {
- if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
- success = false;
- // If we only have limited NPOT support then the wrap mode must be CLAMP.
- wrap.s = wrap.t = WRAP_CLAMP;
- }
- gl.bindTexture(texture);
- gl.setTextureWrap(wrap);
- return success;
- }
- const void *Canvas::getHandle() const
- {
- return &texture;
- }
- void Canvas::setupGrab()
- {
- // already grabbing
- if (current == this)
- return;
- // cleanup after previous Canvas
- if (current != nullptr)
- {
- systemViewport = current->systemViewport;
- current->stopGrab(true);
- }
- else
- systemViewport = gl.getViewport();
- // indicate we are using this Canvas.
- current = this;
- // bind the framebuffer object.
- gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
- gl.setViewport({0, 0, width, height});
- // Set up the projection matrix
- gl.matrices.projection.push_back(Matrix::ortho(0.0, width, 0.0, height));
- // Make sure the correct sRGB setting is used when drawing to the canvas.
- if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
- {
- if (format == FORMAT_SRGB)
- glEnable(GL_FRAMEBUFFER_SRGB);
- else if (screenHasSRGB)
- glDisable(GL_FRAMEBUFFER_SRGB);
- }
- }
- void Canvas::startGrab(const std::vector<Canvas *> &canvases)
- {
- // Whether the new canvas list is different from the old one.
- // A more thorough check is done below.
- bool canvaseschanged = canvases.size() != attachedCanvases.size();
- if (canvases.size() > 0)
- {
- if (!isMultiCanvasSupported())
- throw love::Exception("Multi-canvas rendering is not supported on this system.");
- if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
- throw love::Exception("This system can't simultaneously render to %d canvases.", canvases.size()+1);
- if (actual_samples != 0)
- throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
- }
- bool multiformatsupported = isMultiFormatMultiCanvasSupported();
- for (size_t i = 0; i < canvases.size(); i++)
- {
- if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
- throw love::Exception("All canvases must have the same dimensions.");
- if (canvases[i]->getTextureFormat() != format && !multiformatsupported)
- throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
- if (canvases[i]->getMSAA() != 0)
- throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
- if (!canvaseschanged && canvases[i] != attachedCanvases[i])
- canvaseschanged = true;
- }
- setupGrab();
- // Don't attach anything if there's nothing to change.
- if (!canvaseschanged)
- return;
- // Attach the canvas textures to the active FBO and set up MRTs.
- std::vector<GLenum> drawbuffers;
- drawbuffers.reserve(canvases.size() + 1);
- drawbuffers.push_back(GL_COLOR_ATTACHMENT0);
- // Attach the canvas textures to the currently bound framebuffer.
- for (int i = 0; i < (int) canvases.size(); i++)
- {
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 + i,
- GL_TEXTURE_2D, *(GLuint *) canvases[i]->getHandle(), 0);
- drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i);
- }
- // set up multiple render targets
- glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]);
- // We want to avoid reference cycles, so we don't retain the attached
- // Canvases here. The code in Graphics::setCanvas retains them.
- attachedCanvases = canvases;
- }
- void Canvas::startGrab()
- {
- setupGrab();
- if (attachedCanvases.size() == 0)
- return;
- // make sure the FBO is only using a single draw buffer
- glDrawBuffer(GL_COLOR_ATTACHMENT0);
- attachedCanvases.clear();
- }
- void Canvas::stopGrab(bool switchingToOtherCanvas)
- {
- // i am not grabbing. leave me alone
- if (current != this)
- return;
- // Make sure the canvas texture is up to date if we're using MSAA.
- resolveMSAA(false);
- gl.matrices.projection.pop_back();
- if (switchingToOtherCanvas)
- {
- if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
- {
- if (format == FORMAT_SRGB)
- glDisable(GL_FRAMEBUFFER_SRGB);
- }
- }
- else
- {
- // bind system framebuffer.
- gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO());
- current = nullptr;
- gl.setViewport(systemViewport);
- if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
- {
- if (format == FORMAT_SRGB && !screenHasSRGB)
- glDisable(GL_FRAMEBUFFER_SRGB);
- else if (format != FORMAT_SRGB && screenHasSRGB)
- glEnable(GL_FRAMEBUFFER_SRGB);
- }
- }
- }
- bool Canvas::checkCreateStencil()
- {
- // Do nothing if we've already created the stencil buffer.
- if (depth_stencil != 0)
- return true;
- if (current != this)
- gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
- GLenum format = GL_STENCIL_INDEX8;
- GLenum attachment = GL_STENCIL_ATTACHMENT;
- // Prefer a combined depth/stencil buffer.
- if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object
- || GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
- {
- format = GL_DEPTH_STENCIL;
- attachment = GL_DEPTH_STENCIL_ATTACHMENT;
- }
- glGenRenderbuffers(1, &depth_stencil);
- glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil);
- if (requested_samples > 1)
- glRenderbufferStorageMultisample(GL_RENDERBUFFER, requested_samples, format, width, height);
- else
- glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
- // Attach the stencil buffer to the framebuffer object.
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, depth_stencil);
- glBindRenderbuffer(GL_RENDERBUFFER, 0);
- bool success = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
- // We don't want the stencil buffer filled with garbage.
- if (success)
- glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- else
- {
- glDeleteRenderbuffers(1, &depth_stencil);
- depth_stencil = 0;
- }
- if (current && current != this)
- gl.bindFramebuffer(GL_FRAMEBUFFER, current->fbo);
- else if (!current)
- gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO());
- return success;
- }
- love::image::ImageData *Canvas::newImageData(love::image::Image *image, int x, int y, int w, int h)
- {
- if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > width || (y + h) > height)
- throw love::Exception("Invalid ImageData rectangle dimensions.");
- int row = 4 * w;
- int size = row * h;
- GLubyte *pixels = nullptr;
- try
- {
- pixels = new GLubyte[size];
- }
- catch (std::bad_alloc &)
- {
- throw love::Exception("Out of memory.");
- }
- // Make sure the canvas texture is up to date if we're using MSAA.
- if (current == this)
- resolveMSAA(false);
- // Our texture is attached to 'resolve_fbo' when we use MSAA.
- if (resolve_fbo != 0)
- gl.bindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
- else
- gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
- glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
- GLuint prevfbo = current ? current->fbo : gl.getDefaultFBO();
- gl.bindFramebuffer(GL_FRAMEBUFFER, prevfbo);
- // The new ImageData now owns the pixel data, so we don't delete it here.
- return image->newImageData(w, h, pixels, true);
- }
- bool Canvas::resolveMSAA(bool restoreprev)
- {
- if (resolve_fbo == 0 || msaa_buffer == 0)
- return false;
- GLint w = width;
- GLint h = height;
- // Do the MSAA resolve by blitting the MSAA renderbuffer to the texture.
- // For many of the MSAA extensions that add suffixes to the functions, we
- // assign function pointers in OpenGL.cpp so we can call the core functions.
- gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
- gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
- if (GLAD_APPLE_framebuffer_multisample)
- glResolveMultisampleFramebufferAPPLE();
- else
- glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
- if (restoreprev)
- {
- GLuint fbo = current ? current->fbo : gl.getDefaultFBO();
- gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
- }
- return true;
- }
- Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
- {
- switch (format)
- {
- case FORMAT_NORMAL:
- // 32-bit render targets don't have guaranteed support on OpenGL ES 2.
- if (GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8))
- return FORMAT_RGBA4;
- else
- return FORMAT_RGBA8;
- case FORMAT_HDR:
- return FORMAT_RGBA16F;
- default:
- return format;
- }
- }
- void Canvas::convertFormat(Canvas::Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type)
- {
- format = getSizedFormat(format);
- externalformat = GL_RGBA;
- switch (format)
- {
- case FORMAT_RGBA4:
- internalformat = GL_RGBA4;
- type = GL_UNSIGNED_SHORT_4_4_4_4;
- break;
- case FORMAT_RGB5A1:
- internalformat = GL_RGB5_A1;
- type = GL_UNSIGNED_SHORT_5_5_5_1;
- break;
- case FORMAT_RGB565:
- internalformat = GL_RGB565;
- externalformat = GL_RGB;
- type = GL_UNSIGNED_SHORT_5_6_5;
- break;
- case FORMAT_R8:
- internalformat = GL_R8;
- externalformat = GL_RED;
- type = GL_UNSIGNED_BYTE;
- break;
- case FORMAT_RG8:
- internalformat = GL_RG8;
- externalformat = GL_RG;
- type = GL_UNSIGNED_BYTE;
- break;
- case FORMAT_RGBA8:
- default:
- internalformat = GL_RGBA8;
- type = GL_UNSIGNED_BYTE;
- break;
- case FORMAT_RGB10A2:
- internalformat = GL_RGB10_A2;
- type = GL_UNSIGNED_INT_2_10_10_10_REV;
- break;
- case FORMAT_RG11B10F:
- internalformat = GL_R11F_G11F_B10F;
- externalformat = GL_RGB;
- type = GL_UNSIGNED_INT_10F_11F_11F_REV;
- break;
- case FORMAT_R16F:
- internalformat = GL_R16F;
- externalformat = GL_RED;
- if (GLAD_OES_texture_half_float)
- type = GL_HALF_FLOAT_OES;
- else if (GLAD_VERSION_1_0)
- type = GL_FLOAT;
- else
- type = GL_HALF_FLOAT;
- break;
- case FORMAT_RG16F:
- internalformat = GL_RG16F;
- externalformat = GL_RG;
- if (GLAD_OES_texture_half_float)
- type = GL_HALF_FLOAT_OES;
- else if (GLAD_VERSION_1_0)
- type = GL_FLOAT;
- else
- type = GL_HALF_FLOAT;
- break;
- case FORMAT_RGBA16F:
- internalformat = GL_RGBA16F;
- if (GLAD_OES_texture_half_float)
- type = GL_HALF_FLOAT_OES;
- else if (GLAD_VERSION_1_0)
- type = GL_FLOAT;
- else
- type = GL_HALF_FLOAT;
- break;
- case FORMAT_R32F:
- internalformat = GL_R32F;
- externalformat = GL_RED;
- type = GL_FLOAT;
- break;
- case FORMAT_RG32F:
- internalformat = GL_RG32F;
- externalformat = GL_RG;
- type = GL_FLOAT;
- break;
- case FORMAT_RGBA32F:
- internalformat = GL_RGBA32F;
- type = GL_FLOAT;
- break;
- case FORMAT_SRGB:
- internalformat = GL_SRGB8_ALPHA8;
- type = GL_UNSIGNED_BYTE;
- if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
- externalformat = GL_SRGB_ALPHA;
- break;
- }
- }
- size_t Canvas::getFormatBitsPerPixel(Format format)
- {
- switch (getSizedFormat(format))
- {
- case FORMAT_R8:
- return 8;
- case FORMAT_RGBA4:
- case FORMAT_RGB5A1:
- case FORMAT_RGB565:
- case FORMAT_RG8:
- case FORMAT_R16F:
- return 16;
- case FORMAT_RGBA8:
- case FORMAT_RGB10A2:
- case FORMAT_RG11B10F:
- case FORMAT_RG16F:
- case FORMAT_R32F:
- case FORMAT_SRGB:
- default:
- return 32;
- case FORMAT_RGBA16F:
- case FORMAT_RG32F:
- return 64;
- case FORMAT_RGBA32F:
- return 128;
- }
- }
- bool Canvas::isSupported()
- {
- return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object;
- }
- bool Canvas::isMultiCanvasSupported()
- {
- // system must support at least 4 simultaneous active canvases.
- return gl.getMaxRenderTargets() >= 4;
- }
- bool Canvas::isMultiFormatMultiCanvasSupported()
- {
- return isMultiCanvasSupported() && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
- }
- bool Canvas::supportedFormats[] = {false};
- bool Canvas::checkedFormats[] = {false};
- bool Canvas::isFormatSupported(Canvas::Format format)
- {
- if (!isSupported())
- return false;
- bool supported = true;
- format = getSizedFormat(format);
- switch (format)
- {
- case FORMAT_RGBA4:
- case FORMAT_RGB5A1:
- supported = true;
- break;
- case FORMAT_RGB565:
- supported = GLAD_ES_VERSION_2_0 || GLAD_VERSION_4_2 || GLAD_ARB_ES2_compatibility;
- break;
- case FORMAT_R8:
- case FORMAT_RG8:
- if (GLAD_VERSION_1_0)
- supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_rg;
- else if (GLAD_ES_VERSION_2_0)
- supported = GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg;
- break;
- case FORMAT_RGBA8:
- supported = GLAD_VERSION_1_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8;
- break;
- case FORMAT_RGB10A2:
- supported = GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0;
- break;
- case FORMAT_RG11B10F:
- supported = GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_color_buffer_packed_float;
- break;
- case FORMAT_R16F:
- case FORMAT_RG16F:
- if (GLAD_VERSION_1_0)
- supported = GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_texture_rg);
- else
- supported = GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || (GLAD_OES_texture_half_float && GLAD_EXT_texture_rg));
- break;
- case FORMAT_RGBA16F:
- if (GLAD_VERSION_1_0)
- supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float;
- else if (GLAD_ES_VERSION_2_0)
- supported = GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float);
- break;
- case FORMAT_R32F:
- case FORMAT_RG32F:
- supported = GLAD_VERSION_3_0 || (GLAD_ARB_texture_float && GLAD_ARB_texture_rg);
- break;
- case FORMAT_RGBA32F:
- supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float;
- break;
- case FORMAT_SRGB:
- if (GLAD_VERSION_1_0)
- {
- supported = GLAD_VERSION_3_0 || ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
- && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB));
- }
- else
- supported = GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB;
- break;
- default:
- supported = false;
- break;
- }
- if (!supported)
- return false;
- if (checkedFormats[format])
- return supportedFormats[format];
- // Even though we might have the necessary OpenGL version or extension,
- // drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
- // a texture to a FBO whose format the driver doesn't like. So we should
- // test with an actual FBO.
- GLenum internalformat = GL_RGBA;
- GLenum externalformat = GL_RGBA;
- GLenum textype = GL_UNSIGNED_BYTE;
- convertFormat(format, internalformat, externalformat, textype);
- // in GLES2, the internalformat and format params of TexImage have to match.
- if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
- internalformat = externalformat;
- GLuint texture = 0;
- glGenTextures(1, &texture);
- gl.bindTexture(texture);
- Texture::Filter f;
- f.min = f.mag = Texture::FILTER_NEAREST;
- gl.setTextureFilter(f);
- Texture::Wrap w;
- gl.setTextureWrap(w);
- glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr);
- GLuint fbo = 0;
- supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
- glDeleteFramebuffers(1, &fbo);
- gl.deleteTexture(texture);
- // Cache the result so we don't do this for every isFormatSupported call.
- checkedFormats[format] = true;
- supportedFormats[format] = supported;
- return supported;
- }
- bool Canvas::getConstant(const char *in, Format &out)
- {
- return formats.find(in, out);
- }
- bool Canvas::getConstant(Format in, const char *&out)
- {
- return formats.find(in, out);
- }
- StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM>::Entry Canvas::formatEntries[] =
- {
- {"normal", FORMAT_NORMAL},
- {"hdr", FORMAT_HDR},
- {"rgba4", FORMAT_RGBA4},
- {"rgb5a1", FORMAT_RGB5A1},
- {"rgb565", FORMAT_RGB565},
- {"r8", FORMAT_R8},
- {"rg8", FORMAT_RG8},
- {"rgba8", FORMAT_RGBA8},
- {"rgb10a2", FORMAT_RGB10A2},
- {"rg11b10f", FORMAT_RG11B10F},
- {"r16f", FORMAT_R16F},
- {"rg16f", FORMAT_RG16F},
- {"rgba16f", FORMAT_RGBA16F},
- {"r32f", FORMAT_R32F},
- {"rg32f", FORMAT_RG32F},
- {"rgba32f", FORMAT_RGBA32F},
- {"srgb", FORMAT_SRGB},
- };
- StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM> Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries));
- } // opengl
- } // graphics
- } // love
|