/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "gui/GUIutil.h" #include "lib/bits.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/World.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "renderer/PostprocManager.h" #include "renderer/Renderer.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) { } CPostprocManager::~CPostprocManager() { Cleanup(); } void CPostprocManager::Cleanup() { if (!m_IsInitialized) // Only cleanup if previously used return; if (m_PingFbo) pglDeleteFramebuffersEXT(1, &m_PingFbo); if (m_PongFbo) pglDeleteFramebuffersEXT(1, &m_PongFbo); if (m_BloomFbo) pglDeleteFramebuffersEXT(1, &m_BloomFbo); m_PingFbo = m_PongFbo = m_BloomFbo = 0; if (m_ColorTex1) glDeleteTextures(1, &m_ColorTex1); if (m_ColorTex2) glDeleteTextures(1, &m_ColorTex2); if (m_DepthTex) glDeleteTextures(1, &m_DepthTex); m_ColorTex1 = m_ColorTex2 = m_DepthTex = 0; if (m_BlurTex2a) glDeleteTextures(1, &m_BlurTex2a); if (m_BlurTex2b) glDeleteTextures(1, &m_BlurTex2b); if (m_BlurTex4a) glDeleteTextures(1, &m_BlurTex4a); if (m_BlurTex4b) glDeleteTextures(1, &m_BlurTex4b); if (m_BlurTex8a) glDeleteTextures(1, &m_BlurTex8a); if (m_BlurTex8b) glDeleteTextures(1, &m_BlurTex8b); m_BlurTex2a = m_BlurTex2b = m_BlurTex4a = m_BlurTex4b = m_BlurTex8a = m_BlurTex8b = 0; } void CPostprocManager::Initialize() { if (m_IsInitialized) return; // The screen size starts out correct and then must be updated with Resize() m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); RecreateBuffers(); m_IsInitialized = true; // This might happen after the map is loaded and the effect chosen SetPostEffect(m_PostProcEffect); } void CPostprocManager::Resize() { m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); // If the buffers were intialized, recreate them to the new size. if (m_IsInitialized) RecreateBuffers(); } void CPostprocManager::RecreateBuffers() { Cleanup(); #define GEN_BUFFER_RGBA(name, w, h) \ glGenTextures(1, (GLuint*)&name); \ glBindTexture(GL_TEXTURE_2D, name); \ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Two fullscreen ping-pong textures. GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height); GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height); // Textures for several blur sizes. It would be possible to reuse // m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given // that these are fairly small it's probably not worth complicating the coordinates passed // to the blur helper functions. GEN_BUFFER_RGBA(m_BlurTex2a, m_Width / 2, m_Height / 2); GEN_BUFFER_RGBA(m_BlurTex2b, m_Width / 2, m_Height / 2); GEN_BUFFER_RGBA(m_BlurTex4a, m_Width / 4, m_Height / 4); GEN_BUFFER_RGBA(m_BlurTex4b, m_Width / 4, m_Height / 4); GEN_BUFFER_RGBA(m_BlurTex8a, m_Width / 8, m_Height / 8); GEN_BUFFER_RGBA(m_BlurTex8b, m_Width / 8, m_Height / 8); #undef GEN_BUFFER_RGBA // Allocate the Depth/Stencil texture. glGenTextures(1, (GLuint*)&m_DepthTex); glBindTexture(GL_TEXTURE_2D, m_DepthTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8_EXT, m_Width, m_Height, 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); // Set up the framebuffers with some initial textures. pglGenFramebuffersEXT(1, &m_PingFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ColorTex1, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex, 0); GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (A): 0x%04X", status); } pglGenFramebuffersEXT(1, &m_PongFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ColorTex2, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex, 0); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status); } pglGenFramebuffersEXT(1, &m_BloomFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); /* pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_BloomTex1, 0); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status); } */ pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CPostprocManager::ApplyBlurDownscale2x(GLuint inTex, GLuint outTex, int inWidth, int inHeight) { // Bind inTex to framebuffer for rendering. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, outTex, 0); // Get bloom shader with instructions to simply copy texels. CShaderDefines defines; defines.Add(str_BLOOM_NOP, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, g_Renderer.GetSystemShaderDefines(), defines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); GLuint renderedTex = inTex; // Cheat by creating high quality mipmaps for inTex, so the copying operation actually // produces good scaling due to hardware filtering. glBindTexture(GL_TEXTURE_2D, renderedTex); pglGenerateMipmapEXT(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); shader->BindTexture(str_renderedTex, renderedTex); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth / 2, inHeight / 2 }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } void CPostprocManager::ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight) { // Set tempTex as our rendering target. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tempTex, 0); // Get bloom shader, for a horizontal Gaussian blur pass. CShaderDefines defines2; defines2.Add(str_BLOOM_PASS_H, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, g_Renderer.GetSystemShaderDefines(), defines2); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->BindTexture(str_renderedTex, inOutTex); shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth, inHeight }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); // Set result texture as our render target. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, inOutTex, 0); // Get bloom shader, for a vertical Gaussian blur pass. CShaderDefines defines3; defines3.Add(str_BLOOM_PASS_V, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, g_Renderer.GetSystemShaderDefines(), defines3); tech->BeginPass(); shader = tech->GetShader(); // Our input texture to the shader is the output of the horizontal pass. shader->BindTexture(str_renderedTex, tempTex); shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f); g_Renderer.SetViewport(vp); shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } void CPostprocManager::ApplyBlur() { glDisable(GL_BLEND); GLint originalFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &originalFBO); int width = m_Width, height = m_Height; #define SCALE_AND_BLUR(tex1, tex2, temptex) \ ApplyBlurDownscale2x(tex1, tex2, width, height); \ width /= 2; \ height /= 2; \ ApplyBlurGauss(tex2, temptex, width, height); // We do the same thing for each scale, incrementally adding more and more blur. SCALE_AND_BLUR(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2, m_BlurTex2a, m_BlurTex2b); SCALE_AND_BLUR(m_BlurTex2a, m_BlurTex4a, m_BlurTex4b); SCALE_AND_BLUR(m_BlurTex4a, m_BlurTex8a, m_BlurTex8b); #undef SCALE_AND_BLUR pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, originalFBO); } void CPostprocManager::CaptureRenderOutput() { ENSURE(m_IsInitialized); // clear both FBOs and leave m_PingFbo selected for rendering; // m_WhichBuffer stays true at this point pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; pglDrawBuffers(1, buffers); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); pglDrawBuffers(1, buffers); m_WhichBuffer = true; } void CPostprocManager::ReleaseRenderOutput() { ENSURE(m_IsInitialized); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // we blit to screen from the previous active buffer if (m_WhichBuffer) pglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PingFbo); else pglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PongFbo); pglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); 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_READ_FRAMEBUFFER_EXT, 0); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CPostprocManager::ApplyEffect(CShaderTechniquePtr &shaderTech1, int pass) { // select the other FBO for rendering if (!m_WhichBuffer) pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); else pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); shaderTech1->BeginPass(pass); CShaderProgramPtr shader = shaderTech1->GetShader(pass); shader->Bind(); // Use the textures from the current FBO as input to the shader. // We also bind a bunch of other textures and parameters, but since // this only happens once per frame the overhead is negligible. if (m_WhichBuffer) shader->BindTexture(str_renderedTex, m_ColorTex1); else shader->BindTexture(str_renderedTex, m_ColorTex2); shader->BindTexture(str_depthTex, m_DepthTex); shader->BindTexture(str_blurTex2, m_BlurTex2a); shader->BindTexture(str_blurTex4, m_BlurTex4a); shader->BindTexture(str_blurTex8, m_BlurTex8a); shader->Uniform(str_width, m_Width); shader->Uniform(str_height, m_Height); shader->Uniform(str_zNear, g_Game->GetView()->GetNear()); shader->Uniform(str_zFar, g_Game->GetView()->GetFar()); shader->Uniform(str_brightness, g_LightEnv.m_Brightness); shader->Uniform(str_hdr, g_LightEnv.m_Contrast); shader->Uniform(str_saturation, g_LightEnv.m_Saturation); shader->Uniform(str_bloom, g_LightEnv.m_Bloom); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); shader->Unbind(); shaderTech1->EndPass(pass); glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); m_WhichBuffer = !m_WhichBuffer; } void CPostprocManager::ApplyPostproc() { ENSURE(m_IsInitialized); // Don't do anything if we are using the default effect. if (m_PostProcEffect == L"default") return; pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0); GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT }; pglDrawBuffers(1, buffers); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0); pglDrawBuffers(1, buffers); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); // First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied! // (This may need to change depending on future usage, however that will have a fps hit) ApplyBlur(); for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass) ApplyEffect(m_PostProcTech, pass); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex, 0); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex, 0); } // Generate list of available effect-sets std::vector CPostprocManager::GetPostEffects() { std::vector effects; const VfsPath path(L"shaders/effects/postproc/"); VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, path, 0, pathnames) < 0) LOGERROR("Error finding Post effects in '%s'", path.string8()); for (const VfsPath& path : pathnames) if (path.Extension() == L".xml") effects.push_back(path.Basename().string()); // Add the default "null" effect to the list. effects.push_back(L"default"); sort(effects.begin(), effects.end()); return effects; } void CPostprocManager::SetPostEffect(const CStrW& name) { if (m_IsInitialized) { if (name != L"default") { CStrW n = L"postproc/" + name; m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8())); } } m_PostProcEffect = name; } #else #warning TODO: implement PostprocManager for GLES void ApplyBlurDownscale2x(GLuint UNUSED(inTex), GLuint UNUSED(outTex), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyBlurGauss(GLuint UNUSED(inOutTex), GLuint UNUSED(tempTex), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyEffect(CShaderTechniquePtr &UNUSED(shaderTech1), int UNUSED(pass)) { } CPostprocManager::CPostprocManager() { } CPostprocManager::~CPostprocManager() { } void CPostprocManager::Initialize() { } void CPostprocManager::Resize() { } void CPostprocManager::Cleanup() { } void CPostprocManager::RecreateBuffers() { } std::vector CPostprocManager::GetPostEffects() { return std::vector(); } void CPostprocManager::SetPostEffect(const CStrW& UNUSED(name)) { } void CPostprocManager::CaptureRenderOutput() { } void CPostprocManager::ApplyPostproc() { } void CPostprocManager::ReleaseRenderOutput() { } #endif