From 6c447ab4f60e605d4a54e40defee58f5e7d8d24e Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sat, 1 Feb 2020 23:54:51 -0400 Subject: [PATCH] Clean up some common Image and Canvas code. --- src/modules/graphics/Canvas.cpp | 2 +- src/modules/graphics/Graphics.cpp | 2 +- src/modules/graphics/Graphics.h | 12 +- src/modules/graphics/Image.cpp | 2 +- src/modules/graphics/opengl/Canvas.cpp | 146 +---------------------- src/modules/graphics/opengl/Canvas.h | 27 ----- src/modules/graphics/opengl/Graphics.cpp | 126 +++++++++++++++++-- src/modules/graphics/opengl/Graphics.h | 8 +- src/modules/graphics/opengl/Image.cpp | 5 - src/modules/graphics/opengl/Image.h | 2 - src/modules/graphics/wrap_Graphics.cpp | 9 +- 11 files changed, 140 insertions(+), 201 deletions(-) diff --git a/src/modules/graphics/Canvas.cpp b/src/modules/graphics/Canvas.cpp index ce384450c..aeca38809 100644 --- a/src/modules/graphics/Canvas.cpp +++ b/src/modules/graphics/Canvas.cpp @@ -72,7 +72,7 @@ Canvas::Canvas(const Settings &settings) auto gfx = Module::getInstance(Module::M_GRAPHICS); const Graphics::Capabilities &caps = gfx->getCapabilities(); - if (!gfx->isCanvasFormatSupported(format, readable)) + if (!gfx->isPixelFormatSupported(format, true, readable, false)) { const char *fstr = "rgba8"; const char *readablestr = ""; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 835344e29..d3863cf33 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -694,7 +694,7 @@ void Graphics::setCanvas(const RenderTargets &rts) PixelFormat dsformat = PIXELFORMAT_STENCIL8; if (wantsdepth && wantsstencil) dsformat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8; - else if (wantsdepth && isCanvasFormatSupported(PIXELFORMAT_DEPTH24_UNORM, false)) + else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, true, false, false)) dsformat = PIXELFORMAT_DEPTH24_UNORM; else if (wantsdepth) dsformat = PIXELFORMAT_DEPTH16_UNORM; diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index e8f146cf1..4a035cb8d 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -785,12 +785,14 @@ public: const Capabilities &getCapabilities() const; /** - * Gets whether the specified pixel format is supported by Canvases or - * Images. + * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format. **/ - virtual bool isCanvasFormatSupported(PixelFormat format) const = 0; - virtual bool isCanvasFormatSupported(PixelFormat format, bool readable) const = 0; - virtual bool isImageFormatSupported(PixelFormat format, bool sRGB = false) const = 0; + virtual PixelFormat getSizedFormat(PixelFormat format) const = 0; + + /** + * Gets whether the specified pixel format is supported. + **/ + virtual bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) = 0; /** * Gets the renderer used by love.graphics. diff --git a/src/modules/graphics/Image.cpp b/src/modules/graphics/Image.cpp index 2f155832a..46cac2e5a 100644 --- a/src/modules/graphics/Image.cpp +++ b/src/modules/graphics/Image.cpp @@ -79,7 +79,7 @@ Image::~Image() void Image::init(PixelFormat fmt, int w, int h, const Settings &settings) { Graphics *gfx = Module::getInstance(Module::M_GRAPHICS); - if (gfx != nullptr && !gfx->isImageFormatSupported(fmt, sRGB)) + if (gfx != nullptr && !gfx->isPixelFormatSupported(fmt, false, true, sRGB)) { const char *str; if (love::getConstant(fmt, str)) diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 8a91ab577..029513261 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -196,7 +196,9 @@ Canvas::Canvas(const Settings &settings) , renderbuffer(0) , actualSamples(0) { - format = getSizedFormat(format); + auto gfx = Module::getInstance(Module::M_GRAPHICS); + if (gfx != nullptr) + format = gfx->getSizedFormat(format); initQuad(); loadVolatile(); @@ -474,153 +476,11 @@ void Canvas::generateMipmaps() glGenerateMipmap(gltextype); } -PixelFormat Canvas::getSizedFormat(PixelFormat format) -{ - switch (format) - { - case PIXELFORMAT_NORMAL: - if (isGammaCorrect()) - return PIXELFORMAT_sRGBA8_UNORM; - else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, true, true, false)) - // 32-bit render targets don't have guaranteed support on GLES2. - return PIXELFORMAT_RGBA4_UNORM; - else - return PIXELFORMAT_RGBA8_UNORM; - case PIXELFORMAT_HDR: - return PIXELFORMAT_RGBA16_FLOAT; - default: - return format; - } -} - -bool Canvas::isSupported() -{ - return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object; -} - bool Canvas::isMultiFormatMultiCanvasSupported() { return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); } -Canvas::SupportedFormat Canvas::supportedFormats[] = {}; -Canvas::SupportedFormat Canvas::checkedFormats[] = {}; - -bool Canvas::isFormatSupported(PixelFormat format) -{ - return isFormatSupported(format, !isPixelFormatDepthStencil(format)); -} - -bool Canvas::isFormatSupported(PixelFormat format, bool readable) -{ - if (!isSupported()) - return false; - - const char *fstr = "?"; - love::getConstant(format, fstr); - - bool supported = true; - format = getSizedFormat(format); - - if (!OpenGL::isPixelFormatSupported(format, true, readable, false)) - return false; - - if (checkedFormats[format].get(readable)) - return supportedFormats[format].get(readable); - - // 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. - GLuint texture = 0; - GLuint renderbuffer = 0; - - // Avoid the test for depth/stencil formats - not every GL version - // guarantees support for depth/stencil-only render targets (which we would - // need for the test below to work), and we already do some finagling in - // convertPixelFormat to try to use the best-supported internal - // depth/stencil format for a particular driver. - if (isPixelFormatDepthStencil(format)) - { - checkedFormats[format].set(readable, true); - supportedFormats[format].set(readable, true); - return true; - } - - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, unusedSRGB); - - GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); - - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); - - // Make sure at least something is bound to a color attachment. I believe - // this is required on ES2 but I'm not positive. - if (isPixelFormatDepthStencil(format)) - gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0); - - if (readable) - { - glGenTextures(1, &texture); - gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); - - Texture::Filter f; - f.min = f.mag = Texture::FILTER_NEAREST; - gl.setTextureFilter(TEXTURE_2D, f); - - Texture::Wrap w; - gl.setTextureWrap(TEXTURE_2D, w); - - unusedSRGB = false; - gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 1, 1); - } - else - { - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); - } - - for (GLenum attachment : fmt.framebufferAttachments) - { - if (attachment == GL_NONE) - continue; - - if (readable) - gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); - else - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); - } - - supported = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - gl.deleteFramebuffer(fbo); - - if (texture != 0) - gl.deleteTexture(texture); - - if (renderbuffer != 0) - glDeleteRenderbuffers(1, &renderbuffer); - - // Cache the result so we don't do this for every isFormatSupported call. - checkedFormats[format].set(readable, true); - supportedFormats[format].set(readable, supported); - - return supported; -} - -void Canvas::resetFormatSupport() -{ - for (int i = 0; i < (int)PIXELFORMAT_MAX_ENUM; i++) - { - checkedFormats[i].readable = false; - checkedFormats[i].nonreadable = false; - } -} - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 317d9eeea..8543c3503 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -71,34 +71,10 @@ public: return fbo; } - static PixelFormat getSizedFormat(PixelFormat format); - static bool isSupported(); static bool isMultiFormatMultiCanvasSupported(); - static bool isFormatSupported(PixelFormat format, bool readable); - static bool isFormatSupported(PixelFormat format); - static void resetFormatSupport(); private: - struct SupportedFormat - { - bool readable = false; - bool nonreadable = false; - - bool get(bool getreadable) - { - return getreadable ? readable : nonreadable; - } - - void set(bool setreadable, bool val) - { - if (setreadable) - readable = val; - else - nonreadable = val; - } - }; - GLuint fbo; GLuint texture; @@ -108,9 +84,6 @@ private: int actualSamples; - static SupportedFormat supportedFormats[PIXELFORMAT_MAX_ENUM]; - static SupportedFormat checkedFormats[PIXELFORMAT_MAX_ENUM]; - }; // Canvas } // opengl diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index d636ed1eb..b443718fa 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -91,9 +91,9 @@ static GLenum getGLBlendFactor(BlendFactor factor) Graphics::Graphics() : windowHasStencil(false) , mainVAO(0) + , supportedFormats() { gl = OpenGL(); - Canvas::resetFormatSupport(); auto window = getInstance(M_WINDOW); @@ -1388,19 +1388,127 @@ void Graphics::initCapabilities() capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i); } -bool Graphics::isCanvasFormatSupported(PixelFormat format) const +PixelFormat Graphics::getSizedFormat(PixelFormat format) const { - return Canvas::isFormatSupported(format); + switch (format) + { + case PIXELFORMAT_NORMAL: + if (isGammaCorrect()) + return PIXELFORMAT_sRGBA8_UNORM; + else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, true, true, false)) + // 32-bit render targets don't have guaranteed support on GLES2. + return PIXELFORMAT_RGBA4_UNORM; + else + return PIXELFORMAT_RGBA8_UNORM; + case PIXELFORMAT_HDR: + return PIXELFORMAT_RGBA16_FLOAT; + default: + return format; + } } -bool Graphics::isCanvasFormatSupported(PixelFormat format, bool readable) const +bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB) { - return Canvas::isFormatSupported(format, readable); -} + format = getSizedFormat(format); -bool Graphics::isImageFormatSupported(PixelFormat format, bool sRGB) const -{ - return Image::isFormatSupported(format, sRGB); + if (sRGB && format == PIXELFORMAT_RGBA8_UNORM) + { + format = PIXELFORMAT_sRGBA8_UNORM; + sRGB = false; + } + + OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0]; + + if (supported.hasValue) + return supported.value; + + if (!OpenGL::isPixelFormatSupported(format, rendertarget, readable, sRGB)) + { + supported.set(false); + return supported.value; + } + + if (!rendertarget) + { + supported.set(true); + return supported.value; + } + + // 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. + GLuint texture = 0; + GLuint renderbuffer = 0; + + // Avoid the test for depth/stencil formats - not every GL version + // guarantees support for depth/stencil-only render targets (which we would + // need for the test below to work), and we already do some finagling in + // convertPixelFormat to try to use the best-supported internal + // depth/stencil format for a particular driver. + if (isPixelFormatDepthStencil(format)) + { + supported.set(true); + return true; + } + + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, readable, sRGB); + + GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + // Make sure at least something is bound to a color attachment. I believe + // this is required on ES2 but I'm not positive. + if (isPixelFormatDepthStencil(format)) + gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D), 0, 0, 0); + + if (readable) + { + glGenTextures(1, &texture); + gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); + + Texture::Filter f; + f.min = f.mag = Texture::FILTER_NEAREST; + gl.setTextureFilter(TEXTURE_2D, f); + + Texture::Wrap w; + gl.setTextureWrap(TEXTURE_2D, w); + + gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1); + } + else + { + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, 1, 1); + } + + for (GLenum attachment : fmt.framebufferAttachments) + { + if (attachment == GL_NONE) + continue; + + if (readable) + gl.framebufferTexture(attachment, TEXTURE_2D, texture, 0, 0, 0); + else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, renderbuffer); + } + + supported.set(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); + gl.deleteFramebuffer(fbo); + + if (texture != 0) + gl.deleteTexture(texture); + + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); + + return supported.value; } Shader::Language Graphics::getShaderLanguageTarget() const diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 4e68acd2d..7255ddfc2 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -104,9 +104,8 @@ public: void setWireframe(bool enable) override; - bool isCanvasFormatSupported(PixelFormat format) const override; - bool isCanvasFormatSupported(PixelFormat format, bool readable) const override; - bool isImageFormatSupported(PixelFormat format, bool sRGB) const override; + PixelFormat getSizedFormat(PixelFormat format) const override; + bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override; Renderer getRenderer() const override; RendererInfo getRendererInfo() const override; @@ -153,6 +152,9 @@ private: bool windowHasStencil; GLuint mainVAO; + // [rendertarget][readable][srgb] + OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2]; + }; // Graphics } // opengl diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index 5bdddeeb3..f8477f9ba 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -351,11 +351,6 @@ bool Image::setMipmapSharpness(float sharpness) return true; } -bool Image::isFormatSupported(PixelFormat pixelformat, bool sRGB) -{ - return OpenGL::isPixelFormatSupported(pixelformat, false, true, sRGB); -} - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index a1b203cfb..ac4045e3f 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -54,8 +54,6 @@ public: bool setMipmapSharpness(float sharpness) override; - static bool isFormatSupported(PixelFormat pixelformat, bool sRGB); - private: void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r) override; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index f42938b1f..451d04f82 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -2282,14 +2282,14 @@ int w_getCanvasFormats(lua_State *L) { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format, true); + return instance()->isPixelFormatSupported(format, true, true, false); }; } else { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format, false); + return instance()->isPixelFormatSupported(format, true, false, false); }; } } @@ -2297,7 +2297,8 @@ int w_getCanvasFormats(lua_State *L) { supported = [](PixelFormat format) -> bool { - return instance()->isCanvasFormatSupported(format); + bool readable = !isPixelFormatDepthStencil(format); + return instance()->isPixelFormatSupported(format, true, readable, false); }; } @@ -2308,7 +2309,7 @@ int w_getImageFormats(lua_State *L) { const auto supported = [](PixelFormat format) -> bool { - return instance()->isImageFormatSupported(format); + return instance()->isPixelFormatSupported(format, false, true, false); }; const auto ignore = [](PixelFormat format) -> bool