Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json +++ ps/trunk/binaries/data/mods/public/gui/options/options.json @@ -125,7 +125,11 @@ "config": "antialiasing", "list": [ { "value": "disabled", "label": "Disabled", "tooltip": "Do not use anti-aliasing." }, - { "value": "fxaa", "label": "FXAA", "tooltip": "Fast, but simple anti-aliasing." } + { "value": "fxaa", "label": "FXAA", "tooltip": "Fast, but simple anti-aliasing." }, + { "value": "msaa2", "label": "MSAA (x2)", "tooltip": "Slow, but high-quality anti-aliasing, uses 2 samples per pixel. Supported for GL3.3+." }, + { "value": "msaa4", "label": "MSAA (x4)", "tooltip": "Slow, but high-quality anti-aliasing, uses 4 samples per pixel. Supported for GL3.3+." }, + { "value": "msaa8", "label": "MSAA (x8)", "tooltip": "Slow, but high-quality anti-aliasing, uses 8 samples per pixel. Supported for GL3.3+." }, + { "value": "msaa16", "label": "MSAA (x16)", "tooltip": "Slow, but high-quality anti-aliasing, uses 16 samples per pixel. Supported for GL3.3+." } ], "function": "Renderer_UpdateAntiAliasingTechnique" }, Index: ps/trunk/source/lib/external_libraries/glext_funcs.h =================================================================== --- ps/trunk/source/lib/external_libraries/glext_funcs.h +++ ps/trunk/source/lib/external_libraries/glext_funcs.h @@ -144,6 +144,11 @@ #else +// We might not have multisample on macOS. +#ifndef GL_TEXTURE_2D_MULTISAMPLE +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#endif + // were these defined as real functions in gl.h already? // GL_EXT_draw_range_elements / GL1.2: @@ -385,6 +390,9 @@ FUNC2(void*, glMapBufferRange, glMapBufferRange, "3.0", (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access)) FUNC2(void, glFlushMappedBufferRange, glFlushMappedBufferRange, "3.0", (GLenum target, GLintptr offset, GLsizeiptr length)) +// GL_ARB_texture_multisample / GL3.3: +FUNC2(void, glTexImage2DMultisample, glTexImage2DMultisample, "3.3", (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations)) + // GL_GREMEDY_string_marker (from gDEBugger) FUNC(int, glStringMarkerGREMEDY, (GLsizei len, const GLvoid *string)) Index: ps/trunk/source/renderer/PostprocManager.h =================================================================== --- ps/trunk/source/renderer/PostprocManager.h +++ ps/trunk/source/renderer/PostprocManager.h @@ -70,7 +70,15 @@ // @note CPostprocManager must be initialized first void ReleaseRenderOutput(); + // Returns true if we render main scene in the MSAA framebuffer. + bool IsMultisampleEnabled() const; + + // Resolves the MSAA framebuffer into the regular one. + void ResolveMultisampleFramebuffer(); + private: + void CreateMultisampleBuffer(); + void DestroyMultisampleBuffer(); // Two framebuffers, that we flip between at each shader pass. GLuint m_PingFbo, m_PongFbo; @@ -99,6 +107,11 @@ CStr m_AAName; CShaderTechniquePtr m_AATech; + bool m_UsingMultisampleBuffer; + GLuint m_MultisampleFBO; + GLuint m_MultisampleColorTex, m_MultisampleDepthTex; + GLsizei m_MultisampleCount; + std::vector m_AllowedSampleCounts; // The current screen dimensions in pixels. int m_Width, m_Height; Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp +++ ps/trunk/source/renderer/PostprocManager.cpp @@ -32,13 +32,17 @@ #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" +#include "tools/atlas/GameInterface/GameLoop.h" #if !CONFIG2_GLES CPostprocManager::CPostprocManager() - : m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"), m_ColorTex1(0), m_ColorTex2(0), - m_DepthTex(0), m_BloomFbo(0), m_BlurTex2a(0), m_BlurTex2b(0), m_BlurTex4a(0), m_BlurTex4b(0), - m_BlurTex8a(0), m_BlurTex8b(0), m_WhichBuffer(true), m_Sharpness(0.3f) + : m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"), + m_ColorTex1(0), m_ColorTex2(0), m_DepthTex(0), m_BloomFbo(0), m_BlurTex2a(0), + m_BlurTex2b(0), m_BlurTex4a(0), m_BlurTex4b(0), m_BlurTex8a(0), m_BlurTex8b(0), + m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), + m_MultisampleFBO(0), m_MultisampleColorTex(0), m_MultisampleDepthTex(0), + m_MultisampleCount(0) { } @@ -76,6 +80,14 @@ if (m_IsInitialized) return; + GLint maxSamples = 0; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + const GLsizei possibleSampleCounts[] = {2, 4, 8, 16}; + std::copy_if( + std::begin(possibleSampleCounts), std::end(possibleSampleCounts), + std::back_inserter(m_AllowedSampleCounts), + [maxSamples](const GLsizei sampleCount) { return sampleCount <= maxSamples; } ); + // The screen size starts out correct and then must be updated with Resize() m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); @@ -193,6 +205,12 @@ } */ + if (m_UsingMultisampleBuffer) + { + DestroyMultisampleBuffer(); + CreateMultisampleBuffer(); + } + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } @@ -371,6 +389,13 @@ pglDrawBuffers(1, buffers); m_WhichBuffer = true; + + if (m_UsingMultisampleBuffer) + { + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + pglDrawBuffers(1, buffers); + } } @@ -576,13 +601,52 @@ m_AAName = newAAName; m_AATech.reset(); + if (m_UsingMultisampleBuffer) + { + m_UsingMultisampleBuffer = false; + DestroyMultisampleBuffer(); + } + // We have to hardcode names in the engine, because anti-aliasing // techinques strongly depend on the graphics pipeline. // We might use enums in future though. + const CStr msaaPrefix = "msaa"; if (m_AAName == "fxaa") { m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa")); } + else if (m_AAName.size() > msaaPrefix.size() && m_AAName.substr(0, msaaPrefix.size()) == msaaPrefix) + { +#if !CONFIG2_GLES + // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas. + if (g_AtlasGameLoop && g_AtlasGameLoop->running) + return; + const bool is_msaa_supported = + ogl_HaveVersion("3.3") && + ogl_HaveExtension("GL_ARB_multisample") && + ogl_HaveExtension("GL_ARB_texture_multisample") && + !m_AllowedSampleCounts.empty() && + g_RenderingOptions.GetPreferGLSL(); + if (!is_msaa_supported) + { + LOGWARNING("MSAA is unsupported."); + return; + } + std::stringstream ss(m_AAName.substr(msaaPrefix.size())); + ss >> m_MultisampleCount; + if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) == + std::end(m_AllowedSampleCounts)) + { + m_MultisampleCount = 4; + LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str()); + } + m_UsingMultisampleBuffer = true; + CreateMultisampleBuffer(); +#else + #warning TODO: implement and test MSAA for GLES + LOGWARNING("MSAA is unsupported."); +#endif + } } void CPostprocManager::UpdateSharpeningTechnique() @@ -614,6 +678,77 @@ m_FarPlane = farPlane; } +void CPostprocManager::CreateMultisampleBuffer() +{ + glEnable(GL_MULTISAMPLE); + + glGenTextures(1, &m_MultisampleColorTex); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleColorTex); + pglTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleCount, GL_RGBA, m_Width, m_Height, GL_TRUE); + + // Allocate the Depth/Stencil texture. + glGenTextures(1, &m_MultisampleDepthTex); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleDepthTex); + pglTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleCount, GL_DEPTH24_STENCIL8_EXT, m_Width, m_Height, GL_TRUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + + ogl_WarnIfError(); + + // Set up the framebuffers with some initial textures. + pglGenFramebuffersEXT(1, &m_MultisampleFBO); + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO); + + pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleColorTex, 0); + + pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleDepthTex, 0); + + GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + LOGWARNING("Multisample framebuffer object incomplete (A): 0x%04X", status); + m_UsingMultisampleBuffer = false; + DestroyMultisampleBuffer(); + } + + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void CPostprocManager::DestroyMultisampleBuffer() +{ + if (m_UsingMultisampleBuffer) + return; + if (m_MultisampleFBO) + pglDeleteFramebuffersEXT(1, &m_MultisampleFBO); + if (m_MultisampleColorTex) + glDeleteTextures(1, &m_MultisampleColorTex); + if (m_MultisampleDepthTex) + glDeleteTextures(1, &m_MultisampleDepthTex); + glDisable(GL_MULTISAMPLE); +} + +bool CPostprocManager::IsMultisampleEnabled() const +{ + return m_UsingMultisampleBuffer; +} + +void CPostprocManager::ResolveMultisampleFramebuffer() +{ + if (!m_UsingMultisampleBuffer) + return; + + pglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, m_PingFbo); + pglBlitFramebufferEXT(0, 0, m_Width, m_Height, 0, 0, m_Width, m_Height, + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); + + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); +} + #else #warning TODO: implement PostprocManager for GLES @@ -691,4 +826,21 @@ { } +void CPostprocManager::CreateMultisampleBuffer() +{ +} + +void CPostprocManager::DestroyMultisampleBuffer() +{ +} + +bool CPostprocManager::IsMultisampleEnabled() const +{ + return false; +} + +void CPostprocManager::ResolveMultisampleFramebuffer() +{ +} + #endif Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp +++ ps/trunk/source/renderer/Renderer.cpp @@ -1387,6 +1387,9 @@ ogl_WarnIfError(); } + if (g_Renderer.GetPostprocManager().IsMultisampleEnabled()) + g_Renderer.GetPostprocManager().ResolveMultisampleFramebuffer(); + // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError();