mirror of
https://github.com/minetest/minetest.git
synced 2024-12-22 22:22:23 +01:00
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:
parent
a8ea165042
commit
9b6a399011
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user