1
1
forked from 0ad/0ad
0ad/source/renderer/PostprocManager.cpp
2012-12-18 22:58:34 +00:00

489 lines
16 KiB
C++

/* Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
*/
#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"
CPostprocManager::CPostprocManager()
: m_IsInitialised(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"), m_ColourTex1(0), m_ColourTex2(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::Initialize()
{
RecreateBuffers();
m_IsInitialised = true;
SetPostEffect(L"default");
}
void CPostprocManager::Cleanup()
{
if (m_IsInitialised)
{
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_ColourTex1) glDeleteTextures(1, &m_ColourTex1);
if (m_ColourTex2) glDeleteTextures(1, &m_ColourTex2);
if (m_DepthTex) glDeleteTextures(1, &m_DepthTex);
m_ColourTex1 = m_ColourTex2 = 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::RecreateBuffers()
{
Cleanup();
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
#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_ColourTex1, m_Width, m_Height);
GEN_BUFFER_RGBA(m_ColourTex2, 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_ColourTex1, 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(L"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_ColourTex2, 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(L"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(L"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("BLOOM_NOP", "1");
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("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("renderedTex", renderedTex);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, inWidth / 2, inHeight / 2);
glBegin(GL_QUADS);
glColor4f(1.f, 1.f, 1.f, 1.f);
glTexCoord2f(1.0, 1.0); glVertex2f(1,1);
glTexCoord2f(0.0, 1.0); glVertex2f(-1,1);
glTexCoord2f(0.0, 0.0); glVertex2f(-1,-1);
glTexCoord2f(1.0, 0.0); glVertex2f(1,-1);
glEnd();
glPopAttrib();
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("BLOOM_PASS_H", "1");
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("bloom"),
g_Renderer.GetSystemShaderDefines(), defines2);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->BindTexture("renderedTex", inOutTex);
shader->Uniform("texSize", inWidth, inHeight, 0.0f, 0.0f);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, inWidth, inHeight);
glBegin(GL_QUADS);
glColor4f(1.f, 1.f, 1.f, 1.f);
glTexCoord2f(1.0, 1.0); glVertex2f(1,1);
glTexCoord2f(0.0, 1.0); glVertex2f(-1,1);
glTexCoord2f(0.0, 0.0); glVertex2f(-1,-1);
glTexCoord2f(1.0, 0.0); glVertex2f(1,-1);
glEnd();
glPopAttrib();
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("BLOOM_PASS_V", "1");
tech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("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("renderedTex", tempTex);
shader->Uniform("texSize", inWidth, inHeight, 0.0f, 0.0f);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, inWidth, inHeight);
glBegin(GL_QUADS);
glColor4f(1.f, 1.f, 1.f, 1.f);
glTexCoord2f(1.0, 1.0); glVertex2f(1,1);
glTexCoord2f(0.0, 1.0); glVertex2f(-1,1);
glTexCoord2f(0.0, 0.0); glVertex2f(-1,-1);
glTexCoord2f(1.0, 0.0); glVertex2f(1,-1);
glEnd();
glPopAttrib();
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_ColourTex1 : m_ColourTex2, 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()
{
// 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()
{
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::LoadEffect(CStrW &name)
{
if (!m_IsInitialised)
return;
if (name != L"default")
{
CStrW n = L"postproc/" + name;
m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(n.ToUTF8().c_str());
}
m_PostProcEffect = name;
}
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("renderedTex", m_ColourTex1);
else
shader->BindTexture("renderedTex", m_ColourTex2);
shader->BindTexture("depthTex", m_DepthTex);
shader->BindTexture("blurTex2", m_BlurTex2a);
shader->BindTexture("blurTex4", m_BlurTex4a);
shader->BindTexture("blurTex8", m_BlurTex8a);
shader->Uniform("width", m_Width);
shader->Uniform("height", m_Height);
shader->Uniform("zNear", g_Game->GetView()->GetNear());
shader->Uniform("zFar", g_Game->GetView()->GetFar());
shader->Uniform("brightness", g_LightEnv.m_Brightness);
shader->Uniform("hdr", g_LightEnv.m_Contrast);
shader->Uniform("saturation", g_LightEnv.m_Saturation);
shader->Uniform("bloom", g_LightEnv.m_Bloom);
glBegin(GL_QUADS);
glColor4f(1.f, 1.f, 1.f, 1.f);
glTexCoord2f(1.0, 1.0); glVertex2f(1,1);
glTexCoord2f(0.0, 1.0); glVertex2f(-1,1);
glTexCoord2f(0.0, 0.0); glVertex2f(-1,-1);
glTexCoord2f(1.0, 0.0); glVertex2f(1,-1);
glEnd();
shader->Unbind();
shaderTech1->EndPass(pass);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
m_WhichBuffer = !m_WhichBuffer;
}
void CPostprocManager::ApplyPostproc()
{
if (!m_IsInitialised)
return;
// 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<CStrW> CPostprocManager::GetPostEffects() const
{
std::vector<CStrW> effects;
const VfsPath path(L"shaders/effects/postproc/");
VfsPaths pathnames;
if(vfs::GetPathnames(g_VFS, path, 0, pathnames) < 0)
{
LOGERROR(L"Error finding Post effects in '%ls'", path.string().c_str());
}
for(size_t i = 0; i < pathnames.size(); i++)
{
if (pathnames[i].Extension() != L".xml")
continue;
effects.push_back(pathnames[i].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(CStrW name)
{
LoadEffect(name);
}