diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index f8f1dec059bcb..418e883d1b8ff 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -410,10 +410,12 @@ anisotropic_filter (Anisotropic filtering) bool false # * None - No antialiasing (default) # # * FSAA - Hardware-provided full-screen antialiasing -# (incompatible with Post Processing and Undersampling) # A.K.A multi-sample antialiasing (MSAA) # Smoothens out block edges but does not affect the insides of textures. -# A restart is required to change this option. +# +# If Post Processing is disabled, changing FSAA requires a restart. +# Also, if Post Processing is disabled, FSAA will not work together with +# undersampling or a non-default "3d_mode" setting. # # * FXAA - Fast approximate antialiasing # Applies a post-processing filter to detect and smoothen high-contrast edges. diff --git a/irr/include/EDriverFeatures.h b/irr/include/EDriverFeatures.h index bde2c7698707c..0a35161cf2890 100644 --- a/irr/include/EDriverFeatures.h +++ b/irr/include/EDriverFeatures.h @@ -129,6 +129,9 @@ enum E_VIDEO_DRIVER_FEATURE //! Support for clamping vertices beyond far-plane to depth instead of capping them. EVDF_DEPTH_CLAMP, + //! Support for multisample textures. + EVDF_TEXTURE_MULTISAMPLE, + //! Only used for counting the elements of this enum EVDF_COUNT }; diff --git a/irr/include/IRenderTarget.h b/irr/include/IRenderTarget.h index 1e70c1e06601f..85b9738dc3218 100644 --- a/irr/include/IRenderTarget.h +++ b/irr/include/IRenderTarget.h @@ -26,6 +26,7 @@ enum E_CUBE_SURFACE }; //! Interface of a Render Target. +/** This is a framebuffer object (FBO) in OpenGL. */ class IRenderTarget : public virtual IReferenceCounted { public: diff --git a/irr/include/ITexture.h b/irr/include/ITexture.h index 8760c9f2a8d04..869a325e0c6a1 100644 --- a/irr/include/ITexture.h +++ b/irr/include/ITexture.h @@ -156,6 +156,9 @@ enum E_TEXTURE_TYPE //! 2D texture. ETT_2D, + //! 2D texture with multisampling. + ETT_2D_MS, + //! Cubemap texture. ETT_CUBEMAP }; diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 035f5ad77141c..3d4deace525f0 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -273,6 +273,14 @@ class IVideoDriver : public virtual IReferenceCounted virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) = 0; + //! Adds a multisampled render target texture to the texture cache. + /** \param msaa The number of samples to use, values that make sense are > 1. + Only works if the driver supports the EVDF_TEXTURE_MULTISAMPLE feature, + check via queryFeature. + \see addRenderTargetTexture */ + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) = 0; + //! Adds a new render target texture with 6 sides for a cubemap map to the texture cache. /** \param sideLen Length of one cubemap side. \param name A name for the texture. Later calls of getTexture() with this name will return this texture. @@ -358,6 +366,10 @@ class IVideoDriver : public virtual IReferenceCounted //! Remove all render targets. virtual void removeAllRenderTargets() = 0; + //! Blit contents of one render target to another one. + /** This is glBlitFramebuffer in OpenGL. */ + virtual void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) = 0; + //! Sets a boolean alpha channel on the texture based on a color key. /** This makes the texture fully transparent at the texels where this color key can be found when using for example draw2DImage diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index acce8383bfc9f..b5da7ef296139 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -1678,6 +1678,12 @@ ITexture *CNullDriver::addRenderTargetTexture(const core::dimension2d &size return 0; } +ITexture *CNullDriver::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) +{ + return 0; +} + ITexture *CNullDriver::addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) { diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index b8d45118fc369..5752b53cddd1e 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -227,6 +227,10 @@ class CNullDriver : public IVideoDriver, public IGPUProgrammingServices virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a multisampled render target texture. + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; @@ -410,6 +414,8 @@ class CNullDriver : public IVideoDriver, public IGPUProgrammingServices //! Create render target. IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override {} + //! Remove render target. void removeRenderTarget(IRenderTarget *renderTarget) override; diff --git a/irr/src/COpenGLCoreCacheHandler.h b/irr/src/COpenGLCoreCacheHandler.h index a1277bfeda38e..7445116297bca 100644 --- a/irr/src/COpenGLCoreCacheHandler.h +++ b/irr/src/COpenGLCoreCacheHandler.h @@ -85,13 +85,19 @@ class COpenGLCoreCacheHandler GL.BindTexture(prevTextureType, 0); #if defined(IRR_COMPILE_GL_COMMON) - GL.Disable(prevTextureType); - GL.Enable(curTextureType); + // The "enable/disable texture" stuff is so legacy that + // it's not even allowed for multisample textures. + // (IRR_COMPILE_GL_COMMON is for the legacy driver.) + if (prevTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Disable(prevTextureType); + if (curTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Enable(curTextureType); #endif } #if defined(IRR_COMPILE_GL_COMMON) else if (!prevTexture) - GL.Enable(curTextureType); + if (curTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Enable(curTextureType); #endif GL.BindTexture(curTextureType, static_cast(texture)->getOpenGLTextureName()); @@ -110,7 +116,8 @@ class COpenGLCoreCacheHandler GL.BindTexture(prevTextureType, 0); #if defined(IRR_COMPILE_GL_COMMON) - GL.Disable(prevTextureType); + if (prevTextureType != GL_TEXTURE_2D_MULTISAMPLE) + GL.Disable(prevTextureType); #endif } diff --git a/irr/src/COpenGLCoreRenderTarget.h b/irr/src/COpenGLCoreRenderTarget.h index 6bfc98cc0c142..a174e926e7bb8 100644 --- a/irr/src/COpenGLCoreRenderTarget.h +++ b/irr/src/COpenGLCoreRenderTarget.h @@ -5,6 +5,7 @@ #pragma once #include "IRenderTarget.h" +#include #ifndef GL_FRAMEBUFFER_INCOMPLETE_FORMATS #define GL_FRAMEBUFFER_INCOMPLETE_FORMATS GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT @@ -122,7 +123,7 @@ class COpenGLCoreRenderTarget : public IRenderTarget TOpenGLTexture *currentTexture = (depthStencil && depthStencil->getDriverType() == DriverType) ? static_cast(depthStencil) : 0; if (currentTexture) { - if (currentTexture->getType() == ETT_2D) { + if (currentTexture->getType() == ETT_2D || currentTexture->getType() == ETT_2D_MS) { GLuint textureID = currentTexture->getOpenGLTextureName(); const ECOLOR_FORMAT textureFormat = (textureID != 0) ? depthStencil->getColorFormat() : ECF_UNKNOWN; @@ -172,7 +173,20 @@ class COpenGLCoreRenderTarget : public IRenderTarget if (textureID != 0) { AssignedTextures[i] = GL_COLOR_ATTACHMENT0 + i; - GLenum textarget = currentTexture->getType() == ETT_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int)CubeSurfaces[i]; + GLenum textarget; + switch (currentTexture->getType()) { + case ETT_2D: + textarget = GL_TEXTURE_2D; + break; + case ETT_2D_MS: + textarget = GL_TEXTURE_2D_MULTISAMPLE; + break; + case ETT_CUBEMAP: + textarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int)CubeSurfaces[i]; + break; + default: + throw std::logic_error("not reachable"); + } Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, AssignedTextures[i], textarget, textureID, 0); TEST_GL_ERROR(Driver); } else if (AssignedTextures[i] != GL_NONE) { @@ -198,36 +212,50 @@ class COpenGLCoreRenderTarget : public IRenderTarget // Set depth and stencil attachments. if (RequestDepthStencilUpdate) { - const ECOLOR_FORMAT textureFormat = (DepthStencil) ? DepthStencil->getColorFormat() : ECF_UNKNOWN; + const ECOLOR_FORMAT textureFormat = DepthStencil ? DepthStencil->getColorFormat() : ECF_UNKNOWN; if (IImage::isDepthFormat(textureFormat)) { + GLenum textarget; + switch (DepthStencil->getType()) { + case ETT_2D: + textarget = GL_TEXTURE_2D; + break; + case ETT_2D_MS: + textarget = GL_TEXTURE_2D_MULTISAMPLE; + break; + default: + // ETT_CUBEMAP is rejected for depth/stencil by setTextures + throw std::logic_error("not reachable"); + } + GLuint textureID = static_cast(DepthStencil)->getOpenGLTextureName(); #ifdef _IRR_EMSCRIPTEN_PLATFORM_ // The WEBGL_depth_texture extension does not allow attaching stencil+depth separate. if (textureFormat == ECF_D24S8) { GLenum attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, textureID, 0); AssignedStencil = true; } else { - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget, textureID, 0); AssignedStencil = false; } #else - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget, textureID, 0); if (textureFormat == ECF_D24S8) { - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, textureID, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget, textureID, 0); AssignedStencil = true; } else { if (AssignedStencil) - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget, 0, 0); AssignedStencil = false; } #endif AssignedDepth = true; } else { + // No (valid) depth/stencil texture. if (AssignedDepth) Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 35c8f472ae7b9..a37562543b4be 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -45,12 +45,13 @@ class COpenGLCoreTexture : public ITexture COpenGLCoreTexture(const io::path &name, const std::vector &srcImages, E_TEXTURE_TYPE type, TOpenGLDriver *driver) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), - TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), + TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(0), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) { _IRR_DEBUG_BREAK_IF(srcImages.empty()) DriverType = Driver->getDriverType(); + _IRR_DEBUG_BREAK_IF(Type == ETT_2D_MS); // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY); @@ -141,10 +142,10 @@ class COpenGLCoreTexture : public ITexture TEST_GL_ERROR(Driver); } - COpenGLCoreTexture(const io::path &name, const core::dimension2d &size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver *driver) : + COpenGLCoreTexture(const io::path &name, const core::dimension2d &size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver *driver, u8 msaa = 0) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), - TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), + TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(msaa), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) { DriverType = Driver->getDriverType(); @@ -184,23 +185,47 @@ class COpenGLCoreTexture : public ITexture const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); - GL.TexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // An INVALID_ENUM error is generated by TexParameter* if target is either + // TEXTURE_2D_MULTISAMPLE or TEXTURE_2D_MULTISAMPLE_ARRAY, and pname is any + // sampler state from table 23.18. + // ~ https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf + if (Type != ETT_2D_MS) { + GL.TexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #if defined(GL_VERSION_1_2) - GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); #endif - StatesCache.WrapU = ETC_CLAMP_TO_EDGE; - StatesCache.WrapV = ETC_CLAMP_TO_EDGE; - StatesCache.WrapW = ETC_CLAMP_TO_EDGE; + StatesCache.WrapU = ETC_CLAMP_TO_EDGE; + StatesCache.WrapV = ETC_CLAMP_TO_EDGE; + StatesCache.WrapW = ETC_CLAMP_TO_EDGE; + } switch (Type) { case ETT_2D: GL.TexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); break; + case ETT_2D_MS: { + // glTexImage2DMultisample is supported by OpenGL 3.2+ + // glTexStorage2DMultisample is supported by OpenGL 4.3+ and OpenGL ES 3.1+ +#ifdef IRR_COMPILE_GL_COMMON // legacy driver + constexpr bool use_gl_impl = true; +#else + const bool use_gl_impl = Driver->Version.Spec != OpenGLSpec::ES; +#endif + GLint max_samples = 0; + GL.GetIntegerv(GL_MAX_SAMPLES, &max_samples); + MSAA = std::min(MSAA, (u8)max_samples); + + if (use_gl_impl) + GL.TexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + else + GL.TexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + break; + } case ETT_CUBEMAP: GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); @@ -595,6 +620,8 @@ class COpenGLCoreTexture : public ITexture switch (type) { case ETT_2D: return GL_TEXTURE_2D; + case ETT_2D_MS: + return GL_TEXTURE_2D_MULTISAMPLE; case ETT_CUBEMAP: return GL_TEXTURE_CUBE_MAP; } @@ -610,6 +637,7 @@ class COpenGLCoreTexture : public ITexture GLint InternalFormat; GLenum PixelFormat; GLenum PixelType; + u8 MSAA; void (*Converter)(const void *, s32, void *); bool LockReadOnly; diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 2cbf6d8e67173..07d7f7bb14927 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -651,6 +651,29 @@ IRenderTarget *COpenGLDriver::addRenderTarget() return renderTarget; } +void COpenGLDriver::blitRenderTarget(IRenderTarget *from, IRenderTarget *to) +{ + if (Version < 300) { + os::Printer::log("glBlitFramebuffer not supported by OpenGL < 3.0", ELL_ERROR); + return; + } + + GLuint prev_fbo_id; + CacheHandler->getFBO(prev_fbo_id); + + COpenGLRenderTarget *src = static_cast(from); + COpenGLRenderTarget *dst = static_cast(to); + GL.BindFramebuffer(GL.READ_FRAMEBUFFER, src->getBufferID()); + GL.BindFramebuffer(GL.DRAW_FRAMEBUFFER, dst->getBufferID()); + GL.BlitFramebuffer( + 0, 0, src->getSize().Width, src->getSize().Height, + 0, 0, dst->getSize().Width, dst->getSize().Height, + GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, GL.NEAREST); + + // This resets both read and draw framebuffer. Note that we bypass CacheHandler here. + GL.BindFramebuffer(GL.FRAMEBUFFER, prev_fbo_id); +} + // small helper function to create vertex buffer object address offsets static inline const GLvoid *buffer_offset(const size_t offset) { @@ -2091,7 +2114,9 @@ void COpenGLDriver::setBasicRenderStates(const SMaterial &material, const SMater else if (lastmaterial.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); - if ((AntiAlias >= 2) && (material.AntiAliasing & (EAAM_SIMPLE | EAAM_QUALITY))) { + // Enable MSAA even if it's not enabled in the OpenGL context, we might + // be rendering to an FBO with multisampling. + if (material.AntiAliasing & (EAAM_SIMPLE | EAAM_QUALITY)) { glEnable(GL_MULTISAMPLE_ARB); #ifdef GL_NV_multisample_filter_hint if (FeatureAvailable[IRR_NV_multisample_filter_hint]) { @@ -2694,6 +2719,12 @@ IVideoDriver *COpenGLDriver::getVideoDriver() ITexture *COpenGLDriver::addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format) +{ + return addRenderTargetTextureMs(size, 0, name, format); +} + +ITexture *COpenGLDriver::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) { if (IImage::isCompressedFormat(format)) return 0; @@ -2711,7 +2742,7 @@ ITexture *COpenGLDriver::addRenderTargetTexture(const core::dimension2d &si destSize = destSize.getOptimalSize((size == size.getOptimalSize()), false, false); } - COpenGLTexture *renderTargetTexture = new COpenGLTexture(name, destSize, ETT_2D, format, this); + COpenGLTexture *renderTargetTexture = new COpenGLTexture(name, destSize, msaa > 0 ? ETT_2D_MS : ETT_2D, format, this, msaa); addTexture(renderTargetTexture); renderTargetTexture->drop(); diff --git a/irr/src/COpenGLDriver.h b/irr/src/COpenGLDriver.h index 9c4ecd3b34ca1..126f7aadcdc22 100644 --- a/irr/src/COpenGLDriver.h +++ b/irr/src/COpenGLDriver.h @@ -108,6 +108,8 @@ class COpenGLDriver : public CNullDriver, public IMaterialRendererServices, publ //! Create render target. IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override; + //! draws a vertex primitive list virtual void drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, @@ -270,6 +272,9 @@ class COpenGLDriver : public CNullDriver, public IMaterialRendererServices, publ virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name = "rt", const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; diff --git a/irr/src/COpenGLExtensionHandler.cpp b/irr/src/COpenGLExtensionHandler.cpp index a20932c8f2489..4c7fe69b73064 100644 --- a/irr/src/COpenGLExtensionHandler.cpp +++ b/irr/src/COpenGLExtensionHandler.cpp @@ -612,6 +612,8 @@ bool COpenGLExtensionHandler::queryFeature(E_VIDEO_DRIVER_FEATURE feature) const return FeatureAvailable[IRR_ARB_seamless_cube_map]; case EVDF_DEPTH_CLAMP: return FeatureAvailable[IRR_NV_depth_clamp] || FeatureAvailable[IRR_ARB_depth_clamp]; + case EVDF_TEXTURE_MULTISAMPLE: + return (Version >= 302) || FeatureAvailable[IRR_ARB_texture_multisample]; default: return false; diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 4b85b1345d0cb..ee272ceeebdd6 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -675,6 +675,29 @@ IRenderTarget *COpenGL3DriverBase::addRenderTarget() return renderTarget; } +void COpenGL3DriverBase::blitRenderTarget(IRenderTarget *from, IRenderTarget *to) +{ + if (Version.Spec == OpenGLSpec::ES && Version.Major < 3) { + os::Printer::log("glBlitFramebuffer not supported by OpenGL ES < 3.0", ELL_ERROR); + return; + } + + GLuint prev_fbo_id; + CacheHandler->getFBO(prev_fbo_id); + + COpenGL3RenderTarget *src = static_cast(from); + COpenGL3RenderTarget *dst = static_cast(to); + GL.BindFramebuffer(GL.READ_FRAMEBUFFER, src->getBufferID()); + GL.BindFramebuffer(GL.DRAW_FRAMEBUFFER, dst->getBufferID()); + GL.BlitFramebuffer( + 0, 0, src->getSize().Width, src->getSize().Height, + 0, 0, dst->getSize().Width, dst->getSize().Height, + GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, GL.NEAREST); + + // This resets both read and draw framebuffer. Note that we bypass CacheHandler here. + GL.BindFramebuffer(GL.FRAMEBUFFER, prev_fbo_id); +} + //! draws a vertex primitive list void COpenGL3DriverBase::drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, @@ -1317,6 +1340,8 @@ void COpenGL3DriverBase::setBasicRenderStates(const SMaterial &material, const S GL.LineWidth(core::clamp(static_cast(material.Thickness), DimAliasedLine[0], DimAliasedLine[1])); // Anti aliasing + // Deal with MSAA even if it's not enabled in the OpenGL context, we might be + // rendering to an FBO with multisampling. if (resetAllRenderStates || lastmaterial.AntiAliasing != material.AntiAliasing) { if (material.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) GL.Enable(GL_SAMPLE_ALPHA_TO_COVERAGE); @@ -1631,12 +1656,18 @@ IGPUProgrammingServices *COpenGL3DriverBase::getGPUProgrammingServices() ITexture *COpenGL3DriverBase::addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format) +{ + return addRenderTargetTextureMs(size, 0, name, format); +} + +ITexture *COpenGL3DriverBase::addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format) { // disable mip-mapping bool generateMipLevels = getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); - COpenGL3Texture *renderTargetTexture = new COpenGL3Texture(name, size, ETT_2D, format, this); + COpenGL3Texture *renderTargetTexture = new COpenGL3Texture(name, size, msaa > 0 ? ETT_2D_MS : ETT_2D, format, this, msaa); addTexture(renderTargetTexture); renderTargetTexture->drop(); diff --git a/irr/src/OpenGL/Driver.h b/irr/src/OpenGL/Driver.h index 3104b164dae97..f00ffa8af42fa 100644 --- a/irr/src/OpenGL/Driver.h +++ b/irr/src/OpenGL/Driver.h @@ -74,6 +74,8 @@ class COpenGL3DriverBase : public CNullDriver, public IMaterialRendererServices, IRenderTarget *addRenderTarget() override; + void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override; + //! draws a vertex primitive list virtual void drawVertexPrimitiveList(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, @@ -209,6 +211,9 @@ class COpenGL3DriverBase : public CNullDriver, public IMaterialRendererServices, virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + virtual ITexture *addRenderTargetTextureMs(const core::dimension2d &size, u8 msaa, + const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; + //! Creates a render target texture for a cubemap ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) override; diff --git a/irr/src/OpenGL/ExtensionHandler.h b/irr/src/OpenGL/ExtensionHandler.h index 8f76c0eeafe43..403b828b30aa7 100644 --- a/irr/src/OpenGL/ExtensionHandler.h +++ b/irr/src/OpenGL/ExtensionHandler.h @@ -74,6 +74,8 @@ class COpenGL3ExtensionHandler : public COGLESCoreExtensionHandler return false; case EVDF_STENCIL_BUFFER: return StencilBuffer; + case EVDF_TEXTURE_MULTISAMPLE: + return TextureMultisampleSupported; default: return false; }; @@ -161,6 +163,7 @@ class COpenGL3ExtensionHandler : public COGLESCoreExtensionHandler bool AnisotropicFilterSupported = false; bool BlendMinMaxSupported = false; + bool TextureMultisampleSupported = false; }; } diff --git a/irr/src/OpenGL3/Driver.cpp b/irr/src/OpenGL3/Driver.cpp index ac08164452af2..b397846aa524f 100644 --- a/irr/src/OpenGL3/Driver.cpp +++ b/irr/src/OpenGL3/Driver.cpp @@ -69,6 +69,7 @@ void COpenGL3Driver::initFeatures() AnisotropicFilterSupported = isVersionAtLeast(4, 6) || queryExtension("GL_ARB_texture_filter_anisotropic") || queryExtension("GL_EXT_texture_filter_anisotropic"); BlendMinMaxSupported = true; + TextureMultisampleSupported = true; // COGLESCoreExtensionHandler::Feature static_assert(MATERIAL_MAX_TEXTURES <= 16, "Only up to 16 textures are guaranteed"); diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 430be736d7e4c..3c034522c408a 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -124,6 +124,7 @@ void COpenGLES2Driver::initFeatures() const bool MRTSupported = Version.Major >= 3 || queryExtension("GL_EXT_draw_buffers"); AnisotropicFilterSupported = queryExtension("GL_EXT_texture_filter_anisotropic"); BlendMinMaxSupported = (Version.Major >= 3) || FeatureAvailable[IRR_GL_EXT_blend_minmax]; + TextureMultisampleSupported = isVersionAtLeast(3, 1); const bool TextureLODBiasSupported = queryExtension("GL_EXT_texture_lod_bias"); // COGLESCoreExtensionHandler::Feature diff --git a/src/client/render/pipeline.cpp b/src/client/render/pipeline.cpp index a37fd32fd25a3..d24aa3ce868a7 100644 --- a/src/client/render/pipeline.cpp +++ b/src/client/render/pipeline.cpp @@ -26,7 +26,7 @@ video::ITexture *TextureBuffer::getTexture(u8 index) } -void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear) +void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa) { assert(index != NO_DEPTH_TEXTURE); @@ -41,9 +41,10 @@ void TextureBuffer::setTexture(u8 index, core::dimension2du size, const std::str definition.name = name; definition.format = format; definition.clear = clear; + definition.msaa = msaa; } -void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear) +void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &name, video::ECOLOR_FORMAT format, bool clear, u8 msaa) { assert(index != NO_DEPTH_TEXTURE); @@ -58,6 +59,7 @@ void TextureBuffer::setTexture(u8 index, v2f scale_factor, const std::string &na definition.name = name; definition.format = format; definition.clear = clear; + definition.msaa = msaa; } void TextureBuffer::reset(PipelineContext &context) @@ -125,13 +127,19 @@ bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefini if (definition.valid) { if (definition.clear) { + // We're not able to clear a render target texture + // We're not able to create a normal texture with MSAA + // (could be solved by more refactoring in Irrlicht, but not needed for now) + sanity_check(definition.msaa < 1); + video::IImage *image = m_driver->createImage(definition.format, size); // Cannot use image->fill because it's not implemented for all formats. std::memset(image->getData(), 0, image->getDataSizeFromFormat(definition.format, size.Width, size.Height)); *texture = m_driver->addTexture(definition.name.c_str(), image); image->drop(); - } - else { + } else if (definition.msaa > 0) { + *texture = m_driver->addRenderTargetTextureMs(size, definition.msaa, definition.name.c_str(), definition.format); + } else { *texture = m_driver->addRenderTargetTexture(size, definition.name.c_str(), definition.format); } } @@ -189,6 +197,12 @@ void TextureBufferOutput::activate(PipelineContext &context) RenderTarget::activate(context); } +video::IRenderTarget *TextureBufferOutput::getIrrRenderTarget(PipelineContext &context) +{ + activate(context); // Needed to make sure that render_target is set up. + return render_target; +} + u8 DynamicSource::getTextureCount() { assert(isConfigured()); diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index 9fa47c329c507..17bed8b7bce07 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -117,7 +117,7 @@ class TextureBuffer : public RenderSource * @param name unique name of the texture * @param format color format */ - void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false); + void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0); /** * Configure relative-size texture for the specific index @@ -127,7 +127,7 @@ class TextureBuffer : public RenderSource * @param name unique name of the texture * @param format color format */ - void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false); + void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format, bool clear = false, u8 msaa = 0); virtual u8 getTextureCount() override { return m_textures.size(); } virtual video::ITexture *getTexture(u8 index) override; @@ -146,6 +146,7 @@ class TextureBuffer : public RenderSource core::dimension2du size; std::string name; video::ECOLOR_FORMAT format; + u8 msaa; }; /** @@ -174,6 +175,9 @@ class TextureBufferOutput : public RenderTarget TextureBufferOutput(TextureBuffer *buffer, const std::vector &texture_map, u8 depth_stencil); virtual ~TextureBufferOutput() override; void activate(PipelineContext &context) override; + + video::IRenderTarget *getIrrRenderTarget(PipelineContext &context); + private: static const u8 NO_DEPTH_TEXTURE = 255; diff --git a/src/client/render/secondstage.cpp b/src/client/render/secondstage.cpp index 26ce6888ef8ec..f6a0cf78ec003 100644 --- a/src/client/render/secondstage.cpp +++ b/src/client/render/secondstage.cpp @@ -9,6 +9,7 @@ #include "client/shader.h" #include "client/tile.h" #include "settings.h" +#include "mt_opengl.h" PostProcessingStep::PostProcessingStep(u32 _shader_id, const std::vector &_texture_map) : shader_id(_shader_id), texture_map(_texture_map) @@ -102,21 +103,45 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep static const u8 TEXTURE_EXPOSURE_2 = 4; static const u8 TEXTURE_FXAA = 5; static const u8 TEXTURE_VOLUME = 6; + + static const u8 TEXTURE_MSAA_COLOR = 7; + static const u8 TEXTURE_MSAA_DEPTH = 8; + static const u8 TEXTURE_SCALE_DOWN = 10; static const u8 TEXTURE_SCALE_UP = 20; - // Super-sampling is simply rendering into a larger texture. - // Downscaling is done by the final step when rendering to the screen. - const std::string antialiasing = g_settings->get("antialiasing"); const bool enable_bloom = g_settings->getBool("enable_bloom"); + const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom; const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure"); + + const std::string antialiasing = g_settings->get("antialiasing"); + const u16 antialiasing_scale = MYMAX(2, g_settings->getU16("fsaa")); + + // This code only deals with MSAA in combination with post-processing. MSAA without + // post-processing works via a flag at OpenGL context creation instead. + // To make MSAA work with post-processing, we need multisample texture support, + // which has higher OpenGL (ES) version requirements. + // Note: This is not about renderbuffer objects, but about textures, + // since that's what we use and what Irrlicht allows us to use. + + const bool msaa_available = driver->queryFeature(video::EVDF_TEXTURE_MULTISAMPLE); + const bool enable_msaa = antialiasing == "fsaa" && msaa_available; + if (antialiasing == "fsaa" && !msaa_available) + warningstream << "Ignoring configured FSAA. FSAA is not supported in " + << "combination with post-processing by the current video driver." << std::endl; + const bool enable_ssaa = antialiasing == "ssaa"; const bool enable_fxaa = antialiasing == "fxaa"; - const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom; + // Super-sampling is simply rendering into a larger texture. + // Downscaling is done by the final step when rendering to the screen. if (enable_ssaa) { - u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); - scale *= ssaa_scale; + scale *= antialiasing_scale; + } + + if (enable_msaa) { + buffer->setTexture(TEXTURE_MSAA_COLOR, scale, "3d_render_msaa", color_format, false, antialiasing_scale); + buffer->setTexture(TEXTURE_MSAA_DEPTH, scale, "3d_depthmap_msaa", depth_format, false, antialiasing_scale); } buffer->setTexture(TEXTURE_COLOR, scale, "3d_render", color_format); @@ -125,7 +150,14 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep buffer->setTexture(TEXTURE_DEPTH, scale, "3d_depthmap", depth_format); // attach buffer to the previous step - previousStep->setRenderTarget(pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH)); + if (enable_msaa) { + TextureBufferOutput *msaa = pipeline->createOwned(buffer, std::vector { TEXTURE_MSAA_COLOR }, TEXTURE_MSAA_DEPTH); + previousStep->setRenderTarget(msaa); + TextureBufferOutput *normal = pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH); + pipeline->addStep(msaa, normal); + } else { + previousStep->setRenderTarget(pipeline->createOwned(buffer, std::vector { TEXTURE_COLOR }, TEXTURE_DEPTH)); + } // shared variables u32 shader_id; @@ -234,3 +266,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep return effect; } + +void ResolveMSAAStep::run(PipelineContext &context) +{ + context.device->getVideoDriver()->blitRenderTarget(msaa_fbo->getIrrRenderTarget(context), + target_fbo->getIrrRenderTarget(context)); +} diff --git a/src/client/render/secondstage.h b/src/client/render/secondstage.h index 6b0e422896115..1765a5ed87357 100644 --- a/src/client/render/secondstage.h +++ b/src/client/render/secondstage.h @@ -44,4 +44,18 @@ class PostProcessingStep : public RenderStep void configureMaterial(); }; + +class ResolveMSAAStep : public TrivialRenderStep +{ +public: + ResolveMSAAStep(TextureBufferOutput *_msaa_fbo, TextureBufferOutput *_target_fbo) : + msaa_fbo(_msaa_fbo), target_fbo(_target_fbo) {}; + void run(PipelineContext &context) override; + +private: + TextureBufferOutput *msaa_fbo; + TextureBufferOutput *target_fbo; +}; + + RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep, v2f scale, Client *client); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 4e45bd4cd3f98..c3cb49eed35d1 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -179,7 +179,10 @@ RenderingEngine::RenderingEngine(MyEventReceiver *receiver) // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); - bool enable_fsaa = g_settings->get("antialiasing") == "fsaa"; + // Don't enable MSAA in OpenGL context creation if post-processing is enabled, + // the post-processing pipeline handles it. + bool enable_fsaa = g_settings->get("antialiasing") == "fsaa" && + !g_settings->getBool("enable_post_processing"); u16 fsaa = enable_fsaa ? MYMAX(2, g_settings->getU16("fsaa")) : 0; // Determine driver