Implement support for FSAA in combination with post-processing (#15392)

- Actually it's MSAA I think, or perhaps the terms are equivalent
- I've made it fit into the existing Irrlicht architecture, but that has resulted in code duplication compared to my original "hacky" approach
- OpenGL 3.2+ and OpenGL ES 3.1+ are supported
- EDT_OPENGL3 is not required, EDT_OPENGL works too
- Helpful tutorial: https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing, section "Off-screen MSAA"
- This may be rough around the edges, but in general it works
This commit is contained in:
grorp 2024-11-18 14:06:48 +01:00 committed by GitHub
parent a8ea165042
commit 9b6a399011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 290 additions and 42 deletions

@ -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.

@ -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
};

@ -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:

@ -156,6 +156,9 @@ enum E_TEXTURE_TYPE
//! 2D texture.
ETT_2D,
//! 2D texture with multisampling.
ETT_2D_MS,
//! Cubemap texture.
ETT_CUBEMAP
};

@ -273,6 +273,14 @@ public:
virtual ITexture *addRenderTargetTexture(const core::dimension2d<u32> &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<u32> &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 @@ public:
//! 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

@ -1678,6 +1678,12 @@ ITexture *CNullDriver::addRenderTargetTexture(const core::dimension2d<u32> &size
return 0;
}
ITexture *CNullDriver::addRenderTargetTextureMs(const core::dimension2d<u32> &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)
{

@ -227,6 +227,10 @@ public:
virtual ITexture *addRenderTargetTexture(const core::dimension2d<u32> &size,
const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override;
//! Creates a multisampled render target texture.
virtual ITexture *addRenderTargetTextureMs(const core::dimension2d<u32> &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 @@ public:
//! Create render target.
IRenderTarget *addRenderTarget() override;
void blitRenderTarget(IRenderTarget *from, IRenderTarget *to) override {}
//! Remove render target.
void removeRenderTarget(IRenderTarget *renderTarget) override;

@ -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<const TOpenGLTexture *>(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
}

@ -5,6 +5,7 @@
#pragma once
#include "IRenderTarget.h"
#include <stdexcept>
#ifndef GL_FRAMEBUFFER_INCOMPLETE_FORMATS
#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT
@ -122,7 +123,7 @@ public:
TOpenGLTexture *currentTexture = (depthStencil && depthStencil->getDriverType() == DriverType) ? static_cast<TOpenGLTexture *>(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 @@ public:
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 @@ public:
// 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<TOpenGLTexture *>(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);

@ -45,12 +45,13 @@ public:
COpenGLCoreTexture(const io::path &name, const std::vector<IImage *> &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 @@ public:
TEST_GL_ERROR(Driver);
}
COpenGLCoreTexture(const io::path &name, const core::dimension2d<u32> &size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver *driver) :
COpenGLCoreTexture(const io::path &name, const core::dimension2d<u32> &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 @@ public:
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 @@ protected:
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 @@ protected:
GLint InternalFormat;
GLenum PixelFormat;
GLenum PixelType;
u8 MSAA;
void (*Converter)(const void *, s32, void *);
bool LockReadOnly;

@ -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<COpenGLRenderTarget *>(from);
COpenGLRenderTarget *dst = static_cast<COpenGLRenderTarget *>(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<u32> &size,
const io::path &name, const ECOLOR_FORMAT format)
{
return addRenderTargetTextureMs(size, 0, name, format);
}
ITexture *COpenGLDriver::addRenderTargetTextureMs(const core::dimension2d<u32> &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<u32> &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();

@ -108,6 +108,8 @@ public:
//! 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 @@ public:
virtual ITexture *addRenderTargetTexture(const core::dimension2d<u32> &size,
const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override;
virtual ITexture *addRenderTargetTextureMs(const core::dimension2d<u32> &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;

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

@ -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<COpenGL3RenderTarget *>(from);
COpenGL3RenderTarget *dst = static_cast<COpenGL3RenderTarget *>(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<GLfloat>(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<u32> &size,
const io::path &name, const ECOLOR_FORMAT format)
{
return addRenderTargetTextureMs(size, 0, name, format);
}
ITexture *COpenGL3DriverBase::addRenderTargetTextureMs(const core::dimension2d<u32> &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();

@ -74,6 +74,8 @@ public:
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 @@ public:
virtual ITexture *addRenderTargetTexture(const core::dimension2d<u32> &size,
const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override;
virtual ITexture *addRenderTargetTextureMs(const core::dimension2d<u32> &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;

@ -74,6 +74,8 @@ public:
return false;
case EVDF_STENCIL_BUFFER:
return StencilBuffer;
case EVDF_TEXTURE_MULTISAMPLE:
return TextureMultisampleSupported;
default:
return false;
};
@ -161,6 +163,7 @@ public:
bool AnisotropicFilterSupported = false;
bool BlendMinMaxSupported = false;
bool TextureMultisampleSupported = false;
};
}

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

@ -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

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

@ -117,7 +117,7 @@ public:
* @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 @@ public:
* @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 @@ private:
core::dimension2du size;
std::string name;
video::ECOLOR_FORMAT format;
u8 msaa;
};
/**
@ -174,6 +175,9 @@ public:
TextureBufferOutput(TextureBuffer *buffer, const std::vector<u8> &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;

@ -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<u8> &_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<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_COLOR }, TEXTURE_DEPTH));
if (enable_msaa) {
TextureBufferOutput *msaa = pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_MSAA_COLOR }, TEXTURE_MSAA_DEPTH);
previousStep->setRenderTarget(msaa);
TextureBufferOutput *normal = pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { TEXTURE_COLOR }, TEXTURE_DEPTH);
pipeline->addStep<ResolveMSAAStep>(msaa, normal);
} else {
previousStep->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, std::vector<u8> { 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));
}

@ -44,4 +44,18 @@ private:
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);

@ -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