diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index f8503d926..b05867b70 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -33,423 +33,54 @@ namespace graphics namespace opengl { -// strategy for fbo creation, interchangable at runtime: -// none, opengl >= 3.0, extensions -struct FramebufferStrategy +static GLenum createFBO(GLuint &framebuffer, GLuint texture) { - virtual ~FramebufferStrategy() {} + // get currently bound fbo to reset to it later + GLint current_fbo; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - /// create a new framebuffer and texture - /** - * @param[out] framebuffer Framebuffer name - * @param[in] texture Texture name - * @return Creation status - */ - virtual GLenum createFBO(GLuint &, GLuint) - { - return GL_FRAMEBUFFER_UNSUPPORTED; - } + glGenFramebuffers(1, &framebuffer); + gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer); - /// Create a stencil buffer and attach it to the active framebuffer object - /** - * @param[in] width Width of the stencil buffer - * @param[in] height Height of the stencil buffer - * @param[in] samples Number of samples to use - * @param[out] stencil Name for stencil buffer - * @return Whether the stencil buffer was successfully created - **/ - virtual bool createStencil(int, int, int, GLuint &) - { - return false; - } + if (texture != 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - /// Create a MSAA renderbuffer and attach it to the active FBO. - /** - * @param[in] width Width of the MSAA buffer - * @param[in] height Height of the MSAA buffer - * @param[inout] samples Number of samples to use - * @param[in] internalformat The internal format to use for the buffer - * @param[out] buffer Name for the MSAA buffer - * @return Whether the MSAA buffer was successfully created and attached - **/ - virtual bool createMSAABuffer(int, int, int &, GLenum, GLuint &) - { - return false; - } + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - /// remove objects - /** - * @param[in] framebuffer Framebuffer name - * @param[in] depth_stencil Name for packed depth and stencil buffer - */ - virtual void deleteFBO(GLuint, GLuint, GLuint) {} - virtual void bindFBO(GLuint) {} + // unbind framebuffer + gl.bindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo); - /// attach additional canvases to the active framebuffer for rendering - /** - * @param[in] canvases List of canvases to attach - **/ - virtual void setAttachments(const std::vector &) {} + return status; +} - /// stop using all additional attached canvases - virtual void setAttachments() {} -}; - -struct FramebufferStrategyCorePacked : public FramebufferStrategy +static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer) { - virtual GLenum createFBO(GLuint &framebuffer, GLuint texture) + 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) { - // get currently bound fbo to reset to it later - GLint current_fbo; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - - // create framebuffer - glGenFramebuffers(1, &framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - - if (texture != 0) - { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, texture, 0); - } - - // check status - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - // unbind framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo); - return status; + glDeleteRenderbuffers(1, &buffer); + buffer = 0; } - virtual bool createStencil(int width, int height, int samples, GLuint &stencil) - { - // create combined depth/stencil buffer - glDeleteRenderbuffers(1, &stencil); - glGenRenderbuffers(1, &stencil); - glBindRenderbuffer(GL_RENDERBUFFER, stencil); - - if (samples > 1) - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_STENCIL, width, height); - else - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height); - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, stencil); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - // check status - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffers(1, &stencil); - stencil = 0; - return false; - } - - return true; - } - - virtual bool 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); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffers(1, &buffer); - buffer = 0; - return false; - } - - return true; - } - - virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer) - { - if (depth_stencil != 0) - glDeleteRenderbuffers(1, &depth_stencil); - if (msaa_buffer != 0) - glDeleteRenderbuffers(1, &msaa_buffer); - if (framebuffer != 0) - glDeleteFramebuffers(1, &framebuffer); - } - - virtual void bindFBO(GLuint framebuffer) - { - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - ++Canvas::switchCount; - } - - virtual void setAttachments() - { - // set a single render target - glDrawBuffer(GL_COLOR_ATTACHMENT0); - } - - virtual void setAttachments(const std::vector &canvases) - { - if (canvases.size() == 0) - { - setAttachments(); - return; - } - - std::vector drawbuffers; - 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, canvases[i]->getGLTexture(), 0); - drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i); - } - - // set up multiple render targets - glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]); - } -}; - -struct FramebufferStrategyCore : public FramebufferStrategyCorePacked -{ - virtual bool createStencil(int width, int height, int samples, GLuint &stencil) - { - // create stencil buffer - glDeleteRenderbuffers(1, &stencil); - glGenRenderbuffers(1, &stencil); - glBindRenderbuffer(GL_RENDERBUFFER, stencil); - - if (samples > 1) - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_STENCIL_INDEX8, width, height); - else - glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height); - - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, stencil); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - // check status - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffers(1, &stencil); - stencil = 0; - return false; - } - - return true; - } -}; - -struct FramebufferStrategyPackedEXT : public FramebufferStrategy -{ - virtual GLenum createFBO(GLuint &framebuffer, GLuint texture) - { - GLint current_fbo; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING_EXT, ¤t_fbo); - - // create framebuffer - glGenFramebuffersEXT(1, &framebuffer); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); - - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_TEXTURE_2D, texture, 0); - - // check status - GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); - - // unbind framebuffer - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, (GLuint) current_fbo); - return status; - } - - virtual bool createStencil(int width, int height, int samples, GLuint &stencil) - { - // create combined depth/stencil buffer - glDeleteRenderbuffersEXT(1, &stencil); - glGenRenderbuffersEXT(1, &stencil); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil); - - if (samples > 1) - { - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, - GL_DEPTH_STENCIL, width, height); - } - else - { - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, - width, height); - } - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, stencil); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); - - // check status - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffersEXT(1, &stencil); - stencil = 0; - return false; - } - - return true; - } - - virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer) - { - if (!GLAD_EXT_framebuffer_multisample) - return false; - - glGenRenderbuffersEXT(1, &buffer); - glBindRenderbufferEXT(GL_RENDERBUFFER, buffer); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, - internalformat, width, height); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_RENDERBUFFER, buffer); - glGetRenderbufferParameterivEXT(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); - - glBindRenderbufferEXT(GL_RENDERBUFFER, 0); - - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffersEXT(1, &buffer); - buffer = 0; - return false; - } - - return true; - } - - virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer) - { - if (depth_stencil != 0) - glDeleteRenderbuffersEXT(1, &depth_stencil); - if (msaa_buffer != 0) - glDeleteRenderbuffersEXT(1, &msaa_buffer); - if (framebuffer != 0) - glDeleteFramebuffersEXT(1, &framebuffer); - } - - virtual void bindFBO(GLuint framebuffer) - { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer); - ++Canvas::switchCount; - } - - virtual void setAttachments() - { - // set a single render target - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); - } - - virtual void setAttachments(const std::vector &canvases) - { - if (canvases.size() == 0) - { - setAttachments(); - return; - } - - std::vector drawbuffers; - drawbuffers.push_back(GL_COLOR_ATTACHMENT0_EXT); - - // Attach the canvas textures to the currently bound framebuffer. - for (int i = 0; i < (int) canvases.size(); i++) - { - glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT + i, - GL_TEXTURE_2D, canvases[i]->getGLTexture(), 0); - drawbuffers.push_back(GL_COLOR_ATTACHMENT1_EXT + i); - } - - // set up multiple render targets - glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]); - } -}; - -struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT -{ - virtual bool createStencil(int width, int height, int samples, GLuint &stencil) - { - // create stencil buffer - glDeleteRenderbuffersEXT(1, &stencil); - glGenRenderbuffersEXT(1, &stencil); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil); - - if (samples > 1) - { - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, - GL_STENCIL_INDEX, width, height); - } - else - { - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX, - width, height); - } - - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, stencil); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); - - // check status - if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - { - glDeleteRenderbuffersEXT(1, &stencil); - stencil = 0; - return false; - } - - return true; - } - - bool isSupported() - { - GLuint fb = 0, stencil = 0; - GLenum status = createFBO(fb, 0); - deleteFBO(fb, stencil, 0); - return status == GL_FRAMEBUFFER_COMPLETE; - } -}; - -FramebufferStrategy *strategy = nullptr; - -FramebufferStrategy strategyNone; -FramebufferStrategyCorePacked strategyCorePacked; -FramebufferStrategyCore strategyCore; -FramebufferStrategyPackedEXT strategyPackedEXT; -FramebufferStrategyEXT strategyEXT; + return status; +} Canvas *Canvas::current = nullptr; OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport(); bool Canvas::screenHasSRGB = false; -int Canvas::switchCount = 0; int Canvas::canvasCount = 0; -static void getStrategy() -{ - if (!strategy) - { - if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) - strategy = &strategyCorePacked; - else if (GLAD_ES_VERSION_2_0) - strategy = &strategyCore; - else if (GLAD_EXT_framebuffer_object && GLAD_EXT_packed_depth_stencil) - strategy = &strategyPackedEXT; - else if (GLAD_EXT_framebuffer_object && strategyEXT.isSupported()) - strategy = &strategyEXT; - else - strategy = &strategyNone; - } -} - Canvas::Canvas(int width, int height, Format format, int msaa) : fbo(0) , resolve_fbo(0) @@ -459,7 +90,6 @@ Canvas::Canvas(int width, int height, Format format, int msaa) , format(format) , requested_samples(msaa) , actual_samples(0) - , msaa_dirty(false) , texture_memory(0) { this->width = width; @@ -488,8 +118,6 @@ Canvas::Canvas(int width, int height, Format format, int msaa) vertices[3].s = 1; vertices[3].t = 0; - getStrategy(); - loadVolatile(); ++canvasCount; @@ -517,7 +145,7 @@ bool Canvas::createMSAAFBO(GLenum internalformat) } // Create our FBO without a texture. - status = strategy->createFBO(fbo, 0); + status = createFBO(fbo, 0); GLuint previous = gl.getDefaultFBO(); if (current != this) @@ -525,30 +153,28 @@ bool Canvas::createMSAAFBO(GLenum internalformat) if (current != nullptr) previous = current->fbo; - strategy->bindFBO(fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); } // Create and attach the MSAA buffer for our FBO. - if (strategy->createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer)) - status = GL_FRAMEBUFFER_COMPLETE; - else - status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + 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 = strategy->createFBO(resolve_fbo, texture); + status = createFBO(resolve_fbo, texture); if (status != GL_FRAMEBUFFER_COMPLETE) { // Clean up. - strategy->deleteFBO(fbo, 0, msaa_buffer); - strategy->deleteFBO(resolve_fbo, 0, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteFramebuffers(1, &resolve_fbo); + glDeleteRenderbuffers(1, &msaa_buffer); fbo = msaa_buffer = resolve_fbo = 0; actual_samples = 0; } if (current != this) - strategy->bindFBO(previous); + gl.bindFramebuffer(GL_FRAMEBUFFER, previous); return status == GL_FRAMEBUFFER_COMPLETE; } @@ -566,6 +192,8 @@ bool Canvas::loadVolatile() 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); @@ -606,13 +234,13 @@ bool Canvas::loadVolatile() // Try to create a MSAA FBO if requested. On failure (or no requested MSAA), // fall back to a regular FBO. if (!createMSAAFBO(internalformat)) - status = strategy->createFBO(fbo, texture); + status = createFBO(fbo, texture); if (status != GL_FRAMEBUFFER_COMPLETE) { if (fbo != 0) { - strategy->deleteFBO(fbo, 0, 0); + glDeleteFramebuffers(1, &fbo); fbo = 0; } return false; @@ -620,8 +248,6 @@ bool Canvas::loadVolatile() clear(Color(0, 0, 0, 0)); - msaa_dirty = (msaa_buffer != 0); - size_t prevmemsize = texture_memory; texture_memory = (getFormatBitsPerPixel(format) * width * height) / 8; @@ -635,13 +261,19 @@ bool Canvas::loadVolatile() void Canvas::unloadVolatile() { - strategy->deleteFBO(fbo, depth_stencil, msaa_buffer); - strategy->deleteFBO(resolve_fbo, 0, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteFramebuffers(1, &resolve_fbo); + + glDeleteRenderbuffers(1, &depth_stencil); + glDeleteRenderbuffers(1, &msaa_buffer); gl.deleteTexture(texture); - fbo = depth_stencil = texture = 0; - resolve_fbo = msaa_buffer = 0; + fbo = 0; + resolve_fbo = 0; + depth_stencil = 0; + msaa_buffer = 0; + texture = 0; attachedCanvases.clear(); @@ -725,10 +357,6 @@ GLuint Canvas::getGLTexture() const void Canvas::predraw() { - // We need to make sure the texture is up-to-date by resolving the MSAA - // buffer (which we render to when the canvas is active) to it. - resolveMSAA(); - gl.bindTexture(texture); } @@ -751,7 +379,7 @@ void Canvas::setupGrab() current = this; // bind the framebuffer object. - strategy->bindFBO(fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); gl.setViewport(OpenGL::Viewport(0, 0, width, height)); // Set up the projection matrix @@ -766,9 +394,6 @@ void Canvas::setupGrab() glDisable(GL_FRAMEBUFFER_SRGB); } - if (msaa_buffer != 0) - msaa_dirty = true; - // We discard the stencil buffer in stopGrab, so we should clear it here // to prevent the discarded (invalid) contents from being accidentally used. if (depth_stencil != 0 && (GLAD_ES_VERSION_3_0 || GLAD_EXT_discard_framebuffer)) @@ -815,7 +440,22 @@ void Canvas::startGrab(const std::vector &canvases) return; // Attach the canvas textures to the active FBO and set up MRTs. - strategy->setAttachments(canvases); + std::vector 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, canvases[i]->getGLTexture(), 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. @@ -830,8 +470,8 @@ void Canvas::startGrab() if (attachedCanvases.size() == 0) return; - // make sure the FBO is only using a single canvas - strategy->setAttachments(); + // make sure the FBO is only using a single draw buffer + glDrawBuffer(GL_COLOR_ATTACHMENT0); attachedCanvases.clear(); } @@ -853,6 +493,9 @@ void Canvas::stopGrab(bool switchingToOtherCanvas) glDiscardFramebufferEXT(GL_FRAMEBUFFER, 2, attachments); } + // Make sure the canvas texture is up to date if we're using MSAA. + resolveMSAA(false); + gl.matrices.projection.pop_back(); if (switchingToOtherCanvas) @@ -866,7 +509,7 @@ void Canvas::stopGrab(bool switchingToOtherCanvas) else { // bind system framebuffer. - strategy->bindFBO(gl.getDefaultFBO()); + gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); current = nullptr; gl.setViewport(systemViewport); @@ -882,9 +525,6 @@ void Canvas::stopGrab(bool switchingToOtherCanvas) void Canvas::clear(Color c) { - if (strategy == &strategyNone) - return; - GLuint previous = gl.getDefaultFBO(); if (current != this) @@ -892,7 +532,7 @@ void Canvas::clear(Color c) if (current != nullptr) previous = current->fbo; - strategy->bindFBO(fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); } GLfloat glcolor[] = {c.r/255.f, c.g/255.f, c.b/255.f, c.a/255.f}; @@ -911,28 +551,40 @@ void Canvas::clear(Color c) } else { - // glClear will clear all active draw buffers, so we need to temporarily - // detach any other canvases (when MRT is being used.) + // glClear will clear all active draw buffers, so should make sure only + // the draw buffer corresponding to our canvas' texture is active. if (attachedCanvases.size() > 0) - strategy->setAttachments(); + glDrawBuffer(GL_COLOR_ATTACHMENT0); // Don't use the state-shadowed gl.setClearColor because we want to save // the previous clear color. glClearColor(glcolor[0], glcolor[1], glcolor[2], glcolor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Restore the draw buffers for rendering to multiple textures. if (attachedCanvases.size() > 0) - strategy->setAttachments(attachedCanvases); + { + std::vector drawbuffers; + drawbuffers.reserve(attachedCanvases.size() + 1); + + for (int i = 0; i < (int) attachedCanvases.size() + 1; i++) + drawbuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + + glDrawBuffers((GLsizei) drawbuffers.size(), &drawbuffers[0]); + } // Restore the global clear color. gl.setClearColor(gl.getClearColor()); } if (current != this) - strategy->bindFBO(previous); + { + // If we're not the active canvas, we need to do a MSAA resolve after + // clearing the FBO since the resolve texture might be used afterward. + resolveMSAA(false); - if (msaa_buffer != 0) - msaa_dirty = true; + gl.bindFramebuffer(GL_FRAMEBUFFER, previous); + } } bool Canvas::checkCreateStencil() @@ -942,20 +594,47 @@ bool Canvas::checkCreateStencil() return true; if (current != this) - strategy->bindFBO(fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); - bool success = strategy->createStencil(width, height, requested_samples, depth_stencil); + GLenum format = GL_STENCIL_INDEX8; + GLenum attachment = GL_STENCIL_ATTACHMENT; - if (success) + // 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) { - // We don't want the stencil buffer filled with garbage. + 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); + else + { + glDeleteRenderbuffers(1, &depth_stencil); + depth_stencil = 0; } if (current && current != this) - strategy->bindFBO(current->fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, current->fbo); else if (!current) - strategy->bindFBO(gl.getDefaultFBO()); + gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); return success; } @@ -965,8 +644,6 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *image, int x, i if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > width || (y + h) > height) throw love::Exception("Invalid ImageData rectangle dimensions."); - resolveMSAA(); - int row = 4 * w; int size = row * h; GLubyte *pixels = nullptr; @@ -979,68 +656,50 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *image, int x, i 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 && (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)) - glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo); - else if (resolve_fbo != 0 && GLAD_EXT_framebuffer_multisample) - glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo); + if (resolve_fbo != 0) + gl.bindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo); else - strategy->bindFBO(fbo); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - if (current) - strategy->bindFBO(current->fbo); - else - strategy->bindFBO(gl.getDefaultFBO()); + 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 Canvas::resolveMSAA(bool restoreprev) { if (resolve_fbo == 0 || msaa_buffer == 0) return false; - if (!msaa_dirty) - return true; - - GLuint previous = gl.getDefaultFBO(); - if (current != nullptr) - previous = current->fbo; + GLint w = width; + GLint h = height; // Do the MSAA resolve by blitting the MSAA renderbuffer to the texture. - // For many of the extensions that add suffixes to the functions, we assign - // function pointers in OpenGL.cpp so we can call the core functions. - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object - || GLAD_ANGLE_framebuffer_multisample) - { - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo); - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - else if (GLAD_EXT_framebuffer_multisample && GLAD_EXT_framebuffer_blit) - { - glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fbo); - glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, resolve_fbo); - glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - else if (GLAD_APPLE_framebuffer_multisample) - { - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo); + // 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 - return false; + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); - strategy->bindFBO(previous); - - if (current != this) - msaa_dirty = false; + if (restoreprev) + { + GLuint fbo = current ? current->fbo : gl.getDefaultFBO(); + gl.bindFramebuffer(GL_FRAMEBUFFER, fbo); + } return true; } @@ -1141,8 +800,7 @@ size_t Canvas::getFormatBitsPerPixel(Format format) bool Canvas::isSupported() { - getStrategy(); - return (strategy != &strategyNone); + return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object; } bool Canvas::isMultiCanvasSupported() @@ -1237,8 +895,8 @@ bool Canvas::isFormatSupported(Canvas::Format format) glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr); GLuint fbo = 0; - supported = (strategy->createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE); - strategy->deleteFBO(fbo, 0, 0); + supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE); + glDeleteFramebuffers(1, &fbo); gl.deleteTexture(texture); diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index 44fabd0e6..1f79210a4 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -113,8 +113,6 @@ public: return actual_samples; } - bool resolveMSAA(); - static bool isSupported(); static bool isMultiCanvasSupported(); static bool isFormatSupported(Format format); @@ -127,7 +125,6 @@ public: // Whether the main screen should have linear -> sRGB conversions enabled. static bool screenHasSRGB; - static int switchCount; static int canvasCount; static bool getConstant(const char *in, Format &out); @@ -136,6 +133,7 @@ public: private: bool createMSAAFBO(GLenum internalformat); + bool resolveMSAA(bool restoreprev); static Format getSizedFormat(Format format); static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type); @@ -156,7 +154,6 @@ private: int requested_samples; int actual_samples; - bool msaa_dirty; size_t texture_memory; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index d978d5ea9..84c5fec91 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -471,7 +471,7 @@ void Graphics::present() // Reset the per-frame stat counts. gl.stats.drawCalls = 0; - Canvas::switchCount = 0; + gl.stats.framebufferBinds = 0; } int Graphics::getWidth() const @@ -1267,7 +1267,7 @@ Graphics::Stats Graphics::getStats() const Stats stats; stats.drawCalls = gl.stats.drawCalls; - stats.canvasSwitches = Canvas::switchCount; + stats.canvasSwitches = gl.stats.framebufferBinds; stats.canvases = Canvas::canvasCount; stats.images = Image::imageCount; stats.fonts = Font::fontCount; diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 91c20c042..d097224e2 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -196,17 +196,42 @@ void OpenGL::initVendor() void OpenGL::initOpenGLFunctions() { + // Alias extension-suffixed framebuffer functions to core versions since + // there are so many different-named extensions that do the same things... if (!(GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)) { - // There are like 100 different suffixed versions of glRenderbufferStorageMultisample, - // so we'll just assign all the ones we use to the core function pointer. - if (GLAD_APPLE_framebuffer_multisample) - fp_glRenderbufferStorageMultisample = (pfn_glRenderbufferStorageMultisample) fp_glRenderbufferStorageMultisampleAPPLE; - else if (GLAD_ANGLE_framebuffer_multisample) - fp_glRenderbufferStorageMultisample = (pfn_glRenderbufferStorageMultisample) fp_glRenderbufferStorageMultisampleANGLE; + if (GLAD_VERSION_1_0 && GLAD_EXT_framebuffer_object) + { + fp_glBindRenderbuffer = fp_glBindRenderbufferEXT; + fp_glDeleteRenderbuffers = fp_glDeleteRenderbuffersEXT; + fp_glGenRenderbuffers = fp_glGenRenderbuffersEXT; + fp_glRenderbufferStorage = fp_glRenderbufferStorageEXT; + fp_glGetRenderbufferParameteriv = fp_glGetRenderbufferParameterivEXT; + fp_glBindFramebuffer = fp_glBindFramebufferEXT; + fp_glDeleteFramebuffers = fp_glDeleteFramebuffersEXT; + fp_glGenFramebuffers = fp_glGenFramebuffersEXT; + fp_glCheckFramebufferStatus = fp_glCheckFramebufferStatusEXT; + fp_glFramebufferTexture2D = fp_glFramebufferTexture2DEXT; + fp_glFramebufferRenderbuffer = fp_glFramebufferRenderbufferEXT; + fp_glGetFramebufferAttachmentParameteriv = fp_glGetFramebufferAttachmentParameterivEXT; + fp_glGenerateMipmap = fp_glGenerateMipmapEXT; + } - if (GLAD_ANGLE_framebuffer_blit) - fp_glBlitFramebuffer = (pfn_glBlitFramebuffer) fp_glBlitFramebufferANGLE; + if (GLAD_EXT_framebuffer_blit) + fp_glBlitFramebuffer = fp_glBlitFramebufferEXT; + else if (GLAD_ANGLE_framebuffer_blit) + fp_glBlitFramebuffer = fp_glBlitFramebufferANGLE; + else if (GLAD_NV_framebuffer_blit) + fp_glBlitFramebuffer = fp_glBlitFramebufferNV; + + if (GLAD_EXT_framebuffer_multisample) + fp_glRenderbufferStorageMultisample = fp_glRenderbufferStorageMultisampleEXT; + else if (GLAD_APPLE_framebuffer_multisample) + fp_glRenderbufferStorageMultisample = fp_glRenderbufferStorageMultisampleAPPLE; + else if (GLAD_ANGLE_framebuffer_multisample) + fp_glRenderbufferStorageMultisample = fp_glRenderbufferStorageMultisampleANGLE; + else if (GLAD_NV_framebuffer_multisample) + fp_glRenderbufferStorageMultisample = fp_glRenderbufferStorageMultisampleNV; } } @@ -297,17 +322,6 @@ void OpenGL::prepareDraw() // Make sure the active shader has the correct values for its // love-provided uniforms. shader->checkSetScreenParams(); - - // We need to make sure antialiased Canvases are properly resolved - // before sampling from their textures in a shader. - // This is kind of a big hack. :( - for (auto &r : shader->getBoundRetainables()) - { - // Even bigger hack! D: - Canvas *canvas = dynamic_cast(r.second); - if (canvas != nullptr) - canvas->resolveMSAA(); - } } const Matrix &curproj = matrices.projection.back(); @@ -445,6 +459,12 @@ float OpenGL::getPointSize() const return state.pointSize; } +void OpenGL::bindFramebuffer(GLenum target, GLuint framebuffer) +{ + glBindFramebuffer(target, framebuffer); + ++stats.framebufferBinds; +} + GLuint OpenGL::getDefaultFBO() const { #ifdef LOVE_IOS diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index b432f9d6b..f6aec3dfd 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -148,6 +148,7 @@ public: { size_t textureMemory; int drawCalls; + int framebufferBinds; } stats; OpenGL(); @@ -250,6 +251,11 @@ public: **/ float getPointSize() const; + /** + * Binds a Framebuffer Object to the specified target. + **/ + void bindFramebuffer(GLenum target, GLuint framebuffer); + /** * This will usually be 0 (system drawable), but some platforms require a * non-zero FBO for rendering.