From 7934ce0a66727e5ffe324a9c65e682c9b50a5617 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 25 Oct 2020 22:24:07 -0300 Subject: [PATCH] Handle backbuffer MSAA manually in love.graphics code Previously love would use SDL APIs to make the system (WGL etc) handle it, which meant the OpenGL context had to be recreated whenever MSAA changed. --- src/modules/graphics/Graphics.h | 5 +- src/modules/graphics/opengl/Graphics.cpp | 169 ++++++++++++++++++----- src/modules/graphics/opengl/Graphics.h | 16 ++- src/modules/window/sdl/Window.cpp | 57 +++----- src/modules/window/sdl/Window.h | 4 +- 5 files changed, 175 insertions(+), 76 deletions(-) diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index d26ba929a..49a540aa5 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -471,7 +471,7 @@ public: * @param width The viewport width. * @param height The viewport height. **/ - virtual bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) = 0; + virtual bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) = 0; /** * Un-sets the current graphics display mode (uninitializing objects if @@ -506,6 +506,9 @@ public: double getCurrentDPIScale() const; double getScreenDPIScale() const; + virtual int getRequestedBackbufferMSAA() const = 0; + virtual int getBackbufferMSAA() const = 0; + /** * Sets the current constant color. **/ diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 8f6bfffb1..30bfa9ac5 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -91,6 +91,8 @@ static GLenum getGLBlendFactor(BlendFactor factor) Graphics::Graphics() : windowHasStencil(false) , mainVAO(0) + , internalBackbufferFBO(0) + , requestedBackbufferMSAA(0) , defaultBuffers() , supportedFormats() { @@ -105,14 +107,14 @@ Graphics::Graphics() if (window->isOpen()) { int w, h; - love::window::WindowSettings settings; - window->getWindow(w, h, settings); + love::window::WindowSettings s; + window->getWindow(w, h, s); double dpiW = w; double dpiH = h; window->windowToDPICoords(&dpiW, &dpiH); - setMode((int) dpiW, (int) dpiH, window->getPixelWidth(), window->getPixelHeight(), settings.stencil); + setMode((int) dpiW, (int) dpiH, window->getPixelWidth(), window->getPixelHeight(), s.stencil, s.msaa); } } } @@ -171,14 +173,94 @@ void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelh // Set up the projection matrix projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0, -10.0f, 10.0f); } + + updateBackbuffer(width, height, pixelwidth, pixelheight, requestedBackbufferMSAA); } -bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) +void Graphics::updateBackbuffer(int width, int height, int /*pixelwidth*/, int pixelheight, int msaa) +{ + bool useinternalbackbuffer = false; + if (msaa > 1) + useinternalbackbuffer = true; + + // Our internal backbuffer code needs glBlitFramebuffer. + if (!(GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_ES_VERSION_3_0 + || GLAD_EXT_framebuffer_blit || GLAD_ANGLE_framebuffer_blit || GLAD_NV_framebuffer_blit)) + { + if (!(msaa > 1 && GLAD_APPLE_framebuffer_multisample)) + useinternalbackbuffer = false; + } + + GLuint prevFBO = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL); + bool restoreFBO = prevFBO != getInternalBackbufferFBO(); + + if (useinternalbackbuffer) + { + Texture::Settings settings; + settings.width = width; + settings.height = height; + settings.dpiScale = (float)pixelheight / (float)height; + settings.msaa = msaa; + settings.renderTarget = true; + settings.readable.set(false); + + settings.format = isGammaCorrect() ? PIXELFORMAT_sRGBA8_UNORM : PIXELFORMAT_RGBA8_UNORM; + internalBackbuffer.set(newTexture(settings), Acquire::NORETAIN); + + settings.format = PIXELFORMAT_DEPTH24_UNORM_STENCIL8; + internalBackbufferDepthStencil.set(newTexture(settings), Acquire::NORETAIN); + + RenderTargets rts; + rts.colors.push_back(internalBackbuffer.get()); + rts.depthStencil.texture = internalBackbufferDepthStencil; + + internalBackbufferFBO = bindCachedFBO(rts); + } + else + { + internalBackbuffer.set(nullptr); + internalBackbufferDepthStencil.set(nullptr); + internalBackbufferFBO = 0; + } + + requestedBackbufferMSAA = msaa; + + if (restoreFBO) + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, prevFBO); +} + +GLuint Graphics::getInternalBackbufferFBO() const +{ + if (internalBackbufferFBO != 0) + return internalBackbufferFBO; + else + return getSystemBackbufferFBO(); +} + +GLuint Graphics::getSystemBackbufferFBO() const +{ +#ifdef LOVE_IOS + // Hack: iOS uses a custom FBO. + SDL_SysWMinfo info = {}; + SDL_VERSION(&info.version); + SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); + + if (info.info.uikit.resolveFramebuffer != 0) + return info.info.uikit.resolveFramebuffer; + else + return info.info.uikit.framebuffer; +#else + return 0; +#endif +} + +bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) { this->width = width; this->height = height; this->windowHasStencil = windowhasstencil; + this->requestedBackbufferMSAA = msaa; // Okay, setup OpenGL. gl.initContext(); @@ -194,8 +276,6 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b created = true; initCapabilities(); - setViewportSize(width, height, pixelwidth, pixelheight); - // Enable blending gl.setEnableState(OpenGL::ENABLE_BLEND, true); @@ -235,6 +315,8 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b setDebug(isDebugEnabled()); + setViewportSize(width, height, pixelwidth, pixelheight); + if (batchedDrawState.vb[0] == nullptr) { // Initial sizes that should be good enough for most cases. It will @@ -318,6 +400,9 @@ void Graphics::unSetMode() flushBatchedDraws(); + internalBackbuffer.set(nullptr); + internalBackbufferDepthStencil.set(nullptr); + // Unload all volatile objects. These must be reloaded after the display // mode change. Volatile::unloadAll(); @@ -536,7 +621,7 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h, if (iswindow) { - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getInternalBackbufferFBO()); // The projection matrix is flipped compared to rendering to a texture, // due to OpenGL considering (0,0) bottom-left instead of top-left. @@ -578,6 +663,8 @@ void Graphics::endPass() // Discard the depth/stencil buffer if we're using an internal cached one. if (depthstencil == nullptr && (rts.temporaryRTFlags & (TEMPORARY_RT_DEPTH | TEMPORARY_RT_STENCIL)) != 0) discard({}, true); + else if (!rts.getFirstTarget().texture.get()) + discard({}, true); // Backbuffer // Resolve MSAA buffers. MSAA is only supported for 2D render targets so we // don't have to worry about resolving to slices. @@ -796,7 +883,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (!isRenderTargetActive() && gl.getDefaultFBO() == 0) + if (!isRenderTargetActive() && getInternalBackbufferFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); @@ -863,7 +950,7 @@ void Graphics::cleanupRenderTexture(love::graphics::Texture *texture) } } -void Graphics::bindCachedFBO(const RenderTargets &targets) +GLuint Graphics::bindCachedFBO(const RenderTargets &targets) { GLuint fbo = framebufferObjects[targets]; @@ -946,6 +1033,8 @@ void Graphics::bindCachedFBO(const RenderTargets &targets) framebufferObjects[targets] = fbo; } + + return fbo; } void Graphics::present(void *screenshotCallbackData) @@ -961,13 +1050,34 @@ void Graphics::present(void *screenshotCallbackData) flushBatchedDraws(); endPass(); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + int w = getPixelWidth(); + int h = getPixelHeight(); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getInternalBackbufferFBO()); + + // Copy internal backbuffer to system backbuffer. When MSAA is used this + // is a direct MSAA resolve. + if (internalBackbuffer.get()) + { + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, getSystemBackbufferFBO()); + + // Discard system backbuffer to prevent it from copying its contents + // from VRAM to chip memory. + discard(OpenGL::FRAMEBUFFER_DRAW, {true}, true); + + // updateBackbuffer checks for glBlitFramebuffer support. + if (GLAD_APPLE_framebuffer_multisample && internalBackbuffer->getMSAA() > 1) + glResolveMultisampleFramebufferAPPLE(); + else + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Discarding the internal backbuffer directly after resolving it should + // eliminate any copy back to vram it might need to do. + discard(OpenGL::FRAMEBUFFER_READ, {true}, false); + } if (!pendingScreenshotCallbacks.empty()) { - int w = getPixelWidth(); - int h = getPixelHeight(); - size_t row = 4 * w; size_t size = row * h; @@ -986,26 +1096,7 @@ void Graphics::present(void *screenshotCallbackData) throw love::Exception("Out of memory."); } -#ifdef LOVE_IOS - SDL_SysWMinfo info = {}; - SDL_VERSION(&info.version); - SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); - - if (info.info.uikit.resolveFramebuffer != 0) - { - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer); - - // We need to do an explicit MSAA resolve on iOS, because it uses - // GLES FBOs rather than a system framebuffer. - if (GLAD_ES_VERSION_3_0) - glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); - else if (GLAD_APPLE_framebuffer_multisample) - glResolveMultisampleFramebufferAPPLE(); - - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, info.info.uikit.resolveFramebuffer); - } -#endif - + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, getSystemBackbufferFBO()); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Replace alpha values with full opacity. @@ -1069,6 +1160,8 @@ void Graphics::present(void *screenshotCallbackData) if (window != nullptr) window->swapBuffers(); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getInternalBackbufferFBO()); + // Reset the per-frame stat counts. drawCalls = 0; gl.stats.shaderSwitches = 0; @@ -1089,6 +1182,16 @@ void Graphics::present(void *screenshotCallbackData) } } +int Graphics::getRequestedBackbufferMSAA() const +{ + return requestedBackbufferMSAA; +} + +int Graphics::getBackbufferMSAA() const +{ + return internalBackbuffer.get() ? internalBackbuffer->getMSAA() : 0; +} + void Graphics::setScissor(const Rect &rect) { flushBatchedDraws(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 62b0ae71f..40473327c 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -63,7 +63,7 @@ public: love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) override; void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override; - bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override; + bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override; void unSetMode() override; void setActive(bool active) override; @@ -79,6 +79,9 @@ public: void present(void *screenshotCallbackData) override; + int getRequestedBackbufferMSAA() const override; + int getBackbufferMSAA() const override; + void setColor(Colorf c) override; void setScissor(const Rect &rect) override; @@ -138,15 +141,24 @@ private: void getAPIStats(int &shaderswitches) const override; void endPass(); - void bindCachedFBO(const RenderTargets &targets); + GLuint bindCachedFBO(const RenderTargets &targets); void discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil); + void updateBackbuffer(int width, int height, int pixelwidth, int pixelheight, int msaa); + GLuint getInternalBackbufferFBO() const; + GLuint getSystemBackbufferFBO() const; + void setDebug(bool enable); std::unordered_map framebufferObjects; bool windowHasStencil; GLuint mainVAO; + StrongRef internalBackbuffer; + StrongRef internalBackbufferDepthStencil; + GLuint internalBackbufferFBO; + int requestedBackbufferMSAA; + // Only needed for buffer types that can be bound to shaders. StrongRef defaultBuffers[BUFFERTYPE_MAX_ENUM]; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 1f77ff641..f92241c7d 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -93,7 +93,7 @@ void Window::setGraphics(graphics::Graphics *graphics) this->graphics.set(graphics); } -void Window::setGLFramebufferAttributes(int msaa, bool sRGB, bool stencil, int depth) +void Window::setGLFramebufferAttributes(bool sRGB) { // Set GL window / framebuffer attributes. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -101,12 +101,18 @@ void Window::setGLFramebufferAttributes(int msaa, bool sRGB, bool stencil, int d SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencil ? 8 : 0); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depth); SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 0); + // Always use 24/8 depth/stencil (make sure any Graphics implementations + // that have their own backbuffer match this, too). + // Changing this after initial window creation would need the context to be + // destroyed and recreated, which we really don't want. + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + // Backbuffer MSAA is handled by the love.graphics implementation. + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0); @@ -288,7 +294,7 @@ std::vector Window::getContextAttribsList() const return attribslist; } -bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth) +bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags) { std::vector attribslist = getContextAttribsList(); @@ -350,10 +356,9 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla // Try each context profile in order. for (ContextAttribs attribs : attribslist) { - int curMSAA = msaa; bool curSRGB = love::graphics::isGammaCorrect(); - setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth); + setGLFramebufferAttributes(curSRGB); setGLContextAttributes(attribs); windowerror.clear(); @@ -361,33 +366,14 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla create(attribs); - if (!window && curMSAA > 0) - { - // The MSAA setting could have caused the failure. - setGLFramebufferAttributes(0, curSRGB, stencil, depth); - if (create(attribs)) - curMSAA = 0; - } - if (!window && curSRGB) { - // same with sRGB. - setGLFramebufferAttributes(curMSAA, false, stencil, depth); + // The sRGB setting could have caused the failure. + setGLFramebufferAttributes(false); if (create(attribs)) curSRGB = false; } - if (!window && curMSAA > 0 && curSRGB) - { - // Or both! - setGLFramebufferAttributes(0, false, stencil, depth); - if (create(attribs)) - { - curMSAA = 0; - curSRGB = false; - } - } - if (window && context) { // Store the successful context attributes so we can re-use them in @@ -515,7 +501,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) close(); - if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.stencil, f.depth)) + if (!createWindowAndContext(x, y, width, height, sdlflags)) return false; // Make sure the window keeps any previously set icon. @@ -540,7 +526,9 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) { double scaledw, scaledh; fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh); - graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil); + graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil, f.msaa); + + this->settings.msaa = graphics->getBackbufferMSAA(); } #ifdef LOVE_ANDROID @@ -617,13 +605,6 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi else SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); - // Verify MSAA setting. - int buffers = 0; - int samples = 0; - SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers); - SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples); - - settings.msaa = (buffers > 0 ? samples : 0); settings.vsync = getVSync(); settings.stencil = newsettings.stencil; diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index e48a8bca8..7a56de991 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -139,11 +139,11 @@ private: bool debug; }; - void setGLFramebufferAttributes(int msaa, bool sRGB, bool stencil, int depth); + void setGLFramebufferAttributes(bool sRGB); void setGLContextAttributes(const ContextAttribs &attribs); bool checkGLVersion(const ContextAttribs &attribs, std::string &outversion); std::vector getContextAttribsList() const; - bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth); + bool createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags); // Update the saved window settings based on the window's actual state. void updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport);