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);