From 018a3831cb978965bbe07bf48e259393d380bed3 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Tue, 11 Apr 2017 00:00:22 -0300 Subject: [PATCH] =?UTF-8?q?Add=20a=20new=20=E2=80=9Cstencil8=E2=80=9D=20pi?= =?UTF-8?q?xel=20format=20for=20Canvases=20(resolves=20issue=20#1003).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fixed Canvas MSAA (resolves issue #1271). stencil-formatted Canvases can’t be drawn, and can only be used as the value for a new ‘depthstencil’ field to the table-argument variant of love.graphics.setCanvas. --HG-- branch : minor --- src/common/pixelformat.cpp | 8 + src/common/pixelformat.h | 8 + src/modules/graphics/Canvas.h | 2 +- src/modules/graphics/Graphics.cpp | 62 ++-- src/modules/graphics/Graphics.h | 37 ++- src/modules/graphics/Shader.cpp | 10 +- src/modules/graphics/Shader.h | 1 + src/modules/graphics/Texture.cpp | 12 + src/modules/graphics/Texture.h | 3 + src/modules/graphics/opengl/Canvas.cpp | 207 +++++++------ src/modules/graphics/opengl/Canvas.h | 18 +- src/modules/graphics/opengl/Graphics.cpp | 275 ++++++++++-------- src/modules/graphics/opengl/Graphics.h | 17 +- src/modules/graphics/opengl/Image.cpp | 4 +- src/modules/graphics/opengl/Mesh.cpp | 2 +- src/modules/graphics/opengl/OpenGL.cpp | 34 ++- src/modules/graphics/opengl/OpenGL.h | 5 +- .../graphics/opengl/ParticleSystem.cpp | 2 +- src/modules/graphics/opengl/Shader.cpp | 2 +- src/modules/graphics/opengl/SpriteBatch.cpp | 2 +- src/modules/graphics/wrap_Canvas.cpp | 12 +- src/modules/graphics/wrap_Graphics.cpp | 108 ++++--- src/modules/graphics/wrap_Texture.cpp | 8 + 23 files changed, 518 insertions(+), 321 deletions(-) diff --git a/src/common/pixelformat.cpp b/src/common/pixelformat.cpp index 9c22a3f54..f404d3642 100644 --- a/src/common/pixelformat.cpp +++ b/src/common/pixelformat.cpp @@ -52,6 +52,8 @@ static StringMap::Entry formatEntries[] = { "rgb565", PIXELFORMAT_RGB565 }, { "rgb10a2", PIXELFORMAT_RGB10A2 }, { "rg11b10f", PIXELFORMAT_RG11B10F }, + + { "stencil8", PIXELFORMAT_STENCIL8 }, { "DXT1", PIXELFORMAT_DXT1 }, { "DXT3", PIXELFORMAT_DXT3 }, @@ -112,11 +114,17 @@ bool isPixelFormatCompressed(PixelFormat format) return iformat >= (int) PIXELFORMAT_DXT1 && iformat < (int) PIXELFORMAT_MAX_ENUM; } +bool isPixelFormatDepthStencil(PixelFormat format) +{ + return format == PIXELFORMAT_STENCIL8; +} + size_t getPixelFormatSize(PixelFormat format) { switch (format) { case PIXELFORMAT_R8: + case PIXELFORMAT_STENCIL8: return 1; case PIXELFORMAT_RG8: case PIXELFORMAT_R16: diff --git a/src/common/pixelformat.h b/src/common/pixelformat.h index f6f5ec55f..71765aa0a 100644 --- a/src/common/pixelformat.h +++ b/src/common/pixelformat.h @@ -57,6 +57,9 @@ enum PixelFormat PIXELFORMAT_RGB10A2, PIXELFORMAT_RG11B10F, + // depth/stencil formats + PIXELFORMAT_STENCIL8, + // compressed formats PIXELFORMAT_DXT1, PIXELFORMAT_DXT3, @@ -106,6 +109,11 @@ bool getConstant(const char *in, PixelFormat &out); **/ bool isPixelFormatCompressed(PixelFormat format); +/** + * Gets whether the specified pixel format is a depth/stencil type. + **/ +bool isPixelFormatDepthStencil(PixelFormat format); + /** * Gets the size in bytes of the specified pixel format. * NOTE: Currently returns 0 for compressed formats. diff --git a/src/modules/graphics/Canvas.h b/src/modules/graphics/Canvas.h index 6686355b8..3088805f1 100644 --- a/src/modules/graphics/Canvas.h +++ b/src/modules/graphics/Canvas.h @@ -55,7 +55,7 @@ public: virtual int getMSAA() const = 0; virtual int getRequestedMSAA() const = 0; - virtual ptrdiff_t getMSAAHandle() const = 0; + virtual ptrdiff_t getRenderTargetHandle() const = 0; void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override; void drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &t) override; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 1d3b833cf..704505530 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -187,9 +187,9 @@ int Graphics::getPixelHeight() const double Graphics::getCurrentPixelDensity() const { - if (states.back().renderTargets.size() > 0) + if (states.back().renderTargets.colors.size() > 0) { - love::graphics::Canvas *c = states.back().renderTargets[0].canvas; + Canvas *c = states.back().renderTargets.colors[0].canvas; return (double) c->getPixelHeight() / (double) c->getHeight(); } @@ -291,19 +291,28 @@ void Graphics::restoreStateChecked(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - bool canvaseschanged = s.renderTargets.size() != cur.renderTargets.size(); + const auto &sRTs = s.renderTargets; + const auto &curRTs = cur.renderTargets; + + bool canvaseschanged = sRTs.colors.size() != curRTs.colors.size(); if (!canvaseschanged) { - for (size_t i = 0; i < s.renderTargets.size() && i < cur.renderTargets.size(); i++) + for (size_t i = 0; i < sRTs.colors.size() && i < curRTs.colors.size(); i++) { - const auto &rt1 = s.renderTargets[i]; - const auto &rt2 = cur.renderTargets[i]; + const auto &rt1 = sRTs.colors[i]; + const auto &rt2 = curRTs.colors[i]; if (rt1.canvas.get() != rt2.canvas.get() || rt1.slice != rt2.slice) { canvaseschanged = true; break; } } + + if (!canvaseschanged && (sRTs.depthStencil.canvas.get() != curRTs.depthStencil.canvas.get() + || sRTs.depthStencil.slice != curRTs.depthStencil.slice)) + { + canvaseschanged = true; + } } if (canvaseschanged) @@ -401,45 +410,58 @@ void Graphics::setCanvas(RenderTarget rt) if (rt.canvas == nullptr) return setCanvas(); - std::vector rts = {rt}; + RenderTargets rts; + rts.colors.push_back(rt); + setCanvas(rts); } -void Graphics::setCanvas(const std::vector &rts) +void Graphics::setCanvas(const RenderTargetsStrongRef &rts) { - std::vector canvaslist; - canvaslist.reserve(rts.size()); + RenderTargets targets; + targets.colors.reserve(rts.colors.size()); - for (const auto &rt : rts) - canvaslist.emplace_back(rt.canvas.get(), rt.slice); + for (const auto &rt : rts.colors) + targets.colors.emplace_back(rt.canvas.get(), rt.slice); - return setCanvas(canvaslist); + targets.depthStencil = RenderTarget(rts.depthStencil.canvas, rts.depthStencil.slice); + + return setCanvas(targets); } -std::vector Graphics::getCanvas() const +Graphics::RenderTargets Graphics::getCanvas() const { - std::vector rts; - rts.reserve(states.back().renderTargets.size()); + const auto &curRTs = states.back().renderTargets; - for (const auto &rt : states.back().renderTargets) - rts.emplace_back(rt.canvas.get(), rt.slice); + RenderTargets rts; + rts.colors.reserve(curRTs.colors.size()); + + for (const auto &rt : curRTs.colors) + rts.colors.emplace_back(rt.canvas.get(), rt.slice); + + rts.depthStencil = RenderTarget(curRTs.depthStencil.canvas, curRTs.depthStencil.slice); return rts; } bool Graphics::isCanvasActive() const { - return !states.back().renderTargets.empty(); + return !states.back().renderTargets.colors.empty(); } bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const { - for (const auto &rt : states.back().renderTargets) + const auto &curRTs = states.back().renderTargets; + + for (const auto &rt : curRTs.colors) { if (rt.canvas.get() == canvas) return true; } + if (curRTs.depthStencil.canvas.get() == canvas) + return true; + return false; } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index bc6c5b3b4..a55e8872a 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -315,13 +315,18 @@ public: struct RenderTarget { - Canvas *canvas = nullptr; - int slice = 0; + Canvas *canvas; + int slice; RenderTarget(Canvas *canvas, int slice = 0) : canvas(canvas) , slice(slice) {} + + RenderTarget() + : canvas(nullptr) + , slice(0) + {} }; struct RenderTargetStrongRef @@ -335,6 +340,26 @@ public: {} }; + struct RenderTargets + { + std::vector colors; + RenderTarget depthStencil; + + RenderTargets() + : depthStencil(nullptr) + {} + }; + + struct RenderTargetsStrongRef + { + std::vector colors; + RenderTargetStrongRef depthStencil; + + RenderTargetsStrongRef() + : depthStencil(nullptr) + {} + }; + Graphics(); virtual ~Graphics(); @@ -456,11 +481,11 @@ public: Shader *getShader() const; void setCanvas(RenderTarget rt); - virtual void setCanvas(const std::vector &rts) = 0; - void setCanvas(const std::vector &rts); + virtual void setCanvas(const RenderTargets &rts) = 0; + void setCanvas(const RenderTargetsStrongRef &rts); virtual void setCanvas() = 0; - std::vector getCanvas() const; + RenderTargets getCanvas() const; bool isCanvasActive() const; bool isCanvasActive(Canvas *canvas) const; @@ -813,7 +838,7 @@ protected: StrongRef font; StrongRef shader; - std::vector renderTargets; + RenderTargetsStrongRef renderTargets; ColorMask colorMask = ColorMask(true, true, true, true); diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index f0884536f..8b5938eba 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -206,6 +206,14 @@ void Shader::checkMainTextureType(TextureType textype) const } } +void Shader::checkMainTexture(Texture *texture) const +{ + if (!texture->isReadable()) + throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader."); + + checkMainTextureType(texture->getTextureType()); +} + bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err) { if (source.vertex.empty() && source.pixel.empty()) @@ -242,7 +250,7 @@ bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool if (!s.parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings)) { - const char *stagename; + const char *stagename = "unknown"; getConstant(stage, stagename); err = "Error validating " + std::string(stagename) + " shader:\n\n" + std::string(s.getInfoLog()) + "\n" + std::string(s.getInfoDebugLog()); diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 8ed38a53e..de1446afe 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -190,6 +190,7 @@ public: TextureType getMainTextureType() const; void checkMainTextureType(TextureType textype) const; + void checkMainTexture(Texture *texture) const; virtual ptrdiff_t getHandle() const = 0; diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index ff91966b1..df49fe057 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -51,6 +51,7 @@ float Texture::defaultMipmapSharpness = 0.0f; Texture::Texture(TextureType texType) : texType(texType) , format(PIXELFORMAT_UNKNOWN) + , readable(true) , width(0) , height(0) , depth(1) @@ -84,6 +85,11 @@ PixelFormat Texture::getPixelFormat() const return format; } +bool Texture::isReadable() const +{ + return readable; +} + void Texture::draw(Graphics *gfx, const Matrix4 &m) { draw(gfx, quad, m); @@ -93,6 +99,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform) { using namespace vertex; + if (!readable) + throw love::Exception("Textures with non-readable formats cannot be drawn."); + if (texType == TEXTURE_2D_ARRAY) { drawLayer(gfx, q->getLayer(), q, localTransform); @@ -132,6 +141,9 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m) { using namespace vertex; + if (!readable) + throw love::Exception("Textures with non-readable formats cannot be drawn."); + if (texType != TEXTURE_2D_ARRAY) throw love::Exception("drawLayer can only be used with Array Textures!"); diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 944d58563..e91a94826 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -120,6 +120,8 @@ public: TextureType getTextureType() const; PixelFormat getPixelFormat() const; + bool isReadable() const; + int getWidth() const; int getHeight() const; int getDepth() const; @@ -166,6 +168,7 @@ protected: TextureType texType; PixelFormat format; + bool readable; int width; int height; diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 88e245bb8..add3a98c8 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -69,8 +69,9 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, GLuint texture return status; } -static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer) +static bool createRenderbuffer(int width, int height, int &samples, PixelFormat pixelformat, GLuint &buffer) { + int reqsamples = samples; bool unusedSRGB = false; OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(pixelformat, true, unusedSRGB); @@ -84,16 +85,23 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi glGenRenderbuffers(1, &buffer); glBindRenderbuffer(GL_RENDERBUFFER, buffer); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, buffer); + if (samples > 1) + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, fmt.internalformat, width, height); + else + glRenderbufferStorage(GL_RENDERBUFFER, fmt.internalformat, width, height); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, fmt.framebufferAttachments[0], GL_RENDERBUFFER, buffer); + + if (samples > 1) + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + else + samples = 0; glBindRenderbuffer(GL_RENDERBUFFER, 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status == GL_FRAMEBUFFER_COMPLETE && samples > 1) + if (status == GL_FRAMEBUFFER_COMPLETE && (reqsamples <= 1 || samples > 1)) { // Initialize the buffer to transparent black. glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -109,16 +117,17 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); gl.deleteFramebuffer(fbo); - return status == GL_FRAMEBUFFER_COMPLETE && samples > 1; + return status == GL_FRAMEBUFFER_COMPLETE; } Canvas::Canvas(const Settings &settings) : love::graphics::Canvas(settings.type) , fbo(0) , texture(0) - , msaa_buffer(0) - , actual_samples(0) - , texture_memory(0) + , renderbuffer(0) + , requestedSamples(settings.msaa) + , actualSamples(0) + , textureMemory(0) { this->width = settings.width; this->height = settings.height; @@ -140,6 +149,8 @@ Canvas::Canvas(const Settings &settings) this->format = getSizedFormat(settings.format); + readable = this->format != PIXELFORMAT_STENCIL8; + initQuad(); loadVolatile(); @@ -167,9 +178,12 @@ bool Canvas::loadVolatile() throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr); } - if (settings.msaa > 1 && texType != TEXTURE_2D) + if (requestedSamples > 1 && texType != TEXTURE_2D) throw love::Exception("MSAA is only supported for 2D texture types."); + if (!readable && texType != TEXTURE_2D) + throw love::Exception("Non-readable pixel formats are only supported for 2D texture types."); + if (!gl.isTextureTypeSupported(texType)) { const char *textypestr = "unknown"; @@ -214,68 +228,74 @@ bool Canvas::loadVolatile() OpenGL::TempDebugGroup debuggroup("Canvas load"); fbo = texture = 0; - msaa_buffer = 0; + renderbuffer = 0; status = GL_FRAMEBUFFER_COMPLETE; + if (isReadable()) + { + glGenTextures(1, &texture); + gl.bindTextureToUnit(this, 0, false); + + GLenum gltype = OpenGL::getGLTextureType(texType); + + if (GLAD_ANGLE_texture_usage) + glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); + + setFilter(filter); + setWrap(wrap); + + while (glGetError() != GL_NO_ERROR) + /* Clear the error buffer. */; + + bool isSRGB = format == PIXELFORMAT_sRGBA8; + if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers)) + { + status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + return false; + } + + if (glGetError() != GL_NO_ERROR) + { + gl.deleteTexture(texture); + texture = 0; + status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + return false; + } + + // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. + status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + if (fbo != 0) + { + gl.deleteFramebuffer(fbo); + fbo = 0; + } + return false; + } + } + // getMaxRenderbufferSamples will be 0 on systems that don't support // multisampled renderbuffers / don't export FBO multisample extensions. - settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples()); - settings.msaa = std::max(settings.msaa, 0); + actualSamples = requestedSamples; + actualSamples = std::min(actualSamples, gl.getMaxRenderbufferSamples()); + actualSamples = std::max(actualSamples, 0); + actualSamples = actualSamples == 1 ? 0 : actualSamples; - glGenTextures(1, &texture); - gl.bindTextureToUnit(this, 0, false); + if (!isReadable() || actualSamples > 0) + createRenderbuffer(pixelWidth, pixelHeight, actualSamples, format, renderbuffer); - GLenum gltype = OpenGL::getGLTextureType(texType); + size_t prevmemsize = textureMemory; - if (GLAD_ANGLE_texture_usage) - glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE); + textureMemory = getPixelFormatSize(format) * pixelWidth * pixelHeight; - setFilter(filter); - setWrap(wrap); + if (actualSamples > 0 && isReadable()) + textureMemory += (textureMemory * actualSamples); + else if (actualSamples > 0) + textureMemory *= actualSamples; - while (glGetError() != GL_NO_ERROR) - /* Clear the error buffer. */; - - bool isSRGB = format == PIXELFORMAT_sRGBA8; - if (!gl.rawTexStorage(texType, 1, format, isSRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers)) - { - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; - return false; - } - - if (glGetError() != GL_NO_ERROR) - { - gl.deleteTexture(texture); - texture = 0; - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; - return false; - } - - // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. - status = createFBO(fbo, texType, texture, texType == TEXTURE_VOLUME ? depth : layers, true); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - if (fbo != 0) - { - gl.deleteFramebuffer(fbo); - fbo = 0; - } - return false; - } - - actual_samples = settings.msaa == 1 ? 0 : settings.msaa; - - if (actual_samples > 0 && !createMSAABuffer(pixelWidth, pixelHeight, actual_samples, format, msaa_buffer)) - actual_samples = 0; - - size_t prevmemsize = texture_memory; - - texture_memory = getPixelFormatSize(format) * pixelWidth * pixelHeight; - if (msaa_buffer != 0) - texture_memory += (texture_memory * actual_samples); - - gl.updateTextureMemorySize(prevmemsize, texture_memory); + gl.updateTextureMemorySize(prevmemsize, textureMemory); return true; } @@ -285,18 +305,18 @@ void Canvas::unloadVolatile() if (fbo != 0) gl.deleteFramebuffer(fbo); - if (msaa_buffer != 0) - glDeleteRenderbuffers(1, &msaa_buffer); + if (renderbuffer != 0) + glDeleteRenderbuffers(1, &renderbuffer); if (texture != 0) gl.deleteTexture(texture); fbo = 0; - msaa_buffer = 0; + renderbuffer = 0; texture = 0; - gl.updateTextureMemorySize(texture_memory, 0); - texture_memory = 0; + gl.updateTextureMemorySize(textureMemory, 0); + textureMemory = 0; } void Canvas::setFilter(const Texture::Filter &f) @@ -355,6 +375,9 @@ ptrdiff_t Canvas::getHandle() const love::image::ImageData *Canvas::newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) { + if (!isReadable()) + throw love::Exception("Canvas:newImageData cannot be called on non-readable Canvases."); + if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight()) throw love::Exception("Invalid rectangle dimensions."); @@ -423,7 +446,7 @@ PixelFormat Canvas::getSizedFormat(PixelFormat format) case PIXELFORMAT_NORMAL: if (isGammaCorrect()) return PIXELFORMAT_sRGBA8; - else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8, true, false)) + else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8, true, true, false)) // 32-bit render targets don't have guaranteed support on GLES2. return PIXELFORMAT_RGBA4; else @@ -456,7 +479,10 @@ bool Canvas::isFormatSupported(PixelFormat format) bool supported = true; format = getSizedFormat(format); - if (!OpenGL::isPixelFormatSupported(format, true, false)) + bool depthstencil = isPixelFormatDepthStencil(format); + bool readable = !depthstencil; + + if (!OpenGL::isPixelFormatSupported(format, true, readable, false)) return false; if (checkedFormats[format]) @@ -466,28 +492,37 @@ bool Canvas::isFormatSupported(PixelFormat format) // 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. + if (readable) + { + GLuint texture = 0; + glGenTextures(1, &texture); + gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false); - GLuint texture = 0; - 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::Filter f; - f.min = f.mag = Texture::FILTER_NEAREST; - gl.setTextureFilter(TEXTURE_2D, f); + Texture::Wrap w; + gl.setTextureWrap(TEXTURE_2D, w); - Texture::Wrap w; - gl.setTextureWrap(TEXTURE_2D, w); + bool unusedSRGB = false; + gl.rawTexStorage(TEXTURE_2D, 1, format, unusedSRGB, 2, 2); - bool unusedSRGB = false; - OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, unusedSRGB); + GLuint fbo = 0; + supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE); + gl.deleteFramebuffer(fbo); - glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, 2, 2, 0, fmt.externalformat, fmt.type, nullptr); + gl.deleteTexture(texture); + } + else + { + int samples = 0; + GLuint renderbuffer = 0; + supported = createRenderbuffer(2, 2, samples, format, renderbuffer); - GLuint fbo = 0; - supported = (createFBO(fbo, TEXTURE_2D, texture, 1, false) == GL_FRAMEBUFFER_COMPLETE); - gl.deleteFramebuffer(fbo); - - gl.deleteTexture(texture); + if (supported) + glDeleteRenderbuffers(1, &renderbuffer); + } // Cache the result so we don't do this for every isFormatSupported call. checkedFormats[format] = true; diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 7647097ed..439901d0b 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -56,20 +56,19 @@ public: int getMSAA() const override { - return actual_samples; + return actualSamples; } int getRequestedMSAA() const override { - return settings.msaa; + return requestedSamples; } - ptrdiff_t getMSAAHandle() const override + ptrdiff_t getRenderTargetHandle() const override { - return msaa_buffer; + return renderbuffer != 0 ? renderbuffer : texture; } - inline GLenum getStatus() const { return status; @@ -87,18 +86,17 @@ public: private: - Settings settings; - GLuint fbo; GLuint texture; - GLuint msaa_buffer; + GLuint renderbuffer; GLenum status; - int actual_samples; + int requestedSamples; + int actualSamples; - size_t texture_memory; + size_t textureMemory; static bool supportedFormats[PIXELFORMAT_MAX_ENUM]; static bool checkedFormats[PIXELFORMAT_MAX_ENUM]; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index ed56ea3c8..ee7165a95 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -169,7 +169,7 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh this->pixelWidth = pixelwidth; this->pixelHeight = pixelheight; - if (states.back().renderTargets.empty()) + if (!isCanvasActive()) { // Set the viewport to top-left corner. gl.setViewport({0, 0, pixelwidth, pixelheight}); @@ -302,8 +302,8 @@ void Graphics::unSetMode() for (const auto &pair : framebufferObjects) gl.deleteFramebuffer(pair.second); - for (const CachedRenderbuffer &rb : stencilBuffers) - glDeleteRenderbuffers(1, &rb.renderbuffer); + for (love::graphics::Canvas *c : stencilBuffers) + c->release(); framebufferObjects.clear(); stencilBuffers.clear(); @@ -355,6 +355,9 @@ void Graphics::flushStreamDraws() Shader::standardShaders[Shader::STANDARD_ARRAY]->attach(); } + if (!sbstate.texture->isReadable()) + throw love::Exception("Textures with non-readable formats cannot be drawn."); + if (Shader::current) Shader::current->checkMainTextureType(textype); } @@ -502,28 +505,38 @@ void Graphics::setDebug(bool enable) ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n"); } -void Graphics::setCanvas(const std::vector &rts) +void Graphics::setCanvas(const RenderTargets &rts) { DisplayState &state = states.back(); - int ncanvases = (int) rts.size(); + int ncanvases = (int) rts.colors.size(); - if (ncanvases == 0) + if (ncanvases == 0 && rts.depthStencil.canvas == nullptr) return setCanvas(); + else if (ncanvases == 0) + throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer."); - if (ncanvases == (int) state.renderTargets.size()) + const auto &prevRTs = state.renderTargets; + + if (ncanvases == (int) prevRTs.colors.size()) { bool modified = false; for (int i = 0; i < ncanvases; i++) { - if (rts[i].canvas != state.renderTargets[i].canvas.get() - || rts[i].slice != state.renderTargets[i].slice) + if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get() + || rts.colors[i].slice != prevRTs.colors[i].slice) { modified = true; break; } } + if (!modified && rts.depthStencil.canvas == prevRTs.depthStencil.canvas + && rts.depthStencil.slice == prevRTs.depthStencil.slice) + { + modified = true; + } + if (!modified) return; } @@ -531,32 +544,53 @@ void Graphics::setCanvas(const std::vector &rts) if (ncanvases > gl.getMaxRenderTargets()) throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases); - love::graphics::Canvas *firstcanvas = rts[0].canvas; + love::graphics::Canvas *firstcanvas = rts.colors[0].canvas; bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported(); PixelFormat firstformat = firstcanvas->getPixelFormat(); + if (isPixelFormatDepthStencil(firstformat)) + throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas."); + bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8; int pixelwidth = firstcanvas->getPixelWidth(); int pixelheight = firstcanvas->getPixelHeight(); for (int i = 1; i < ncanvases; i++) { - love::graphics::Canvas *c = rts[i].canvas; + love::graphics::Canvas *c = rts.colors[i].canvas; + PixelFormat format = c->getPixelFormat(); if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight) - throw love::Exception("All canvases in must have the same pixel dimensions."); + throw love::Exception("All canvases must have the same pixel dimensions."); - if (!multiformatsupported && c->getPixelFormat() != firstformat) + if (!multiformatsupported && format != firstformat) throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) - throw love::Exception("All Canvases in must have the same MSAA value."); + throw love::Exception("All Canvases must have the same MSAA value."); - if (c->getPixelFormat() == PIXELFORMAT_sRGBA8) + if (isPixelFormatDepthStencil(format)) + throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas."); + + if (format == PIXELFORMAT_sRGBA8) hasSRGBcanvas = true; } + if (rts.depthStencil.canvas != nullptr) + { + love::graphics::Canvas *c = rts.depthStencil.canvas; + + if (!isPixelFormatDepthStencil(c->getPixelFormat())) + throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas."); + + if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight) + throw love::Exception("All canvases must have the same pixel dimensions."); + + if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) + throw love::Exception("All Canvases must have the same MSAA value."); + } + OpenGL::TempDebugGroup debuggroup("setCanvas(...)"); endPass(); @@ -583,13 +617,15 @@ void Graphics::setCanvas(const std::vector &rts) gl.setFramebufferSRGB(false); } - std::vector canvasrefs; - canvasrefs.reserve(rts.size()); + RenderTargetsStrongRef refs; + refs.colors.reserve(rts.colors.size()); - for (auto c : rts) - canvasrefs.emplace_back(c.canvas, c.slice); + for (auto c : rts.colors) + refs.colors.emplace_back(c.canvas, c.slice); - std::swap(state.renderTargets, canvasrefs); + refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice); + + std::swap(state.renderTargets, refs); canvasSwitchCount++; } @@ -598,14 +634,14 @@ void Graphics::setCanvas() { DisplayState &state = states.back(); - if (state.renderTargets.empty()) + if (state.renderTargets.colors.empty() && state.renderTargets.depthStencil.canvas == nullptr) return; OpenGL::TempDebugGroup debuggroup("setCanvas()"); endPass(); - state.renderTargets.clear(); + state.renderTargets = RenderTargetsStrongRef(); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); @@ -635,21 +671,25 @@ void Graphics::endPass() { flushStreamDraws(); - // Discard the stencil buffer. - discard({}, true); + auto &rts = states.back().renderTargets; - auto &canvases = states.back().renderTargets; + // Discard the stencil buffer if we're using an internal cached one. + if (rts.depthStencil.canvas.get() == nullptr) + discard({}, true); // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we // don't have to worry about resolving to slices. - if (canvases.size() > 0 && canvases[0].canvas->getMSAA() > 1) + if (rts.colors.size() > 0 && rts.colors[0].canvas->getMSAA() > 1) { - int w = canvases[0].canvas->getPixelWidth(); - int h = canvases[0].canvas->getPixelHeight(); + int w = rts.colors[0].canvas->getPixelWidth(); + int h = rts.colors[0].canvas->getPixelHeight(); - for (int i = 0; i < (int) canvases.size(); i++) + for (int i = 0; i < (int) rts.colors.size(); i++) { - Canvas *c = (Canvas *) canvases[i].canvas.get(); + Canvas *c = (Canvas *) rts.colors[i].canvas.get(); + + if (!c->isReadable()) + continue; glReadBuffer(GL_COLOR_ATTACHMENT0 + i); @@ -685,7 +725,7 @@ void Graphics::clear(const std::vector &colors) if (colors.size() == 0) return; - int ncanvases = (int) states.back().renderTargets.size(); + int ncanvases = (int) states.back().renderTargets.colors.size(); int ncolors = std::min((int) colors.size(), ncanvases); if (ncolors <= 1 && ncanvases <= 1) @@ -767,7 +807,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (states.back().renderTargets.empty() && gl.getDefaultFBO() == 0) + if (!isCanvasActive() && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); @@ -780,7 +820,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector } else { - int rendertargetcount = std::max((int) states.back().renderTargets.size(), 1); + int rendertargetcount = std::max((int) states.back().renderTargets.colors.size(), 1); for (int i = 0; i < (int) colorbuffers.size(); i++) { @@ -802,11 +842,18 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::bindCachedFBO(const std::vector &targets) +void Graphics::bindCachedFBO(const RenderTargets &targets) { - int ntargets = (int) targets.size(); - uint32 hash = XXH32(&targets[0], sizeof(RenderTarget) * ntargets, 0); + RenderTarget hashtargets[MAX_COLOR_RENDER_TARGETS + 1]; + int hashcount = 0; + for (int i = 0; i < (int) targets.colors.size(); i++) + hashtargets[hashcount++] = targets.colors[i]; + + if (targets.depthStencil.canvas != nullptr) + hashtargets[hashcount++] = targets.depthStencil; + + uint32 hash = XXH32(hashtargets, sizeof(RenderTarget) * hashcount, 0); GLuint fbo = framebufferObjects[hash]; if (fbo != 0) @@ -815,47 +862,66 @@ void Graphics::bindCachedFBO(const std::vector &targets) } else { - int w = targets[0].canvas->getPixelWidth(); - int h = targets[0].canvas->getPixelHeight(); - int msaa = std::max(targets[0].canvas->getMSAA(), 1); + int w = targets.colors[0].canvas->getPixelWidth(); + int h = targets.colors[0].canvas->getPixelHeight(); + int msaa = targets.colors[0].canvas->getMSAA(); + int reqmsaa = targets.colors[0].canvas->getRequestedMSAA(); + + RenderTarget depthstencil = targets.depthStencil; + + if (depthstencil.canvas == nullptr) + { + depthstencil.canvas = getCachedStencilBuffer(w, h, reqmsaa); + depthstencil.slice = 0; + } glGenFramebuffers(1, &fbo); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + int ncolortargets = 0; GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS]; - for (int i = 0; i < ntargets; i++) + auto attachCanvas = [&](const RenderTarget &rt) { - drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i; + bool renderbuffer = msaa > 1 || !rt.canvas->isReadable(); + bool srgb = false; + OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(rt.canvas->getPixelFormat(), renderbuffer, srgb); - if (msaa > 1) + if (fmt.framebufferAttachments[0] == GL_COLOR_ATTACHMENT0) { - GLuint rbo = (GLuint) targets[i].canvas->getMSAAHandle(); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo); + fmt.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0 + ncolortargets; + drawbuffers[ncolortargets] = fmt.framebufferAttachments[0]; + ncolortargets++; } - else + + GLuint handle = (GLuint) rt.canvas->getRenderTargetHandle(); + + for (GLenum attachment : fmt.framebufferAttachments) { - GLuint tex = (GLuint) targets[i].canvas->getHandle(); - TextureType textype = targets[i].canvas->getTextureType(); + if (attachment == GL_NONE) + continue; + else if (renderbuffer) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, handle); + else + { + TextureType textype = rt.canvas->getTextureType(); - int layer = textype == TEXTURE_CUBE ? 0 : targets[i].slice; - int face = textype == TEXTURE_CUBE ? targets[i].slice : 0; + int layer = textype == TEXTURE_CUBE ? 0 : rt.slice; + int face = textype == TEXTURE_CUBE ? rt.slice : 0; - gl.framebufferTexture(drawbuffers[i], textype, tex, 0, layer, face); + gl.framebufferTexture(attachment, textype, handle, 0, layer, face); + } } - } + }; - if (ntargets > 1) - glDrawBuffers(ntargets, drawbuffers); + for (const auto &rt : targets.colors) + attachCanvas(rt); - GLuint stencil = attachCachedStencilBuffer(w, h, targets[0].canvas->getRequestedMSAA()); + if (depthstencil.canvas != nullptr) + attachCanvas(depthstencil); - if (stencil == 0) - { - gl.deleteFramebuffer(fbo); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); - throw love::Exception("Could not create stencil buffer!"); - } + if (ncolortargets > 1) + glDrawBuffers(ncolortargets, drawbuffers); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -870,80 +936,33 @@ void Graphics::bindCachedFBO(const std::vector &targets) } } -GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples) +love::graphics::Canvas *Graphics::getCachedStencilBuffer(int w, int h, int samples) { - samples = samples == 1 ? 0 : samples; + love::graphics::Canvas *canvas = nullptr; - for (const CachedRenderbuffer &rb : stencilBuffers) + for (love::graphics::Canvas *c : stencilBuffers) { - if (rb.w == w && rb.h == h && rb.samples == samples) + if (c->getPixelWidth() == w && c->getPixelHeight() == h && c->getRequestedMSAA() == samples) { - // Attach the buffer to the framebuffer object. - for (GLenum attachment : rb.attachments) - { - if (attachment != GL_NONE) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); - } - - return rb.renderbuffer; + canvas = c; + break; } } - OpenGL::TempDebugGroup debuggroup("Create cached stencil buffer"); - - CachedRenderbuffer rb; - rb.w = w; - rb.h = h; - rb.samples = samples; - - rb.attachments[0] = GL_STENCIL_ATTACHMENT; - rb.attachments[1] = GL_NONE; - - GLenum format = GL_STENCIL_INDEX8; - - // Prefer a combined depth/stencil buffer. - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) + if (canvas == nullptr) { - format = GL_DEPTH24_STENCIL8; - rb.attachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; - } - else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) - { - format = GL_DEPTH24_STENCIL8; - rb.attachments[0] = GL_DEPTH_ATTACHMENT; - rb.attachments[1] = GL_STENCIL_ATTACHMENT; + Canvas::Settings settings; + settings.format = PIXELFORMAT_STENCIL8; + settings.width = w; + settings.height = h; + settings.msaa = samples; + + canvas = newCanvas(settings); + + stencilBuffers.push_back(canvas); } - glGenRenderbuffers(1, &rb.renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, rb.renderbuffer); - - if (rb.samples > 1) - glRenderbufferStorageMultisample(GL_RENDERBUFFER, rb.samples, format, rb.w, rb.h); - else - glRenderbufferStorage(GL_RENDERBUFFER, format, rb.w, rb.h); - - // Attach the buffer to the framebuffer object. - for (GLenum attachment : rb.attachments) - { - if (attachment != GL_NONE) - glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); - } - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffers(1, &rb.renderbuffer); - rb.renderbuffer = 0; - } - - if (rb.renderbuffer != 0) - { - glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - stencilBuffers.push_back(rb); - } - - return rb.renderbuffer; + return canvas; } void Graphics::present(void *screenshotCallbackData) @@ -951,7 +970,7 @@ void Graphics::present(void *screenshotCallbackData) if (!isActive()) return; - if (!states.back().renderTargets.empty()) + if (isCanvasActive()) throw love::Exception("present cannot be called while a Canvas is active."); endPass(); @@ -1083,7 +1102,7 @@ void Graphics::setScissor(const Rect &rect) glrect.h = (int) (rect.h * density); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor(glrect, !state.renderTargets.empty()); + gl.setScissor(glrect, isCanvasActive()); state.scissor = true; state.scissorRect = rect; @@ -1100,7 +1119,7 @@ void Graphics::setScissor() void Graphics::drawToStencilBuffer(StencilAction action, int value) { - if (states.back().renderTargets.empty() && !windowHasStencil) + if (!isCanvasActive() && !windowHasStencil) throw love::Exception("The window must have stenciling enabled to draw to the main screen's stencil buffer."); flushStreamDraws(); @@ -1161,7 +1180,7 @@ void Graphics::stopDrawToStencilBuffer() void Graphics::setStencilTest(CompareMode compare, int value) { - if (compare != COMPARE_ALWAYS && states.back().renderTargets.empty() && !windowHasStencil) + if (compare != COMPARE_ALWAYS && !isCanvasActive() && !windowHasStencil) throw love::Exception("The window must have stenciling enabled to use setStencilTest on the main screen."); DisplayState &state = states.back(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index aec148df3..ff7a81935 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -97,7 +97,7 @@ public: void setColor(Colorf c) override; - void setCanvas(const std::vector &rts) override; + void setCanvas(const RenderTargets &rts) override; void setCanvas() override; void setScissor(const Rect &rect) override; @@ -131,26 +131,17 @@ public: private: - struct CachedRenderbuffer - { - int w; - int h; - int samples; - GLenum attachments[2]; - GLuint renderbuffer; - }; - love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override; void endPass(); - void bindCachedFBO(const std::vector &targets); + void bindCachedFBO(const RenderTargets &targets); void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); - GLuint attachCachedStencilBuffer(int w, int h, int samples); + love::graphics::Canvas *getCachedStencilBuffer(int w, int h, int samples); void setDebug(bool enable); std::unordered_map framebufferObjects; - std::vector stencilBuffers; + std::vector stencilBuffers; QuadIndices *quadIndices; diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index b56762a3b..1b1022aec 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -237,7 +237,7 @@ bool Image::loadVolatile() OpenGL::TempDebugGroup debuggroup("Image load"); - if (!OpenGL::isPixelFormatSupported(format, false, sRGB)) + if (!OpenGL::isPixelFormatSupported(format, false, true, sRGB)) { const char *str; if (love::getConstant(format, str)) @@ -511,7 +511,7 @@ Image::MipmapsType Image::getMipmapsType() const bool Image::isFormatSupported(PixelFormat pixelformat) { - return OpenGL::isPixelFormatSupported(pixelformat, false, false); + return OpenGL::isPixelFormatSupported(pixelformat, false, true, false); } bool Image::hasSRGBSupport() diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index 9173ebdcd..d9c65bf6a 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -100,7 +100,7 @@ void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, throw love::Exception("Instancing is not supported on this system."); if (Shader::current && texture.get()) - Shader::current->checkMainTextureType(texture->getTextureType()); + Shader::current->checkMainTexture(texture); gfx->flushStreamDraws(); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 481c134c9..09293b20a 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -1173,6 +1173,9 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r { TextureFormat f; + f.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0; + f.framebufferAttachments[1] = GL_NONE; + if (pixelformat == PIXELFORMAT_RGBA8 && isSRGB) pixelformat = PIXELFORMAT_sRGBA8; else if (pixelformat == PIXELFORMAT_ETC1) @@ -1314,6 +1317,32 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r f.type = GL_UNSIGNED_INT_10F_11F_11F_REV; break; + case PIXELFORMAT_STENCIL8: + // Prefer a combined depth/stencil buffer due to driver issues. + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) + { + f.internalformat = GL_DEPTH24_STENCIL8; + f.externalformat = GL_DEPTH_STENCIL; + f.type = GL_UNSIGNED_INT_24_8; + f.framebufferAttachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; + } + else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) + { + f.internalformat = GL_DEPTH24_STENCIL8; + f.externalformat = GL_DEPTH_STENCIL; + f.type = GL_UNSIGNED_INT_24_8; + f.framebufferAttachments[0] = GL_DEPTH_ATTACHMENT; + f.framebufferAttachments[1] = GL_STENCIL_ATTACHMENT; + } + else + { + f.internalformat = GL_STENCIL_INDEX8; + f.externalformat = GL_STENCIL; + f.type = GL_UNSIGNED_BYTE; + f.framebufferAttachments[0] = GL_STENCIL_ATTACHMENT; + } + break; + case PIXELFORMAT_DXT1: f.internalformat = isSRGB ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; @@ -1454,7 +1483,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r return f; } -bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool isSRGB) +bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB) { if (rendertarget && isPixelFormatCompressed(pixelformat)) return false; @@ -1548,6 +1577,9 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, else return GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float; + case PIXELFORMAT_STENCIL8: + return rendertarget && !readable; + case PIXELFORMAT_DXT1: return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1; case PIXELFORMAT_DXT3: diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 8adf35648..5a8dbc189 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -96,6 +96,9 @@ public: GLenum externalformat = 0; GLenum type = 0; + // For depth/stencil formats. + GLenum framebufferAttachments[2]; + bool swizzled = false; GLint swizzle[4]; }; @@ -379,7 +382,7 @@ public: static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB); static bool isTexStorageSupported(); - static bool isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool isSRGB); + static bool isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, bool readable, bool isSRGB); static bool hasTextureFilteringSupport(PixelFormat pixelformat); static const char *errorString(GLenum errorcode); diff --git a/src/modules/graphics/opengl/ParticleSystem.cpp b/src/modules/graphics/opengl/ParticleSystem.cpp index f80be1fa2..dfa07b2c0 100644 --- a/src/modules/graphics/opengl/ParticleSystem.cpp +++ b/src/modules/graphics/opengl/ParticleSystem.cpp @@ -58,7 +58,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m) return; if (Shader::current && texture.get()) - Shader::current->checkMainTextureType(texture->getTextureType()); + Shader::current->checkMainTexture(texture); OpenGL::TempDebugGroup debuggroup("ParticleSystem draw"); diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 6200b1169..3cb094faf 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -677,7 +677,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count { if (textures[i] != nullptr) { - if (textures[i]->getTextureType() != info->textureType) + if (textures[i]->getTextureType() != info->textureType || !textures[i]->isReadable()) continue; textures[i]->retain(); diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index fbdd9dbd8..69af74003 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -76,7 +76,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) } if (Shader::current) - Shader::current->checkMainTextureType(texture->getTextureType()); + Shader::current->checkMainTexture(texture); } OpenGL::TempDebugGroup debuggroup("SpriteBatch draw"); diff --git a/src/modules/graphics/wrap_Canvas.cpp b/src/modules/graphics/wrap_Canvas.cpp index 6ea98bbe7..7de581b74 100644 --- a/src/modules/graphics/wrap_Canvas.cpp +++ b/src/modules/graphics/wrap_Canvas.cpp @@ -57,11 +57,14 @@ int w_Canvas_renderTo(lua_State *L) if (graphics) { // Save the current render targets so we can restore them when we're done. - std::vector oldtargets = graphics->getCanvas(); + Graphics::RenderTargets oldtargets = graphics->getCanvas(); - for (auto c : oldtargets) + for (auto c : oldtargets.colors) c.canvas->retain(); + if (oldtargets.depthStencil.canvas != nullptr) + oldtargets.depthStencil.canvas->retain(); + luax_catchexcept(L, [&](){ graphics->setCanvas(rt); }); lua_settop(L, 2); // make sure the function is on top of the stack @@ -69,9 +72,12 @@ int w_Canvas_renderTo(lua_State *L) graphics->setCanvas(oldtargets); - for (auto c : oldtargets) + for (auto c : oldtargets.colors) c.canvas->release(); + if (oldtargets.depthStencil.canvas != nullptr) + oldtargets.depthStencil.canvas->release(); + if (status != 0) return lua_error(L); } diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 046e11455..830793cf3 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -124,7 +124,7 @@ int w_discard(lua_State *L) else { bool discardcolor = luax_optboolean(L, 1, true); - size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size()); + size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().colors.size()); colorbuffers = std::vector(numbuffers, discardcolor); } @@ -201,6 +201,21 @@ int w_getPixelDensity(lua_State *L) return 1; } +static Graphics::RenderTarget checkRenderTarget(lua_State *L, int idx) +{ + lua_rawgeti(L, idx, 1); + Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0); + lua_pop(L, 1); + + TextureType type = target.canvas->getTextureType(); + if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) + target.slice = luax_checkintflag(L, idx, "layer") - 1; + else if (type == TEXTURE_CUBE) + target.slice = luax_checkintflag(L, idx, "face") - 1; + + return target; +} + int w_setCanvas(lua_State *L) { // Disable stencil writes. @@ -214,7 +229,7 @@ int w_setCanvas(lua_State *L) } bool is_table = lua_istable(L, 1); - std::vector targets; + Graphics::RenderTargets targets; if (is_table) { @@ -227,29 +242,24 @@ int w_setCanvas(lua_State *L) lua_rawgeti(L, 1, i); if (table_of_tables) - { - lua_rawgeti(L, -1, 1); - Graphics::RenderTarget target(luax_checkcanvas(L, -1), 0); - lua_pop(L, 1); - - TextureType type = target.canvas->getTextureType(); - if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) - target.slice = luax_checkintflag(L, -1, "layer") - 1; - else if (type == TEXTURE_CUBE) - target.slice = luax_checkintflag(L, -1, "face") - 1; - - targets.push_back(target); - } + targets.colors.push_back(checkRenderTarget(L, -1)); else { - targets.emplace_back(luax_checkcanvas(L, -1), 0); + targets.colors.emplace_back(luax_checkcanvas(L, -1), 0); - if (targets.back().canvas->getTextureType() != TEXTURE_2D) + if (targets.colors.back().canvas->getTextureType() != TEXTURE_2D) return luaL_error(L, "The table-of-tables variant of setCanvas must be used with non-2D Canvases."); } lua_pop(L, 1); } + + lua_getfield(L, 1, "depthstencil"); + if (lua_istable(L, -1)) + targets.depthStencil = checkRenderTarget(L, -1); + else if (!lua_isnoneornil(L, -1)) + targets.depthStencil.canvas = luax_checkcanvas(L, -1); + lua_pop(L, 1); } else { @@ -261,19 +271,19 @@ int w_setCanvas(lua_State *L) if (i == 1 && type != TEXTURE_2D) { target.slice = (int) luaL_checknumber(L, 2) - 1; - targets.push_back(target); + targets.colors.push_back(target); break; } if (i > 1 && type != TEXTURE_2D) return luaL_error(L, "This variant of setCanvas only supports 2D texture types."); - targets.push_back(target); + targets.colors.push_back(target); } } luax_catchexcept(L, [&]() { - if (targets.size() > 0) + if (targets.colors.size() > 0) instance()->setCanvas(targets); else instance()->setCanvas(); @@ -282,10 +292,31 @@ int w_setCanvas(lua_State *L) return 0; } +static void pushRenderTarget(lua_State *L, const Graphics::RenderTarget &rt) +{ + lua_createtable(L, 1, 1); + + luax_pushtype(L, rt.canvas); + lua_rawseti(L, -2, 1); + + TextureType type = rt.canvas->getTextureType(); + + if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) + { + lua_pushnumber(L, rt.slice + 1); + lua_setfield(L, -2, "layer"); + } + else if (type == TEXTURE_VOLUME) + { + lua_pushnumber(L, rt.slice + 1); + lua_setfield(L, -2, "face"); + } +} + int w_getCanvas(lua_State *L) { - const std::vector targets = instance()->getCanvas(); - int ntargets = (int) targets.size(); + Graphics::RenderTargets targets = instance()->getCanvas(); + int ntargets = (int) targets.colors.size(); if (ntargets == 0) { @@ -294,7 +325,7 @@ int w_getCanvas(lua_State *L) } bool hasNon2DTextureType = false; - for (const auto &rt : targets) + for (const auto &rt : targets.colors) { if (rt.canvas->getTextureType() != TEXTURE_2D) { @@ -303,40 +334,27 @@ int w_getCanvas(lua_State *L) } } - if (hasNon2DTextureType) + if (hasNon2DTextureType || targets.depthStencil.canvas != nullptr) { lua_createtable(L, ntargets, 0); for (int i = 0; i < ntargets; i++) { - const auto &rt = targets[i]; - - lua_createtable(L, 1, 1); - - luax_pushtype(L, rt.canvas); - lua_rawseti(L, -2, 1); - - TextureType type = rt.canvas->getTextureType(); - - if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME) - { - lua_pushnumber(L, rt.slice + 1); - lua_setfield(L, -2, "layer"); - } - else if (type == TEXTURE_VOLUME) - { - lua_pushnumber(L, rt.slice + 1); - lua_setfield(L, -2, "face"); - } - + pushRenderTarget(L, targets.colors[i]); lua_rawseti(L, -2, i + 1); } + if (targets.depthStencil.canvas != nullptr) + { + pushRenderTarget(L, targets.depthStencil); + lua_setfield(L, -2, "depthstencil"); + } + return 1; } else { - for (const auto &rt : targets) + for (const auto &rt : targets.colors) luax_pushtype(L, rt.canvas); return ntargets; diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index 9f2d23bdf..4c9cf26b7 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -237,6 +237,13 @@ int w_Texture_getFormat(lua_State *L) return 1; } +int w_Texture_isReadable(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->isReadable()); + return 1; +} + const luaL_Reg w_Texture_functions[] = { { "getTextureType", w_Texture_getTextureType }, @@ -257,6 +264,7 @@ const luaL_Reg w_Texture_functions[] = { "setWrap", w_Texture_setWrap }, { "getWrap", w_Texture_getWrap }, { "getFormat", w_Texture_getFormat }, + { "isReadable", w_Texture_isReadable }, { 0, 0 } };