Index: ps/trunk/source/graphics/ShaderProgram.cpp
===================================================================
--- ps/trunk/source/graphics/ShaderProgram.cpp (revision 26166)
+++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 26167)
@@ -1,940 +1,941 @@
/* Copyright (C) 2021 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 "ShaderProgram.h"
#include "graphics/Color.h"
#include "graphics/PreprocessorWrapper.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextureManager.h"
#include "lib/timer.h"
#include "lib/res/graphics/ogl_tex.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include
#if !CONFIG2_GLES
class CShaderProgramARB : public CShaderProgram
{
public:
CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexIndexes, const std::map& fragmentIndexes,
int streamflags) :
CShaderProgram(streamflags),
m_VertexFile(vertexFile), m_FragmentFile(fragmentFile),
m_Defines(defines),
m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes)
{
glGenProgramsARB(1, &m_VertexProgram);
glGenProgramsARB(1, &m_FragmentProgram);
}
~CShaderProgramARB()
{
Unload();
glDeleteProgramsARB(1, &m_VertexProgram);
glDeleteProgramsARB(1, &m_FragmentProgram);
}
bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code)
{
ogl_WarnIfError();
glBindProgramARB(target, program);
ogl_WarnIfError();
glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str());
if (ogl_SquelchError(GL_INVALID_OPERATION))
{
GLint errPos = 0;
glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos);
int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1;
char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr);
return false;
}
glBindProgramARB(target, 0);
ogl_WarnIfError();
return true;
}
void Reload() override
{
Unload();
CVFSFile vertexFile;
if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK)
return;
CVFSFile fragmentFile;
if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK)
return;
CPreprocessorWrapper preprocessor;
preprocessor.AddDefines(m_Defines);
CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString());
CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString());
if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode))
return;
if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode))
return;
m_IsValid = true;
}
void Unload()
{
m_IsValid = false;
}
void Bind() override
{
glEnable(GL_VERTEX_PROGRAM_ARB);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram);
BindClientStates();
}
void Unbind() override
{
glDisable(GL_VERTEX_PROGRAM_ARB);
glDisable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
UnbindClientStates();
// TODO: should unbind textures, probably
}
int GetUniformVertexIndex(CStrIntern id)
{
std::map::iterator it = m_VertexIndexes.find(id);
if (it == m_VertexIndexes.end())
return -1;
return it->second;
}
frag_index_pair_t GetUniformFragmentIndex(CStrIntern id)
{
std::map::iterator it = m_FragmentIndexes.find(id);
if (it == m_FragmentIndexes.end())
return std::make_pair(-1, 0);
return it->second;
}
Binding GetTextureBinding(texture_id_t id) override
{
frag_index_pair_t fPair = GetUniformFragmentIndex(id);
int index = fPair.first;
if (index == -1)
return Binding();
else
return Binding((int)fPair.second, index);
}
void BindTexture(texture_id_t id, GLuint tex) override
{
frag_index_pair_t fPair = GetUniformFragmentIndex(id);
int index = fPair.first;
if (index != -1)
{
glActiveTextureARB(GL_TEXTURE0 + index);
glBindTexture(fPair.second, tex);
}
}
void BindTexture(Binding id, GLuint tex) override
{
int index = id.second;
if (index != -1)
{
glActiveTextureARB(GL_TEXTURE0 + index);
glBindTexture(id.first, tex);
}
}
Binding GetUniformBinding(uniform_id_t id) override
{
return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first);
}
void Uniform(Binding id, float v0, float v1, float v2, float v3) override
{
if (id.first != -1)
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3);
if (id.second != -1)
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3);
}
void Uniform(Binding id, const CMatrix3D& v) override
{
if (id.first != -1)
{
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44);
}
if (id.second != -1)
{
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44);
}
}
void Uniform(Binding id, size_t count, const CMatrix3D* v) override
{
ENSURE(count == 1);
Uniform(id, v[0]);
}
void Uniform(Binding id, size_t count, const float* v) override
{
ENSURE(count == 4);
Uniform(id, v[0], v[1], v[2], v[3]);
}
std::vector GetFileDependencies() const override
{
return {m_VertexFile, m_FragmentFile};
}
private:
VfsPath m_VertexFile;
VfsPath m_FragmentFile;
CShaderDefines m_Defines;
GLuint m_VertexProgram;
GLuint m_FragmentProgram;
std::map m_VertexIndexes;
// pair contains
std::map m_FragmentIndexes;
};
#endif // #if !CONFIG2_GLES
//////////////////////////////////////////////////////////////////////////
TIMER_ADD_CLIENT(tc_ShaderGLSLCompile);
TIMER_ADD_CLIENT(tc_ShaderGLSLLink);
class CShaderProgramGLSL : public CShaderProgram
{
public:
CShaderProgramGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexAttribs,
int streamflags) :
CShaderProgram(streamflags),
m_VertexFile(vertexFile), m_FragmentFile(fragmentFile),
m_Defines(defines),
m_VertexAttribs(vertexAttribs)
{
m_Program = 0;
m_VertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER);
m_FragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
m_FileDependencies = {m_VertexFile, m_FragmentFile};
}
~CShaderProgramGLSL()
{
Unload();
glDeleteShader(m_VertexShader);
glDeleteShader(m_FragmentShader);
}
bool Compile(GLhandleARB shader, const VfsPath& file, const CStr& code)
{
TIMER_ACCRUE(tc_ShaderGLSLCompile);
ogl_WarnIfError();
const char* code_string = code.c_str();
GLint code_length = code.length();
glShaderSourceARB(shader, 1, &code_string, &code_length);
glCompileShaderARB(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
GLint length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
// Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0
// (http://code.google.com/p/android/issues/detail?id=9953)
if (!ok && length == 0)
length = 4096;
if (length > 1)
{
char* infolog = new char[length];
glGetShaderInfoLog(shader, length, NULL, infolog);
if (ok)
LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog);
else
LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog);
delete[] infolog;
}
ogl_WarnIfError();
return (ok ? true : false);
}
bool Link()
{
TIMER_ACCRUE(tc_ShaderGLSLLink);
ENSURE(!m_Program);
m_Program = glCreateProgramObjectARB();
glAttachObjectARB(m_Program, m_VertexShader);
ogl_WarnIfError();
glAttachObjectARB(m_Program, m_FragmentShader);
ogl_WarnIfError();
// Set up the attribute bindings explicitly, since apparently drivers
// don't always pick the most efficient bindings automatically,
// and also this lets us hardcode indexes into VertexPointer etc
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glBindAttribLocationARB(m_Program, it->second, it->first.c_str());
glLinkProgramARB(m_Program);
GLint ok = 0;
glGetProgramiv(m_Program, GL_LINK_STATUS, &ok);
GLint length = 0;
glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length);
if (!ok && length == 0)
length = 4096;
if (length > 1)
{
char* infolog = new char[length];
glGetProgramInfoLog(m_Program, length, NULL, infolog);
if (ok)
LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog);
else
LOGERROR("Failed to link program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog);
delete[] infolog;
}
ogl_WarnIfError();
if (!ok)
return false;
m_Uniforms.clear();
m_Samplers.clear();
Bind();
ogl_WarnIfError();
GLint numUniforms = 0;
glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms);
ogl_WarnIfError();
for (GLint i = 0; i < numUniforms; ++i)
{
char name[256] = {0};
GLsizei nameLength = 0;
GLint size = 0;
GLenum type = 0;
glGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name);
ogl_WarnIfError();
GLint loc = glGetUniformLocationARB(m_Program, name);
CStrIntern nameIntern(name);
m_Uniforms[nameIntern] = std::make_pair(loc, type);
// Assign sampler uniforms to sequential texture units
if (type == GL_SAMPLER_2D
|| type == GL_SAMPLER_CUBE
#if !CONFIG2_GLES
|| type == GL_SAMPLER_2D_SHADOW
#endif
)
{
int unit = (int)m_Samplers.size();
m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D);
m_Samplers[nameIntern].second = unit;
glUniform1iARB(loc, unit); // link uniform to unit
ogl_WarnIfError();
}
}
// TODO: verify that we're not using more samplers than is supported
Unbind();
ogl_WarnIfError();
return true;
}
void Reload() override
{
Unload();
CVFSFile vertexFile;
if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK)
return;
CVFSFile fragmentFile;
if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK)
return;
std::vector newFileDependencies = {m_VertexFile, m_FragmentFile};
CPreprocessorWrapper preprocessor([&newFileDependencies](const CStr& includePath, CStr& out) -> bool {
const VfsPath includeFilePath(L"shaders/glsl/" + wstring_from_utf8(includePath));
// Add dependencies anyway to reload the shader when the file is
// appeared.
newFileDependencies.push_back(includeFilePath);
CVFSFile includeFile;
if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK)
return false;
out = includeFile.GetAsString();
return true;
});
preprocessor.AddDefines(m_Defines);
#if CONFIG2_GLES
// GLES defines the macro "GL_ES" in its GLSL preprocessor,
// but since we run our own preprocessor first, we need to explicitly
// define it here
preprocessor.AddDefine("GL_ES", "1");
#endif
CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString());
CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString());
m_FileDependencies = std::move(newFileDependencies);
if (vertexCode.empty())
LOGERROR("Failed to preprocess vertex shader: '%s'", m_VertexFile.string8());
if (fragmentCode.empty())
LOGERROR("Failed to preprocess fragment shader: '%s'", m_FragmentFile.string8());
#if CONFIG2_GLES
// Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00,
// and also to set default float precision for fragment shaders
vertexCode.Replace("#version 110\n", "#version 100\n");
vertexCode.Replace("#version 110\r\n", "#version 100\n");
vertexCode.Replace("#version 120\n", "#version 100\n");
vertexCode.Replace("#version 120\r\n", "#version 100\n");
fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n");
#endif
if (!Compile(m_VertexShader, m_VertexFile, vertexCode))
return;
if (!Compile(m_FragmentShader, m_FragmentFile, fragmentCode))
return;
if (!Link())
return;
m_IsValid = true;
}
void Unload()
{
m_IsValid = false;
if (m_Program)
glDeleteProgram(m_Program);
m_Program = 0;
// The shader objects can be reused and don't need to be deleted here
}
void Bind() override
{
glUseProgramObjectARB(m_Program);
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glEnableVertexAttribArrayARB(it->second);
}
void Unbind() override
{
glUseProgramObjectARB(0);
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glDisableVertexAttribArrayARB(it->second);
// TODO: should unbind textures, probably
}
Binding GetTextureBinding(texture_id_t id) override
{
std::map >::iterator it = m_Samplers.find(CStrIntern(id));
if (it == m_Samplers.end())
return Binding();
else
return Binding((int)it->second.first, it->second.second);
}
void BindTexture(texture_id_t id, GLuint tex) override
{
std::map >::iterator it = m_Samplers.find(CStrIntern(id));
if (it == m_Samplers.end())
return;
glActiveTextureARB(GL_TEXTURE0 + it->second.second);
glBindTexture(it->second.first, tex);
}
void BindTexture(Binding id, GLuint tex) override
{
if (id.second == -1)
return;
glActiveTextureARB(GL_TEXTURE0 + id.second);
glBindTexture(id.first, tex);
}
Binding GetUniformBinding(uniform_id_t id) override
{
std::map >::iterator it = m_Uniforms.find(id);
if (it == m_Uniforms.end())
return Binding();
else
return Binding(it->second.first, (int)it->second.second);
}
void Uniform(Binding id, float v0, float v1, float v2, float v3) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT)
glUniform1fARB(id.first, v0);
else if (id.second == GL_FLOAT_VEC2)
glUniform2fARB(id.first, v0, v1);
else if (id.second == GL_FLOAT_VEC3)
glUniform3fARB(id.first, v0, v1, v2);
else if (id.second == GL_FLOAT_VEC4)
glUniform4fARB(id.first, v0, v1, v2, v3);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)");
}
}
void Uniform(Binding id, const CMatrix3D& v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT_MAT4)
glUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)");
}
}
void Uniform(Binding id, size_t count, const CMatrix3D* v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT_MAT4)
glUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)");
}
}
void Uniform(Binding id, size_t count, const float* v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT)
glUniform1fvARB(id.first, count, v);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float)");
}
}
// Map the various fixed-function Pointer functions onto generic vertex attributes
// (matching the attribute indexes from ShaderManager's ParseAttribSemantics):
void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer);
m_ValidStreams |= STREAM_POS;
}
void NormalPointer(GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(2, 3, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer);
m_ValidStreams |= STREAM_NORMAL;
}
void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(3, size, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer);
m_ValidStreams |= STREAM_COLOR;
}
void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer);
m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0);
}
void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) override
{
std::map::iterator it = m_VertexAttribs.find(id);
if (it != m_VertexAttribs.end())
{
glVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer);
}
}
void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
std::map::iterator it = m_VertexAttribs.find(id);
if (it != m_VertexAttribs.end())
{
#if CONFIG2_GLES
+ UNUSED2(size); UNUSED2(type); UNUSED2(stride); UNUSED2(pointer);
debug_warn(L"glVertexAttribIPointer not supported on GLES");
#else
glVertexAttribIPointerEXT(it->second, size, type, stride, pointer);
#endif
}
}
std::vector GetFileDependencies() const override
{
return m_FileDependencies;
}
private:
VfsPath m_VertexFile;
VfsPath m_FragmentFile;
std::vector m_FileDependencies;
CShaderDefines m_Defines;
std::map m_VertexAttribs;
GLhandleARB m_Program;
GLhandleARB m_VertexShader;
GLhandleARB m_FragmentShader;
std::map > m_Uniforms;
std::map > m_Samplers; // texture target & unit chosen for each uniform sampler
};
//////////////////////////////////////////////////////////////////////////
CShaderProgram::CShaderProgram(int streamflags)
: m_IsValid(false), m_StreamFlags(streamflags), m_ValidStreams(0)
{
}
#if CONFIG2_GLES
/*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& UNUSED(defines),
const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes),
int UNUSED(streamflags))
{
LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device",
vertexFile.string8(), fragmentFile.string8());
return NULL;
}
#else
/*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexIndexes, const std::map& fragmentIndexes,
int streamflags)
{
return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags);
}
#endif
/*static*/ CShaderProgram* CShaderProgram::ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexAttribs,
int streamflags)
{
return new CShaderProgramGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamflags);
}
bool CShaderProgram::IsValid() const
{
return m_IsValid;
}
int CShaderProgram::GetStreamFlags() const
{
return m_StreamFlags;
}
void CShaderProgram::BindTexture(texture_id_t id, const CTexturePtr& tex)
{
GLuint h;
ogl_tex_get_texture_id(tex->GetHandle(), &h);
BindTexture(id, h);
}
void CShaderProgram::BindTexture(Binding id, const CTexturePtr& tex)
{
GLuint h;
ogl_tex_get_texture_id(tex->GetHandle(), &h);
BindTexture(id, h);
}
void CShaderProgram::BindTexture(texture_id_t id, const Renderer::Backend::GL::CTexture* tex)
{
BindTexture(id, tex->GetHandle());
}
void CShaderProgram::BindTexture(Binding id, const Renderer::Backend::GL::CTexture* tex)
{
BindTexture(id, tex->GetHandle());
}
void CShaderProgram::Uniform(Binding id, int v)
{
Uniform(id, (float)v, (float)v, (float)v, (float)v);
}
void CShaderProgram::Uniform(Binding id, float v)
{
Uniform(id, v, v, v, v);
}
void CShaderProgram::Uniform(Binding id, float v0, float v1)
{
Uniform(id, v0, v1, 0.0f, 0.0f);
}
void CShaderProgram::Uniform(Binding id, const CVector3D& v)
{
Uniform(id, v.X, v.Y, v.Z, 0.0f);
}
void CShaderProgram::Uniform(Binding id, const CColor& v)
{
Uniform(id, v.r, v.g, v.b, v.a);
}
void CShaderProgram::Uniform(uniform_id_t id, int v)
{
Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v);
}
void CShaderProgram::Uniform(uniform_id_t id, float v)
{
Uniform(GetUniformBinding(id), v, v, v, v);
}
void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1)
{
Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f);
}
void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v)
{
Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f);
}
void CShaderProgram::Uniform(uniform_id_t id, const CColor& v)
{
Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a);
}
void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3)
{
Uniform(GetUniformBinding(id), v0, v1, v2, v3);
}
void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v)
{
Uniform(GetUniformBinding(id), v);
}
void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v)
{
Uniform(GetUniformBinding(id), count, v);
}
void CShaderProgram::Uniform(uniform_id_t id, size_t count, const float* v)
{
Uniform(GetUniformBinding(id), count, v);
}
// These should all be overridden by CShaderProgramGLSL, and not used
// if a non-GLSL shader was loaded instead:
void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type),
GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("Shader type doesn't support VertexAttribPointer");
}
void CShaderProgram::VertexAttribIPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type),
GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("Shader type doesn't support VertexAttribIPointer");
}
#if CONFIG2_GLES
// These should all be overridden by CShaderProgramGLSL
// (GLES doesn't support any other types of shader program):
void CShaderProgram::VertexPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::VertexPointer should be overridden");
}
void CShaderProgram::NormalPointer(GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::NormalPointer should be overridden");
}
void CShaderProgram::ColorPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::ColorPointer should be overridden");
}
void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::TexCoordPointer should be overridden");
}
#else
// These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders
// both use the fixed-function vertex attribute pointers so we'll share their
// definitions here:
void CShaderProgram::VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glVertexPointer(size, type, stride, pointer);
m_ValidStreams |= STREAM_POS;
}
void CShaderProgram::NormalPointer(GLenum type, GLsizei stride, const void* pointer)
{
glNormalPointer(type, stride, pointer);
m_ValidStreams |= STREAM_NORMAL;
}
void CShaderProgram::ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glColorPointer(size, type, stride, pointer);
m_ValidStreams |= STREAM_COLOR;
}
void CShaderProgram::TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glClientActiveTextureARB(texture);
glTexCoordPointer(size, type, stride, pointer);
glClientActiveTextureARB(GL_TEXTURE0);
m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0);
}
void CShaderProgram::BindClientStates()
{
ENSURE(m_StreamFlags == (m_StreamFlags & (STREAM_POS|STREAM_NORMAL|STREAM_COLOR|STREAM_UV0|STREAM_UV1)));
// Enable all the desired client states for non-GLSL rendering
if (m_StreamFlags & STREAM_POS) glEnableClientState(GL_VERTEX_ARRAY);
if (m_StreamFlags & STREAM_NORMAL) glEnableClientState(GL_NORMAL_ARRAY);
if (m_StreamFlags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY);
if (m_StreamFlags & STREAM_UV0)
{
glClientActiveTextureARB(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (m_StreamFlags & STREAM_UV1)
{
glClientActiveTextureARB(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0);
}
// Rendering code must subsequently call VertexPointer etc for all of the streams
// that were activated in this function, else AssertPointersBound will complain
// that some arrays were unspecified
m_ValidStreams = 0;
}
void CShaderProgram::UnbindClientStates()
{
if (m_StreamFlags & STREAM_POS) glDisableClientState(GL_VERTEX_ARRAY);
if (m_StreamFlags & STREAM_NORMAL) glDisableClientState(GL_NORMAL_ARRAY);
if (m_StreamFlags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY);
if (m_StreamFlags & STREAM_UV0)
{
glClientActiveTextureARB(GL_TEXTURE0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (m_StreamFlags & STREAM_UV1)
{
glClientActiveTextureARB(GL_TEXTURE1);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0);
}
}
#endif // !CONFIG2_GLES
void CShaderProgram::AssertPointersBound()
{
ENSURE((m_StreamFlags & ~m_ValidStreams) == 0);
}
Index: ps/trunk/source/lib/external_libraries/opengles2_wrapper.h
===================================================================
--- ps/trunk/source/lib/external_libraries/opengles2_wrapper.h (revision 26166)
+++ ps/trunk/source/lib/external_libraries/opengles2_wrapper.h (revision 26167)
@@ -1,116 +1,121 @@
/* Copyright (C) 2022 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef INCLUDED_GLES2_WRAPPER
#define INCLUDED_GLES2_WRAPPER
#include "lib/config2.h" // CONFIG2_GLES
#if CONFIG2_GLES
#include
#define GL_FRAMEBUFFER_BINDING_EXT GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
#define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER
#define GL_WRITE_ONLY GL_WRITE_ONLY_OES
#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0
#define GL_DEPTH_ATTACHMENT_EXT GL_DEPTH_ATTACHMENT
// Functions
#define glActiveTextureARB glActiveTexture
#define glBlendColorEXT glBlendColor
#define glBlendEquationEXT glBlendEquation
#define glCompressedTexImage2DARB glCompressedTexImage2D
#define glAttachObjectARB glAttachShader
#define glBindAttribLocationARB glBindAttribLocation
#define glCompileShaderARB glCompileShader
#define glCreateProgramObjectARB glCreateProgram
#define glCreateShaderObjectARB glCreateShader
#define glDisableVertexAttribArrayARB glDisableVertexAttribArray
#define glEnableVertexAttribArrayARB glEnableVertexAttribArray
#define glGetActiveUniformARB glGetActiveUniform
#define glGetUniformLocationARB glGetUniformLocation
#define glLinkProgramARB glLinkProgram
#define glShaderSourceARB glShaderSource
#define glUniform1fARB glUniform1f
#define glUniform2fARB glUniform2f
#define glUniform3fARB glUniform3f
#define glUniform4fARB glUniform4f
#define glUniform1iARB glUniform1i
#define glUniform1fvARB glUniform1fv
#define glUniformMatrix4fvARB glUniformMatrix4fv
#define glUseProgramObjectARB glUseProgram
#define glVertexAttribPointerARB glVertexAttribPointer
#define glBindBufferARB glBindBuffer
#define glBufferDataARB glBufferData
#define glBufferSubDataARB glBufferSubData
#define glDeleteBuffersARB glDeleteBuffers
#define glGenBuffersARB glGenBuffers
#define glBindFramebufferEXT glBindFramebuffer
#define glCheckFramebufferStatusEXT glCheckFramebufferStatus
#define glDeleteFramebuffersEXT glDeleteFramebuffers
#define glFramebufferTexture2DEXT glFramebufferTexture2D
#define glGenFramebuffersEXT glGenFramebuffers
// Extensions
// GL_OES_texture_border_clamp
#define GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_OES
#define GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_OES
// GL_OES_rgb8_rgba8
#define GL_RGBA8 GL_RGBA8_OES
// GL_OES_mapbuffer
#define glMapBufferARB glMapBufferOES
#define glUnmapBufferARB glUnmapBufferOES
// GL_OES_depth32
#define GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT32_OES
// GL_KHR_debug
-#define glPopDebugGroupKHR glPopDebugGroup
-#define glPushDebugGroupKHR glPushDebugGroup
-#define GL_DEBUG_SOURCE_API_KHR GL_DEBUG_SOURCE_API
-#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR GL_DEBUG_SOURCE_WINDOW_SYSTEM
-#define GL_DEBUG_SOURCE_SHADER_COMPILER_KHR GL_DEBUG_SOURCE_SHADER_COMPILER
-#define GL_DEBUG_SOURCE_THIRD_PARTY_KHR GL_DEBUG_SOURCE_THIRD_PARTY
-#define GL_DEBUG_SOURCE_APPLICATION_KHR GL_DEBUG_SOURCE_APPLICATION
-#define GL_DEBUG_SOURCE_OTHER_KHR GL_DEBUG_SOURCE_OTHER
-#define GL_DEBUG_TYPE_ERROR_KHR GL_DEBUG_TYPE_ERROR
-#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR
-#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR
-#define GL_DEBUG_TYPE_PORTABILITY_KHR GL_DEBUG_TYPE_PORTABILITY
-#define GL_DEBUG_TYPE_PERFORMANCE_KHR GL_DEBUG_TYPE_PERFORMANCE
-#define GL_DEBUG_TYPE_OTHER_KHR GL_DEBUG_TYPE_OTHER
-#define GL_DEBUG_TYPE_MARKER_KHR GL_DEBUG_TYPE_MARKER
-#define GL_DEBUG_TYPE_PUSH_GROUP_KHR GL_DEBUG_TYPE_PUSH_GROUP
-#define GL_DEBUG_TYPE_POP_GROUP_KHR GL_DEBUG_TYPE_POP_GROUP
-#define GL_DEBUG_SEVERITY_HIGH_KHR GL_DEBUG_SEVERITY_HIGH
-#define GL_DEBUG_SEVERITY_MEDIUM_KHR GL_DEBUG_SEVERITY_MEDIUM
-#define GL_DEBUG_SEVERITY_LOW_KHR GL_DEBUG_SEVERITY_LOW
-#define GL_DEBUG_SEVERITY_NOTIFICATION_KHR GL_DEBUG_SEVERITY_NOTIFICATION
+#define glDebugMessageCallback glDebugMessageCallbackKHR
+#define glDebugMessageControl glDebugMessageControlKHR
+#define glObjectLabel glObjectLabelKHR
+#define glPopDebugGroup glPopDebugGroupKHR
+#define glPushDebugGroup glPushDebugGroupKHR
+
+#define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
+#define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
+#define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
+#define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
+#define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
+#define GL_DEBUG_SOURCE_API GL_DEBUG_SOURCE_API_KHR
+#define GL_DEBUG_SOURCE_APPLICATION GL_DEBUG_SOURCE_APPLICATION_KHR
+#define GL_DEBUG_SOURCE_OTHER GL_DEBUG_SOURCE_OTHER_KHR
+#define GL_DEBUG_SOURCE_SHADER_COMPILER GL_DEBUG_SOURCE_SHADER_COMPILER_KHR
+#define GL_DEBUG_SOURCE_THIRD_PARTY GL_DEBUG_SOURCE_THIRD_PARTY_KHR
+#define GL_DEBUG_SOURCE_WINDOW_SYSTEM GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR
+#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
+#define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
+#define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
+#define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
+#define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
+#define GL_DEBUG_TYPE_POP_GROUP GL_DEBUG_TYPE_POP_GROUP_KHR
+#define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
+#define GL_DEBUG_TYPE_PUSH_GROUP GL_DEBUG_TYPE_PUSH_GROUP_KHR
+#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
#endif // CONFIG2_GLES
#endif // !INCLUDED_GLES2_WRAPPER
Index: ps/trunk/source/renderer/DebugRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/DebugRenderer.cpp (revision 26166)
+++ ps/trunk/source/renderer/DebugRenderer.cpp (revision 26167)
@@ -1,409 +1,412 @@
/* Copyright (C) 2022 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 "renderer/DebugRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Color.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "lib/ogl.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CStrInternStatic.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include
void CDebugRenderer::DrawLine(const CVector3D& from, const CVector3D& to, const CColor& color, const float width)
{
if (from == to)
return;
DrawLine({from, to}, color, width);
}
void CDebugRenderer::DrawLine(const std::vector& line, const CColor& color, const float width)
{
#if CONFIG2_GLES
+ UNUSED2(line); UNUSED2(color); UNUSED2(width);
#warning TODO: implement drawing line for GLES
#else
CShaderTechniquePtr debugLineTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
debugLineTech->BeginPass();
const CCamera& viewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderProgramPtr debugLineShader = debugLineTech->GetShader();
debugLineShader->Uniform(str_transform, viewCamera.GetViewProjection());
debugLineShader->Uniform(str_color, color);
const CVector3D cameraIn = viewCamera.GetOrientation().GetIn();
std::vector vertices;
vertices.reserve(line.size() * 6 * 3);
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
for (size_t idx = 1; idx < line.size(); ++idx)
{
const CVector3D from = line[idx - 1];
const CVector3D to = line[idx];
const CVector3D direction = (to - from).Normalized();
const CVector3D view = direction.Dot(cameraIn) > 0.9f ?
CVector3D(0.0f, 1.0f, 0.0f) :
cameraIn;
const CVector3D offset = view.Cross(direction).Normalized() * width;
ADD(from + offset)
ADD(to - offset)
ADD(to + offset)
ADD(from + offset)
ADD(from - offset)
ADD(to - offset)
}
#undef ADD
debugLineShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
debugLineShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3);
debugLineTech->EndPass();
#endif
}
void CDebugRenderer::DrawCircle(const CVector3D& origin, const float radius, const CColor& color)
{
#if CONFIG2_GLES
+ UNUSED2(origin); UNUSED2(radius); UNUSED2(color);
#warning TODO: implement drawing circle for GLES
#else
CShaderTechniquePtr debugCircleTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
debugCircleTech->BeginPass();
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderProgramPtr debugCircleShader = debugCircleTech->GetShader();
debugCircleShader->Uniform(str_transform, camera.GetViewProjection());
debugCircleShader->Uniform(str_color, color);
const CVector3D cameraUp = camera.GetOrientation().GetUp();
const CVector3D cameraLeft = camera.GetOrientation().GetLeft();
std::vector vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
ADD(origin)
constexpr size_t segments = 16;
for (size_t idx = 0; idx <= segments; ++idx)
{
const float angle = M_PI * 2.0f * idx / segments;
const CVector3D offset = cameraUp * sin(angle) - cameraLeft * cos(angle);
ADD(origin + offset * radius)
}
#undef ADD
debugCircleShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
debugCircleShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 3);
debugCircleTech->EndPass();
#endif
}
void CDebugRenderer::DrawCameraFrustum(const CCamera& camera, const CColor& color, int intermediates)
{
#if CONFIG2_GLES
-#warning TODO: implement camera frustum for GLES
+ UNUSED2(camera); UNUSED2(color); UNUSED2(intermediates);
+ #warning TODO: implement camera frustum for GLES
#else
CCamera::Quad nearPoints;
CCamera::Quad farPoints;
camera.GetViewQuad(camera.GetNearPlane(), nearPoints);
camera.GetViewQuad(camera.GetFarPlane(), farPoints);
for(int i = 0; i < 4; i++)
{
nearPoints[i] = camera.m_Orientation.Transform(nearPoints[i]);
farPoints[i] = camera.m_Orientation.Transform(farPoints[i]);
}
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
std::vector vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
// Near plane.
ADD(nearPoints[0]);
ADD(nearPoints[1]);
ADD(nearPoints[2]);
ADD(nearPoints[3]);
// Far plane.
ADD(farPoints[0]);
ADD(farPoints[1]);
ADD(farPoints[2]);
ADD(farPoints[3]);
// Intermediate planes.
CVector3D intermediatePoints[4];
for(int i = 0; i < intermediates; ++i)
{
const float t = (i + 1.0f) / (intermediates + 1.0f);
for(int j = 0; j < 4; ++j)
intermediatePoints[j] = nearPoints[j] * t + farPoints[j] * (1.0f - t);
ADD(intermediatePoints[0]);
ADD(intermediatePoints[1]);
ADD(intermediatePoints[2]);
ADD(intermediatePoints[3]);
}
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUADS, 0, vertices.size() / 3);
vertices.clear();
// Connection lines.
ADD(nearPoints[0]);
ADD(farPoints[0]);
ADD(nearPoints[1]);
ADD(farPoints[1]);
ADD(nearPoints[2]);
ADD(farPoints[2]);
ADD(nearPoints[3]);
ADD(farPoints[3]);
ADD(nearPoints[0]);
ADD(farPoints[0]);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUAD_STRIP, 0, vertices.size() / 3);
#undef ADD
overlayTech->EndPass();
#endif
}
void CDebugRenderer::DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color)
{
DrawBoundingBox(boundingBox, color, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
}
void CDebugRenderer::DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, transform);
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(boundingBox[x].X); \
data.push_back(boundingBox[y].Y); \
data.push_back(boundingBox[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6*6);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color)
{
DrawBoundingBoxOutline(boundingBox, color, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
}
void CDebugRenderer::DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, transform);
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); \
ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); \
ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(boundingBox[x].X); \
data.push_back(boundingBox[y].Y); \
data.push_back(boundingBox[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, 6*8);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBrush(const CBrush& brush, const CColor& color)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
std::vector data;
std::vector> faces;
brush.GetFaces(faces);
#define ADD_VERT(a) \
STMT( \
data.push_back(u); \
data.push_back(v); \
data.push_back(brush.GetVertices()[faces[i][a]].X); \
data.push_back(brush.GetVertices()[faces[i][a]].Y); \
data.push_back(brush.GetVertices()[faces[i][a]].Z); \
)
for (size_t i = 0; i < faces.size(); ++i)
{
// Triangulate into (0,1,2), (0,2,3), ...
for (size_t j = 1; j < faces[i].size() - 2; ++j)
{
float u = 0;
float v = 0;
ADD_VERT(0);
ADD_VERT(j);
ADD_VERT(j+1);
}
}
#undef ADD_VERT
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, data.size() / 5);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBrushOutline(const CBrush& brush, const CColor& color)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
std::vector data;
std::vector> faces;
brush.GetFaces(faces);
#define ADD_VERT(a) \
STMT( \
data.push_back(u); \
data.push_back(v); \
data.push_back(brush.GetVertices()[faces[i][a]].X); \
data.push_back(brush.GetVertices()[faces[i][a]].Y); \
data.push_back(brush.GetVertices()[faces[i][a]].Z); \
)
for (size_t i = 0; i < faces.size(); ++i)
{
for (size_t j = 0; j < faces[i].size() - 1; ++j)
{
float u = 0;
float v = 0;
ADD_VERT(j);
ADD_VERT(j+1);
}
}
#undef ADD_VERT
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, data.size() / 5);
shaderTech->EndPass();
}
Index: ps/trunk/source/renderer/OverlayRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26166)
+++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26167)
@@ -1,766 +1,767 @@
/* Copyright (C) 2022 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 "OverlayRenderer.h"
#include "graphics/Camera.h"
#include "graphics/LOSTexture.h"
#include "graphics/Overlay.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "lib/hash.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Quaternion.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/DebugRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TexturedLineRData.h"
#include "renderer/VertexArray.h"
#include "renderer/VertexBuffer.h"
#include "renderer/VertexBufferManager.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/SimContext.h"
#include
namespace
{
CShaderTechniquePtr GetOverlayLineShaderTechnique(const CShaderDefines& defines)
{
return g_Renderer.GetShaderManager().LoadEffect(str_overlay_line, defines);
}
} // anonymous namespace
/**
* Key used to group quads into batches for more efficient rendering. Currently groups by the combination
* of the main texture and the texture mask, to minimize texture swapping during rendering.
*/
struct QuadBatchKey
{
QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask)
: m_Texture(texture), m_TextureMask(textureMask)
{ }
bool operator==(const QuadBatchKey& other) const
{
return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask);
}
CTexturePtr m_Texture;
CTexturePtr m_TextureMask;
};
struct QuadBatchHash
{
std::size_t operator()(const QuadBatchKey& d) const
{
size_t seed = 0;
hash_combine(seed, d.m_Texture);
hash_combine(seed, d.m_TextureMask);
return seed;
}
};
/**
* Holds information about a single quad rendering batch.
*/
class QuadBatchData : public CRenderData
{
public:
QuadBatchData() : m_IndicesBase(0), m_NumRenderQuads(0) { }
/// Holds the quad overlay structures requested to be rendered in this batch. Must be cleared
/// after each frame.
std::vector m_Quads;
/// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals).
size_t m_IndicesBase;
/// Amount of quads to actually render in this batch. Potentially (although unlikely to be)
/// different from m_Quads.size() due to restrictions on the total amount of quads that can be
/// rendered. Must be reset after each frame.
size_t m_NumRenderQuads;
};
struct OverlayRendererInternals
{
using QuadBatchMap = std::unordered_map;
OverlayRendererInternals();
~OverlayRendererInternals(){ }
std::vector lines;
std::vector texlines;
std::vector sprites;
std::vector quads;
std::vector spheres;
QuadBatchMap quadBatchMap;
// Dedicated vertex/index buffers for rendering all quads (to within the limits set by
// MAX_QUAD_OVERLAYS).
VertexArray quadVertices;
VertexArray::Attribute quadAttributePos;
VertexArray::Attribute quadAttributeColor;
VertexArray::Attribute quadAttributeUV;
VertexIndexArray quadIndices;
/// Maximum amount of quad overlays we support for rendering. This limit is set to be able to
/// render all quads from a single dedicated VB without having to reallocate it, which is much
/// faster in the typical case of rendering only a handful of quads. When modifying this value,
/// you must take care for the new amount of quads to fit in a single VBO (which is not likely
/// to be a problem).
static const size_t MAX_QUAD_OVERLAYS = 1024;
// Sets of commonly-(re)used shader defines.
CShaderDefines defsOverlayLineNormal;
CShaderDefines defsOverlayLineAlwaysVisible;
CShaderDefines defsQuadOverlay;
// Geometry for a unit sphere
std::vector sphereVertexes;
std::vector sphereIndexes;
void GenerateSphere();
/// Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have
/// been detected. Note that no VBOs must be created before this is called, since the shader
/// path and graphics capabilities are not guaranteed to be stable before this point.
void Initialize();
};
const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f;
OverlayRendererInternals::OverlayRendererInternals()
: quadVertices(GL_DYNAMIC_DRAW), quadIndices(GL_STATIC_DRAW)
{
quadAttributePos.elems = 3;
quadAttributePos.type = GL_FLOAT;
quadVertices.AddAttribute(&quadAttributePos);
quadAttributeColor.elems = 4;
quadAttributeColor.type = GL_FLOAT;
quadVertices.AddAttribute(&quadAttributeColor);
quadAttributeUV.elems = 2;
quadAttributeUV.type = GL_SHORT; // don't use GL_UNSIGNED_SHORT here, TexCoordPointer won't accept it
quadVertices.AddAttribute(&quadAttributeUV);
// Note that we're reusing the textured overlay line shader for the quad overlay rendering. This
// is because their code is almost identical; the only difference is that for the quad overlays
// we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the
// shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define.
defsOverlayLineNormal.Add(str_USE_OBJECTCOLOR, str_1);
defsOverlayLineAlwaysVisible.Add(str_USE_OBJECTCOLOR, str_1);
defsOverlayLineAlwaysVisible.Add(str_IGNORE_LOS, str_1);
}
void OverlayRendererInternals::Initialize()
{
// Perform any initialization after graphics capabilities have been detected. Notably,
// only at this point can we safely allocate VBOs (in contrast to e.g. in the constructor),
// because their creation depends on the shader path, which is not reliably set before this point.
quadVertices.SetNumVertices(MAX_QUAD_OVERLAYS * 4);
quadVertices.Layout(); // allocate backing store
quadIndices.SetNumVertices(MAX_QUAD_OVERLAYS * 6);
quadIndices.Layout(); // allocate backing store
// Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the
// indices are always the same; we can therefore fill in all the indices once and pretty much forget about
// them. We then also no longer need its backing store, since we never change any indices afterwards.
VertexArrayIterator index = quadIndices.GetIterator();
for (u16 i = 0; i < static_cast(MAX_QUAD_OVERLAYS); ++i)
{
*index++ = i * 4 + 0;
*index++ = i * 4 + 1;
*index++ = i * 4 + 2;
*index++ = i * 4 + 2;
*index++ = i * 4 + 3;
*index++ = i * 4 + 0;
}
quadIndices.Upload();
quadIndices.FreeBackingStore();
}
OverlayRenderer::OverlayRenderer()
{
m = new OverlayRendererInternals();
}
OverlayRenderer::~OverlayRenderer()
{
delete m;
}
void OverlayRenderer::Initialize()
{
m->Initialize();
}
void OverlayRenderer::Submit(SOverlayLine* line)
{
m->lines.push_back(line);
}
void OverlayRenderer::Submit(SOverlayTexturedLine* line)
{
// Simplify the rest of the code by guaranteeing non-empty lines
if (line->m_Coords.empty())
return;
m->texlines.push_back(line);
}
void OverlayRenderer::Submit(SOverlaySprite* overlay)
{
m->sprites.push_back(overlay);
}
void OverlayRenderer::Submit(SOverlayQuad* overlay)
{
m->quads.push_back(overlay);
}
void OverlayRenderer::Submit(SOverlaySphere* overlay)
{
m->spheres.push_back(overlay);
}
void OverlayRenderer::EndFrame()
{
m->lines.clear();
m->texlines.clear();
m->sprites.clear();
m->quads.clear();
m->spheres.clear();
// this should leave the capacity unchanged, which is okay since it
// won't be very large or very variable
// Empty the batch rendering data structures, but keep their key mappings around for the next frames
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& quadBatchData = (it->second);
quadBatchData.m_Quads.clear();
quadBatchData.m_NumRenderQuads = 0;
quadBatchData.m_IndicesBase = 0;
}
}
void OverlayRenderer::PrepareForRendering()
{
PROFILE3("prepare overlays");
// This is where we should do something like sort the overlays by
// color/sprite/etc for more efficient rendering
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
if (!line->m_RenderData)
{
line->m_RenderData = std::make_shared();
line->m_RenderData->Update(*line);
// We assume the overlay line will get replaced by the caller
// if terrain changes, so we don't need to detect that here and
// call Update again. Also we assume the caller won't change
// any of the parameters after first submitting the line.
}
}
// Group quad overlays by their texture/mask combination for efficient rendering
// TODO: consider doing this directly in Submit()
for (size_t i = 0; i < m->quads.size(); ++i)
{
SOverlayQuad* const quad = m->quads[i];
QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask);
QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist
// add overlay to list of quads
batchRenderData.m_Quads.push_back(quad);
}
const CVector3D vOffset(0, OverlayRenderer::OVERLAY_VOFFSET, 0);
// Write quad overlay vertices/indices to VA backing store
VertexArrayIterator vertexPos = m->quadAttributePos.GetIterator();
VertexArrayIterator vertexColor = m->quadAttributeColor.GetIterator();
VertexArrayIterator vertexUV = m->quadAttributeUV.GetIterator();
size_t indicesIdx = 0;
size_t totalNumQuads = 0;
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& batchRenderData = (it->second);
batchRenderData.m_NumRenderQuads = 0;
if (batchRenderData.m_Quads.empty())
continue;
// Remember the current index into the (entire) indices array as our base offset for this batch
batchRenderData.m_IndicesBase = indicesIdx;
// points to the index where each iteration's vertices will be appended
for (size_t i = 0; i < batchRenderData.m_Quads.size() && totalNumQuads < OverlayRendererInternals::MAX_QUAD_OVERLAYS; i++)
{
const SOverlayQuad* quad = batchRenderData.m_Quads[i];
// TODO: this is kind of ugly, the iterator should use a type that can have quad->m_Color assigned
// to it directly
const CVector4D quadColor(quad->m_Color.r, quad->m_Color.g, quad->m_Color.b, quad->m_Color.a);
*vertexPos++ = quad->m_Corners[0] + vOffset;
*vertexPos++ = quad->m_Corners[1] + vOffset;
*vertexPos++ = quad->m_Corners[2] + vOffset;
*vertexPos++ = quad->m_Corners[3] + vOffset;
(*vertexUV)[0] = 0;
(*vertexUV)[1] = 0;
++vertexUV;
(*vertexUV)[0] = 0;
(*vertexUV)[1] = 1;
++vertexUV;
(*vertexUV)[0] = 1;
(*vertexUV)[1] = 1;
++vertexUV;
(*vertexUV)[0] = 1;
(*vertexUV)[1] = 0;
++vertexUV;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
indicesIdx += 6;
totalNumQuads++;
batchRenderData.m_NumRenderQuads++;
}
}
m->quadVertices.Upload();
// don't free the backing store! we'll overwrite it on the next frame to save a reallocation.
m->quadVertices.PrepareForRendering();
}
void OverlayRenderer::RenderOverlaysBeforeWater()
{
PROFILE3_GPU("overlays (before)");
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderOverlaysBeforeWater for GLES
#else
glEnable(GL_BLEND);
// Ignore z so that we draw behind terrain (but don't disable GL_DEPTH_TEST
// since we still want to write to the z buffer)
glDepthFunc(GL_ALWAYS);
for (SOverlayLine* line : m->lines)
{
if (line->m_Coords.empty())
continue;
g_Renderer.GetDebugRenderer().DrawLine(line->m_Coords, line->m_Color, static_cast(line->m_Thickness));
}
glDepthFunc(GL_LEQUAL);
glDisable(GL_BLEND);
#endif
}
void OverlayRenderer::RenderOverlaysAfterWater()
{
PROFILE3_GPU("overlays (after)");
RenderTexturedOverlayLines();
RenderQuadOverlays();
RenderSphereOverlays();
}
void OverlayRenderer::RenderTexturedOverlayLines()
{
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderTexturedOverlayLines for GLES
return;
#endif
if (m->texlines.empty())
return;
ogl_WarnIfError();
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDepthMask(0);
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineNormal = GetOverlayLineShaderTechnique(m->defsOverlayLineNormal);
if (shaderTechTexLineNormal)
{
shaderTechTexLineNormal->BeginPass();
CShaderProgramPtr shaderTexLineNormal = shaderTechTexLineNormal->GetShader();
shaderTexLineNormal->Bind();
shaderTexLineNormal->BindTexture(str_losTex, los.GetTexture());
shaderTexLineNormal->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shaderTexLineNormal->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
// batch render only the non-always-visible overlay lines using the normal shader
RenderTexturedOverlayLines(shaderTexLineNormal, false);
shaderTechTexLineNormal->EndPass();
}
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineAlwaysVisible = GetOverlayLineShaderTechnique(m->defsOverlayLineAlwaysVisible);
if (shaderTechTexLineAlwaysVisible)
{
shaderTechTexLineAlwaysVisible->BeginPass();
CShaderProgramPtr shaderTexLineAlwaysVisible = shaderTechTexLineAlwaysVisible->GetShader();
shaderTexLineAlwaysVisible->Bind();
// TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted
shaderTexLineAlwaysVisible->BindTexture(str_losTex, los.GetTexture());
shaderTexLineAlwaysVisible->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shaderTexLineAlwaysVisible->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
// batch render only the always-visible overlay lines using the LoS-ignored shader
RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true);
shaderTechTexLineAlwaysVisible->EndPass();
}
// ----------------------------------------------------------------------------------------
// TODO: the shaders should probably be responsible for unbinding their textures
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(0, 0);
CVertexBuffer::Unbind();
glDepthMask(1);
glDisable(GL_BLEND);
}
void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible)
{
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
// render only those lines matching the requested alwaysVisible status
if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible)
continue;
ENSURE(line->m_RenderData);
line->m_RenderData->Render(*line, shader);
}
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
void OverlayRenderer::RenderQuadOverlays()
{
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderQuadOverlays for GLES
return;
#endif
if (m->quadBatchMap.empty())
return;
CShaderTechniquePtr shaderTech = GetOverlayLineShaderTechnique(m->defsQuadOverlay);
if (!shaderTech)
return;
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDepthMask(0);
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
shader->Bind();
shader->BindTexture(str_losTex, los.GetTexture());
shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
// Base offsets (in bytes) of the two backing stores relative to their owner VBO
u8* indexBase = m->quadIndices.Bind();
u8* vertexBase = m->quadVertices.Bind();
GLsizei indexStride = m->quadIndices.GetStride();
GLsizei vertexStride = m->quadVertices.GetStride();
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& batchRenderData = it->second;
const size_t batchNumQuads = batchRenderData.m_NumRenderQuads;
// Careful; some drivers don't like drawing calls with 0 stuff to draw.
if (batchNumQuads == 0)
continue;
const QuadBatchKey& maskPair = it->first;
shader->BindTexture(str_baseTex, maskPair.m_Texture);
shader->BindTexture(str_maskTex, maskPair.m_TextureMask);
int streamflags = shader->GetStreamFlags();
if (streamflags & STREAM_POS)
shader->VertexPointer(m->quadAttributePos.elems, m->quadAttributePos.type, vertexStride, vertexBase + m->quadAttributePos.offset);
if (streamflags & STREAM_UV0)
shader->TexCoordPointer(GL_TEXTURE0, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
if (streamflags & STREAM_UV1)
shader->TexCoordPointer(GL_TEXTURE1, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
if (streamflags & STREAM_COLOR)
shader->ColorPointer(m->quadAttributeColor.elems, m->quadAttributeColor.type, vertexStride, vertexBase + m->quadAttributeColor.offset);
shader->AssertPointersBound();
glDrawElements(GL_TRIANGLES, (GLsizei)(batchNumQuads * 6), GL_UNSIGNED_SHORT, indexBase + indexStride * batchRenderData.m_IndicesBase);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2;
}
shaderTech->EndPass();
// TODO: the shader should probably be responsible for unbinding its textures
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(0, 0);
CVertexBuffer::Unbind();
glDepthMask(1);
glDisable(GL_BLEND);
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
{
PROFILE3_GPU("overlays (fg)");
#if CONFIG2_GLES
-#warning TODO: implement OverlayRenderer::RenderForegroundOverlays for GLES
+ UNUSED2(viewCamera);
+ #warning TODO: implement OverlayRenderer::RenderForegroundOverlays for GLES
#else
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
CVector3D right = -viewCamera.GetOrientation().GetLeft();
CVector3D up = viewCamera.GetOrientation().GetUp();
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
float uvs[8] = { 0,1, 1,1, 1,0, 0,0 };
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, sizeof(float)*2, &uvs[0]);
for (size_t i = 0; i < m->sprites.size(); ++i)
{
SOverlaySprite* sprite = m->sprites[i];
if (!i || sprite->m_Texture != m->sprites[i - 1]->m_Texture)
shader->BindTexture(str_baseTex, sprite->m_Texture);
shader->Uniform(str_colorMul, sprite->m_Color);
CVector3D pos[4] = {
sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0,
sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y0,
sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1,
sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y1
};
shader->VertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
glDrawArrays(GL_QUADS, 0, (GLsizei)4);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += 2;
}
tech->EndPass();
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
static void TessellateSphereFace(const CVector3D& a, u16 ai,
const CVector3D& b, u16 bi,
const CVector3D& c, u16 ci,
std::vector& vertexes, std::vector& indexes, int level)
{
if (level == 0)
{
indexes.push_back(ai);
indexes.push_back(bi);
indexes.push_back(ci);
}
else
{
CVector3D d = (a + b).Normalized();
CVector3D e = (b + c).Normalized();
CVector3D f = (c + a).Normalized();
int di = vertexes.size() / 3; vertexes.push_back(d.X); vertexes.push_back(d.Y); vertexes.push_back(d.Z);
int ei = vertexes.size() / 3; vertexes.push_back(e.X); vertexes.push_back(e.Y); vertexes.push_back(e.Z);
int fi = vertexes.size() / 3; vertexes.push_back(f.X); vertexes.push_back(f.Y); vertexes.push_back(f.Z);
TessellateSphereFace(a,ai, d,di, f,fi, vertexes, indexes, level-1);
TessellateSphereFace(d,di, b,bi, e,ei, vertexes, indexes, level-1);
TessellateSphereFace(f,fi, e,ei, c,ci, vertexes, indexes, level-1);
TessellateSphereFace(d,di, e,ei, f,fi, vertexes, indexes, level-1);
}
}
static void TessellateSphere(std::vector& vertexes, std::vector& indexes, int level)
{
/* Start with a tetrahedron, then tessellate */
float s = sqrtf(0.5f);
#define VERT(a,b,c) vertexes.push_back(a); vertexes.push_back(b); vertexes.push_back(c);
VERT(-s, 0, -s);
VERT( s, 0, -s);
VERT( s, 0, s);
VERT(-s, 0, s);
VERT( 0, -1, 0);
VERT( 0, 1, 0);
#define FACE(a,b,c) \
TessellateSphereFace( \
CVector3D(vertexes[a*3], vertexes[a*3+1], vertexes[a*3+2]), a, \
CVector3D(vertexes[b*3], vertexes[b*3+1], vertexes[b*3+2]), b, \
CVector3D(vertexes[c*3], vertexes[c*3+1], vertexes[c*3+2]), c, \
vertexes, indexes, level);
FACE(0,4,1);
FACE(1,4,2);
FACE(2,4,3);
FACE(3,4,0);
FACE(1,5,0);
FACE(2,5,1);
FACE(3,5,2);
FACE(0,5,3);
#undef FACE
#undef VERT
}
void OverlayRendererInternals::GenerateSphere()
{
if (sphereVertexes.empty())
TessellateSphere(sphereVertexes, sphereIndexes, 3);
}
void OverlayRenderer::RenderSphereOverlays()
{
PROFILE3_GPU("overlays (spheres)");
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderSphereOverlays for GLES
#else
if (m->spheres.empty())
return;
glEnable(GL_BLEND);
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid);
tech->BeginPass();
shader = tech->GetShader();
m->GenerateSphere();
shader->VertexPointer(3, GL_FLOAT, 0, &m->sphereVertexes[0]);
for (size_t i = 0; i < m->spheres.size(); ++i)
{
SOverlaySphere* sphere = m->spheres[i];
CMatrix3D transform;
transform.SetIdentity();
transform.Scale(sphere->m_Radius, sphere->m_Radius, sphere->m_Radius);
transform.Translate(sphere->m_Center);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
shader->Uniform(str_instancingTransform, transform);
shader->Uniform(str_color, sphere->m_Color);
glDrawElements(GL_TRIANGLES, m->sphereIndexes.size(), GL_UNSIGNED_SHORT, &m->sphereIndexes[0]);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3;
}
tech->EndPass();
glDepthMask(1);
glDisable(GL_BLEND);
#endif
}
Index: ps/trunk/source/renderer/TerrainOverlay.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26166)
+++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26167)
@@ -1,392 +1,394 @@
/* Copyright (C) 2022 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 "TerrainOverlay.h"
#include "graphics/Color.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TerrainRenderer.h"
#include "simulation2/system/SimContext.h"
#include
// Global overlay list management:
static std::vector > g_TerrainOverlayList;
ITerrainOverlay::ITerrainOverlay(int priority)
{
// Add to global list of overlays
g_TerrainOverlayList.emplace_back(this, priority);
// Sort by overlays by priority. Do stable sort so that adding/removing
// overlays doesn't randomly disturb all the existing ones (which would
// be noticeable if they have the same priority and overlap).
std::stable_sort(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
[](const std::pair& a, const std::pair& b) {
return a.second < b.second;
});
}
ITerrainOverlay::~ITerrainOverlay()
{
std::vector >::iterator newEnd =
std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
[this](const std::pair& a) { return a.first == this; });
g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end());
}
void ITerrainOverlay::RenderOverlaysBeforeWater()
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (before)");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderBeforeWater();
}
void ITerrainOverlay::RenderOverlaysAfterWater(int cullGroup)
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (after)");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderAfterWater(cullGroup);
}
//////////////////////////////////////////////////////////////////////////
TerrainOverlay::TerrainOverlay(const CSimContext& simContext, int priority /* = 100 */)
: ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain())
{
}
void TerrainOverlay::StartRender()
{
}
void TerrainOverlay::EndRender()
{
}
void TerrainOverlay::GetTileExtents(
ssize_t& min_i_inclusive, ssize_t& min_j_inclusive,
ssize_t& max_i_inclusive, ssize_t& max_j_inclusive)
{
// Default to whole map
min_i_inclusive = min_j_inclusive = 0;
max_i_inclusive = max_j_inclusive = m_Terrain->GetTilesPerSide()-1;
}
void TerrainOverlay::RenderBeforeWater()
{
if (!m_Terrain)
return; // should never happen, but let's play it safe
#if CONFIG2_GLES
#warning TODO: implement TerrainOverlay::RenderOverlays for GLES
#else
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
// To ensure that outlines are drawn on top of the terrain correctly (and
// don't Z-fight and flicker nastily), draw them as QUADS with the LINE
// PolygonMode, and use PolygonOffset to pull them towards the camera.
// (See e.g. http://www.opengl.org/resources/faq/technical/polygonoffset.htm)
glPolygonOffset(-1.f, -1.f);
//glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_FILL);
glActiveTextureARB(GL_TEXTURE0);
StartRender();
ssize_t min_i, min_j, max_i, max_j;
GetTileExtents(min_i, min_j, max_i, max_j);
// Clamp the min to 0, but the max to -1 - so tile -1 can never be rendered,
// but if unclamped_max<0 then no tiles at all will be rendered. And the same
// for the upper limit.
min_i = Clamp(min_i, 0, m_Terrain->GetTilesPerSide());
min_j = Clamp(min_j, 0, m_Terrain->GetTilesPerSide());
max_i = Clamp(max_i, -1, m_Terrain->GetTilesPerSide()-1);
max_j = Clamp(max_j, -1, m_Terrain->GetTilesPerSide()-1);
for (m_j = min_j; m_j <= max_j; ++m_j)
for (m_i = min_i; m_i <= max_i; ++m_i)
ProcessTile(m_i, m_j);
EndRender();
// Clean up state changes
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
//glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
#endif
}
void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden)
{
RenderTile(color, draw_hidden, m_i, m_j);
}
void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden, ssize_t i, ssize_t j)
{
// TODO: unnecessary computation calls has been removed but we should use
// a vertex buffer or a vertex shader with a texture.
// Not sure if it's possible on old OpenGL.
if (draw_hidden)
{
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
else
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
#if CONFIG2_GLES
-#warning TODO: implement TerrainOverlay::RenderTile for GLES
+ UNUSED2(color); UNUSED2(i); UNUSED2(j);
+ #warning TODO: implement TerrainOverlay::RenderTile for GLES
#else
CVector3D pos[2][2];
for (int di = 0; di < 2; ++di)
for (int dj = 0; dj < 2; ++dj)
m_Terrain->CalcPosition(i + di, j + dj, pos[di][dj]);
std::vector vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
if (m_Terrain->GetTriangulationDir(i, j))
{
ADD(pos[0][0]);
ADD(pos[1][0]);
ADD(pos[0][1]);
ADD(pos[1][0]);
ADD(pos[1][1]);
ADD(pos[0][1]);
}
else
{
ADD(pos[0][0]);
ADD(pos[1][0]);
ADD(pos[1][1]);
ADD(pos[1][1]);
ADD(pos[0][1]);
ADD(pos[0][0]);
}
#undef ADD
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
overlayShader->Bind();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3);
overlayShader->Unbind();
overlayTech->EndPass();
#endif
}
void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden)
{
RenderTileOutline(color, line_width, draw_hidden, m_i, m_j);
}
void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden, ssize_t i, ssize_t j)
{
if (draw_hidden)
{
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
else
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
#if CONFIG2_GLES
-#warning TODO: implement TerrainOverlay::RenderTileOutline for GLES
+ UNUSED2(color); UNUSED2(line_width); UNUSED2(i); UNUSED2(j);
+ #warning TODO: implement TerrainOverlay::RenderTileOutline for GLES
#else
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
if (line_width != 1)
glLineWidth((float)line_width);
std::vector vertices;
#define ADD(i, j) \
m_Terrain->CalcPosition(i, j, position); \
vertices.emplace_back(position.X); \
vertices.emplace_back(position.Y); \
vertices.emplace_back(position.Z);
CVector3D position;
ADD(i, j);
ADD(i+1, j);
ADD(i+1, j+1);
ADD(i, j+1);
#undef ADD
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
overlayShader->Bind();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUADS, 0, vertices.size() / 3);
overlayShader->Unbind();
overlayTech->EndPass();
if (line_width != 1)
glLineWidth(1.0f);
#endif
}
//////////////////////////////////////////////////////////////////////////
TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) :
ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile)
{
}
TerrainTextureOverlay::~TerrainTextureOverlay() = default;
void TerrainTextureOverlay::RenderAfterWater(int cullGroup)
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
const uint32_t requiredWidth = round_up_to_pow2(w);
const uint32_t requiredHeight = round_up_to_pow2(h);
glActiveTextureARB(GL_TEXTURE0);
// Recreate the texture with new size if necessary
if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
{
m_Texture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, requiredWidth, requiredHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_Texture->GetWidth(), m_Texture->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
u8* data = (u8*)calloc(w * h, 4);
BuildTextureRGBA(data, w, h);
glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data);
free(data);
CMatrix3D matrix;
matrix.SetZero();
matrix._11 = m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE);
matrix._23 = m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE);
matrix._44 = 1;
g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix, m_Texture.get());
}
SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const
{
static u8 colors[][3] =
{
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 255, 0, 255 },
{ 0, 255, 255 },
{ 255, 255, 255 },
{ 127, 0, 0 },
{ 0, 127, 0 },
{ 0, 0, 127 },
{ 127, 127, 0 },
{ 127, 0, 127 },
{ 0, 127, 127 },
{ 127, 127, 127},
{ 255, 127, 0 },
{ 127, 255, 0 },
{ 255, 0, 127 },
{ 127, 0, 255},
{ 0, 255, 127 },
{ 0, 127, 255},
{ 255, 127, 127},
{ 127, 255, 127},
{ 127, 127, 255},
{ 127, 255, 255 },
{ 255, 127, 255 },
{ 255, 255, 127 },
};
size_t c = idx % ARRAY_SIZE(colors);
return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);
}
Index: ps/trunk/source/renderer/TerrainRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26166)
+++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26167)
@@ -1,608 +1,609 @@
/* Copyright (C) 2022 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 "renderer/TerrainRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Canvas2D.h"
#include "graphics/Decal.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/LOSTexture.h"
#include "graphics/Patch.h"
#include "graphics/Model.h"
#include "graphics/ShaderManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/TextRenderer.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/DecalRData.h"
#include "renderer/PatchRData.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "renderer/ShadowMap.h"
#include "renderer/SkyManager.h"
#include "renderer/VertexArray.h"
#include "renderer/WaterManager.h"
/**
* TerrainRenderer keeps track of which phase it is in, to detect
* when Submit, PrepareForRendering etc. are called in the wrong order.
*/
enum Phase {
Phase_Submit,
Phase_Render
};
/**
* Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class.
*/
struct TerrainRendererInternals
{
/// Which phase (submitting or rendering patches) are we in right now?
Phase phase;
/// Patches that were submitted for this frame
std::vector visiblePatches[CSceneRenderer::CULL_MAX];
/// Decals that were submitted for this frame
std::vector visibleDecals[CSceneRenderer::CULL_MAX];
/// Fancy water shader
CShaderTechniquePtr fancyWaterTech;
CSimulation2* simulation;
};
///////////////////////////////////////////////////////////////////
// Construction/Destruction
TerrainRenderer::TerrainRenderer()
{
m = new TerrainRendererInternals();
m->phase = Phase_Submit;
}
TerrainRenderer::~TerrainRenderer()
{
delete m;
}
void TerrainRenderer::SetSimulation(CSimulation2* simulation)
{
m->simulation = simulation;
}
///////////////////////////////////////////////////////////////////
// Submit a patch for rendering
void TerrainRenderer::Submit(int cullGroup, CPatch* patch)
{
ENSURE(m->phase == Phase_Submit);
CPatchRData* data = (CPatchRData*)patch->GetRenderData();
if (data == 0)
{
// no renderdata for patch, create it now
data = new CPatchRData(patch, m->simulation);
patch->SetRenderData(data);
}
data->Update(m->simulation);
m->visiblePatches[cullGroup].push_back(data);
}
///////////////////////////////////////////////////////////////////
// Submit a decal for rendering
void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal)
{
ENSURE(m->phase == Phase_Submit);
CDecalRData* data = (CDecalRData*)decal->GetRenderData();
if (data == 0)
{
// no renderdata for decal, create it now
data = new CDecalRData(decal, m->simulation);
decal->SetRenderData(data);
}
data->Update(m->simulation);
m->visibleDecals[cullGroup].push_back(data);
}
///////////////////////////////////////////////////////////////////
// Prepare for rendering
void TerrainRenderer::PrepareForRendering()
{
ENSURE(m->phase == Phase_Submit);
m->phase = Phase_Render;
}
///////////////////////////////////////////////////////////////////
// Clear submissions lists
void TerrainRenderer::EndFrame()
{
ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit);
for (int i = 0; i < CSceneRenderer::CULL_MAX; ++i)
{
m->visiblePatches[i].clear();
m->visibleDecals[i].clear();
}
m->phase = Phase_Submit;
}
void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix,
Renderer::Backend::GL::CTexture* texture)
{
#if CONFIG2_GLES
#warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES
UNUSED2(cullGroup);
UNUSED2(textureMatrix);
UNUSED2(texture);
#else
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(0);
glDisable(GL_DEPTH_TEST);
CShaderTechniquePtr debugOverlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay);
debugOverlayTech->BeginPass();
CShaderProgramPtr debugOverlayShader = debugOverlayTech->GetShader();
debugOverlayShader->Bind();
debugOverlayShader->BindTexture(str_baseTex, texture);
debugOverlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
debugOverlayShader->Uniform(str_textureTransform, textureMatrix);
CPatchRData::RenderStreams(visiblePatches, debugOverlayShader, STREAM_POS | STREAM_POSTOUV0);
glEnable(GL_DEPTH_TEST);
// To make the overlay visible over water, render an additional map-sized
// water-height patch.
CBoundingBoxAligned waterBounds;
for (CPatchRData* data : visiblePatches)
waterBounds += data->GetWaterBounds();
if (!waterBounds.IsEmpty())
{
// Add a delta to avoid z-fighting.
const float height = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight + 0.05f;
const float waterPos[] = {
waterBounds[0].X, height, waterBounds[0].Z,
waterBounds[1].X, height, waterBounds[0].Z,
waterBounds[0].X, height, waterBounds[1].Z,
waterBounds[1].X, height, waterBounds[1].Z
};
const GLsizei stride = sizeof(float) * 3;
debugOverlayShader->VertexPointer(3, GL_FLOAT, stride, waterPos);
debugOverlayShader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, waterPos);
debugOverlayShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
debugOverlayShader->Unbind();
debugOverlayTech->EndPass();
glDepthMask(1);
glDisable(GL_BLEND);
#endif
}
///////////////////////////////////////////////////////////////////
/**
* Set up all the uniforms for a shader pass.
*/
void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow)
{
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
shader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
shader->Uniform(str_cameraPos, sceneRenderer.GetViewCamera().GetOrientation().GetTranslation());
const CLightEnv& lightEnv = sceneRenderer.GetLightEnv();
if (shadow)
shadow->BindTo(shader);
CLOSTexture& los = sceneRenderer.GetScene().GetLOSTexture();
shader->BindTexture(str_losTex, los.GetTextureSmooth());
shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shader->Uniform(str_ambient, lightEnv.m_AmbientColor);
shader->Uniform(str_sunColor, lightEnv.m_SunColor);
shader->Uniform(str_sunDir, lightEnv.GetSunDir());
shader->Uniform(str_fogColor, lightEnv.m_FogColor);
shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
}
void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
std::vector& visibleDecals = m->visibleDecals[cullGroup];
if (visiblePatches.empty() && visibleDecals.empty())
return;
// render the solid black sides of the map first
CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_solid);
techSolid->BeginPass();
CShaderProgramPtr shaderSolid = techSolid->GetShader();
shaderSolid->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
shaderSolid->Uniform(str_color, 0.0f, 0.0f, 0.0f, 1.0f);
CPatchRData::RenderSides(visiblePatches, shaderSolid);
techSolid->EndPass();
CPatchRData::RenderBases(visiblePatches, context, shadow);
// no need to write to the depth buffer a second time
glDepthMask(0);
// render blend passes for each patch
CPatchRData::RenderBlends(visiblePatches, context, shadow);
CDecalRData::RenderDecals(visibleDecals, context, shadow);
// restore OpenGL state
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(2, 0);
g_Renderer.BindTexture(3, 0);
glDepthMask(1);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_BLEND);
}
///////////////////////////////////////////////////////////////////
// Render un-textured patches as polygons
void TerrainRenderer::RenderPatches(int cullGroup, const CColor& color)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
if (visiblePatches.empty())
return;
#if CONFIG2_GLES
-#warning TODO: implement TerrainRenderer::RenderPatches for GLES
+ UNUSED2(color);
+ #warning TODO: implement TerrainRenderer::RenderPatches for GLES
#else
CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_dummy);
dummyTech->BeginPass();
CShaderProgramPtr dummyShader = dummyTech->GetShader();
dummyShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
dummyShader->Uniform(str_color, color);
CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS);
dummyTech->EndPass();
#endif
}
///////////////////////////////////////////////////////////////////
// Render outlines of submitted patches as lines
void TerrainRenderer::RenderOutlines(int cullGroup)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
if (visiblePatches.empty())
return;
for (size_t i = 0; i < visiblePatches.size(); ++i)
visiblePatches[i]->RenderOutline();
}
///////////////////////////////////////////////////////////////////
// Scissor rectangle of water patches
CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CCamera& camera)
{
CBoundingBoxAligned scissor;
for (const CPatchRData* data : m->visiblePatches[cullGroup])
{
const CBoundingBoxAligned& waterBounds = data->GetWaterBounds();
if (waterBounds.IsEmpty())
continue;
const CBoundingBoxAligned waterBoundsInViewPort =
camera.GetBoundsInViewPort(waterBounds);
if (!waterBoundsInViewPort.IsEmpty())
scissor += waterBoundsInViewPort;
}
return CBoundingBoxAligned(
CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f),
CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f));
}
// Render fancy water
bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
PROFILE3_GPU("fancy water");
OGL_SCOPED_DEBUG_GROUP("Render Fancy Water");
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
WaterManager& waterManager = sceneRenderer.GetWaterManager();
CShaderDefines defines = context;
// If we're using fancy water, make sure its shader is loaded
if (!m->fancyWaterTech || waterManager.m_NeedsReloading)
{
if (waterManager.m_WaterRealDepth)
defines.Add(str_USE_REAL_DEPTH, str_1);
if (waterManager.m_WaterFancyEffects)
defines.Add(str_USE_FANCY_EFFECTS, str_1);
if (waterManager.m_WaterRefraction)
defines.Add(str_USE_REFRACTION, str_1);
if (waterManager.m_WaterReflection)
defines.Add(str_USE_REFLECTION, str_1);
m->fancyWaterTech = g_Renderer.GetShaderManager().LoadEffect(str_water_high, defines);
if (!m->fancyWaterTech)
{
LOGERROR("Failed to load water shader. Falling back to a simple water.\n");
waterManager.m_RenderWater = false;
return false;
}
waterManager.m_NeedsReloading = false;
}
CLOSTexture& losTexture = sceneRenderer.GetScene().GetLOSTexture();
// Calculating the advanced informations about Foam and all if the quality calls for it.
/*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves))
{
WaterMgr->m_NeedInfoUpdate = false;
WaterMgr->CreateSuperfancyInfo();
}*/
const double time = waterManager.m_WaterTexTimer;
const float repeatPeriod = waterManager.m_RepeatPeriod;
// Render normals and foam to a framebuffer if we're in fancy effects
if (waterManager.m_WaterFancyEffects)
{
// Save the post-processing framebuffer.
GLint fbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, waterManager.m_FancyEffectsFBO);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDisable(GL_CULL_FACE);
// Overwrite waves that would be behind the ground.
CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
dummyTech->BeginPass();
CShaderProgramPtr dummyShader = dummyTech->GetShader();
dummyShader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
{
CPatchRData* data = visiblePatches[i];
data->RenderWater(dummyShader, true, true);
}
dummyTech->EndPass();
glEnable(GL_CULL_FACE);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
m->fancyWaterTech->BeginPass();
CShaderProgramPtr fancyWaterShader = m->fancyWaterTech->GetShader();
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
const double period = 8.0;
fancyWaterShader->BindTexture(str_normalMap, waterManager.m_NormalMap[waterManager.GetCurrentTextureIndex(period)]);
fancyWaterShader->BindTexture(str_normalMap2, waterManager.m_NormalMap[waterManager.GetNextTextureIndex(period)]);
if (waterManager.m_WaterFancyEffects)
{
fancyWaterShader->BindTexture(str_waterEffectsTex, waterManager.m_FancyTexture.get());
}
if (waterManager.m_WaterRefraction && waterManager.m_WaterRealDepth)
{
fancyWaterShader->BindTexture(str_depthTex, waterManager.m_RefrFboDepthTexture.get());
fancyWaterShader->Uniform(str_projInvTransform, waterManager.m_RefractionProjInvMatrix);
fancyWaterShader->Uniform(str_viewInvTransform, waterManager.m_RefractionViewInvMatrix);
}
if (waterManager.m_WaterRefraction)
fancyWaterShader->BindTexture(str_refractionMap, waterManager.m_RefractionTexture.get());
if (waterManager.m_WaterReflection)
fancyWaterShader->BindTexture(str_reflectionMap, waterManager.m_ReflectionTexture.get());
fancyWaterShader->BindTexture(str_losTex, losTexture.GetTextureSmooth());
const CLightEnv& lightEnv = sceneRenderer.GetLightEnv();
fancyWaterShader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
fancyWaterShader->BindTexture(str_skyCube, sceneRenderer.GetSkyManager().GetSkyCube());
// TODO: check that this rotates in the right direction.
CMatrix3D skyBoxRotation;
skyBoxRotation.SetIdentity();
skyBoxRotation.RotateY(M_PI + lightEnv.GetRotation());
fancyWaterShader->Uniform(str_skyBoxRot, skyBoxRotation);
if (waterManager.m_WaterRefraction)
fancyWaterShader->Uniform(str_refractionMatrix, waterManager.m_RefractionMatrix);
if (waterManager.m_WaterReflection)
fancyWaterShader->Uniform(str_reflectionMatrix, waterManager.m_ReflectionMatrix);
fancyWaterShader->Uniform(str_ambient, lightEnv.m_AmbientColor);
fancyWaterShader->Uniform(str_sunDir, lightEnv.GetSunDir());
fancyWaterShader->Uniform(str_sunColor, lightEnv.m_SunColor);
fancyWaterShader->Uniform(str_color, waterManager.m_WaterColor);
fancyWaterShader->Uniform(str_tint, waterManager.m_WaterTint);
fancyWaterShader->Uniform(str_waviness, waterManager.m_Waviness);
fancyWaterShader->Uniform(str_murkiness, waterManager.m_Murkiness);
fancyWaterShader->Uniform(str_windAngle, waterManager.m_WindAngle);
fancyWaterShader->Uniform(str_repeatScale, 1.0f / repeatPeriod);
fancyWaterShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f);
fancyWaterShader->Uniform(str_cameraPos, camera.GetOrientation().GetTranslation());
fancyWaterShader->Uniform(str_fogColor, lightEnv.m_FogColor);
fancyWaterShader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
fancyWaterShader->Uniform(str_time, (float)time);
fancyWaterShader->Uniform(str_screenSize, (float)g_Renderer.GetWidth(), (float)g_Renderer.GetHeight(), 0.0f, 0.0f);
if (waterManager.m_WaterType == L"clap")
{
fancyWaterShader->Uniform(str_waveParams1, 30.0f,1.5f,20.0f,0.03f);
fancyWaterShader->Uniform(str_waveParams2, 0.5f,0.0f,0.0f,0.0f);
}
else if (waterManager.m_WaterType == L"lake")
{
fancyWaterShader->Uniform(str_waveParams1, 8.5f,1.5f,15.0f,0.03f);
fancyWaterShader->Uniform(str_waveParams2, 0.2f,0.0f,0.0f,0.07f);
}
else
{
fancyWaterShader->Uniform(str_waveParams1, 15.0f,0.8f,10.0f,0.1f);
fancyWaterShader->Uniform(str_waveParams2, 0.3f,0.0f,0.1f,0.3f);
}
if (shadow)
shadow->BindTo(fancyWaterShader);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
{
CPatchRData* data = visiblePatches[i];
data->RenderWater(fancyWaterShader);
}
m->fancyWaterTech->EndPass();
glDepthFunc(GL_LEQUAL);
glDisable(GL_BLEND);
return true;
}
void TerrainRenderer::RenderSimpleWater(int cullGroup)
{
#if CONFIG2_GLES
UNUSED2(cullGroup);
#else
PROFILE3_GPU("simple water");
OGL_SCOPED_DEBUG_GROUP("Render Simple Water");
const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
const double time = waterManager.m_WaterTexTimer;
CShaderTechniquePtr waterSimpleTech =
g_Renderer.GetShaderManager().LoadEffect(str_water_simple);
waterSimpleTech->BeginPass();
CShaderProgramPtr waterSimpleShader = waterSimpleTech->GetShader();
waterSimpleShader->Bind();
waterSimpleShader->BindTexture(str_baseTex, waterManager.m_WaterTexture[waterManager.GetCurrentTextureIndex(1.6)]);
waterSimpleShader->BindTexture(str_losTex, losTexture.GetTextureSmooth());
waterSimpleShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
waterSimpleShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f);
waterSimpleShader->Uniform(str_time, static_cast(time));
waterSimpleShader->Uniform(str_color, waterManager.m_WaterColor);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
{
CPatchRData* data = visiblePatches[i];
data->RenderWater(waterSimpleShader, false, true);
}
waterSimpleShader->Unbind();
g_Renderer.BindTexture(1, 0);
glActiveTextureARB(GL_TEXTURE0_ARB);
waterSimpleTech->EndPass();
#endif
}
///////////////////////////////////////////////////////////////////
// Render water that is part of the terrain
void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
waterManager.UpdateQuality();
if (!waterManager.WillRenderFancyWater())
RenderSimpleWater(cullGroup);
else
RenderFancyWater(context, cullGroup, shadow);
}
void TerrainRenderer::RenderPriorities(int cullGroup)
{
PROFILE("priorities");
ENSURE(m->phase == Phase_Render);
CCanvas2D canvas;
CTextRenderer textRenderer;
textRenderer.SetCurrentFont(CStrIntern("mono-stroke-10"));
textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f));
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
visiblePatches[i]->RenderPriorities(textRenderer);
canvas.DrawText(textRenderer);
}
Index: ps/trunk/source/renderer/WaterManager.cpp
===================================================================
--- ps/trunk/source/renderer/WaterManager.cpp (revision 26166)
+++ ps/trunk/source/renderer/WaterManager.cpp (revision 26167)
@@ -1,1107 +1,1108 @@
/* Copyright (C) 2022 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 "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/ogl.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/WaterManager.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/components/ICmpRangeManager.h"
struct CoastalPoint
{
CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {};
int index;
CVector2D position;
};
struct SWavesVertex
{
// vertex position
CVector3D m_BasePosition;
CVector3D m_ApexPosition;
CVector3D m_SplashPosition;
CVector3D m_RetreatPosition;
CVector2D m_PerpVect;
u8 m_UV[3];
// pad to a power of two
u8 m_Padding[5];
};
cassert(sizeof(SWavesVertex) == 64);
struct WaveObject
{
CVertexBuffer::VBChunk* m_VBvertices;
CBoundingBoxAligned m_AABB;
size_t m_Width;
float m_TimeDiff;
};
WaterManager::WaterManager()
{
// water
m_RenderWater = false; // disabled until textures are successfully loaded
m_WaterHeight = 5.0f;
m_RefTextureSize = 0;
m_ReflectionFbo = 0;
m_RefractionFbo = 0;
m_FancyEffectsFBO = 0;
m_WaterTexTimer = 0.0;
m_WindAngle = 0.0f;
m_Waviness = 8.0f;
m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f);
m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f);
m_Murkiness = 0.45f;
m_RepeatPeriod = 16.0f;
m_DistanceHeightmap = NULL;
m_BlurredNormalMap = NULL;
m_WindStrength = NULL;
m_ShoreWaves_VBIndices = NULL;
m_WaterEffects = true;
m_WaterFancyEffects = false;
m_WaterRealDepth = false;
m_WaterRefraction = false;
m_WaterReflection = false;
m_WaterType = L"ocean";
m_NeedsReloading = false;
m_NeedInfoUpdate = true;
m_MapSize = 0;
m_updatei0 = 0;
m_updatej0 = 0;
m_updatei1 = 0;
m_updatej1 = 0;
}
WaterManager::~WaterManager()
{
// Cleanup if the caller messed up
UnloadWaterTextures();
for (WaveObject* const& obj : m_ShoreWaves)
{
if (obj->m_VBvertices)
g_VBMan.Release(obj->m_VBvertices);
delete obj;
}
if (m_ShoreWaves_VBIndices)
g_VBMan.Release(m_ShoreWaves_VBIndices);
delete[] m_DistanceHeightmap;
delete[] m_BlurredNormalMap;
delete[] m_WindStrength;
if (!g_Renderer.GetCapabilities().m_PrettyWater)
return;
m_FancyTexture.reset();
m_FancyTextureDepth.reset();
m_ReflFboDepthTexture.reset();
m_RefrFboDepthTexture.reset();
glDeleteFramebuffersEXT(1, &m_FancyEffectsFBO);
glDeleteFramebuffersEXT(1, &m_RefractionFbo);
glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
///////////////////////////////////////////////////////////////////
// Progressive load of water textures
int WaterManager::LoadWaterTextures()
{
// TODO: this doesn't need to be progressive-loading any more
// (since texture loading is async now)
wchar_t pathname[PATH_MAX];
// Load diffuse grayscale images (for non-fancy water)
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaterTexture[i] = texture;
}
if (!g_Renderer.GetCapabilities().m_PrettyWater)
{
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
return 0;
}
#if CONFIG2_GLES
#warning Fix WaterManager::LoadWaterTextures on GLES
#else
// Load normalmaps (for fancy water)
ReloadWaterNormalTextures();
// Load CoastalWaves
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaveTex = texture;
}
// Load Foam
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_FoamTex = texture;
}
// Use screen-sized textures for minimum artifacts.
m_RefTextureSize = g_Renderer.GetHeight();
m_RefTextureSize = round_up_to_pow2(m_RefTextureSize);
// Create reflection texture
m_ReflectionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
glBindTexture(GL_TEXTURE_2D, m_ReflectionTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// Create refraction texture
m_RefractionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
glBindTexture(GL_TEXTURE_2D, m_RefractionTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// Create depth textures
m_ReflFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
glBindTexture(GL_TEXTURE_2D, m_ReflFboDepthTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
m_RefrFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
glBindTexture(GL_TEXTURE_2D, m_RefrFboDepthTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
Resize();
// Create the water framebuffers
GLint currentFbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFbo);
m_ReflectionFbo = 0;
glGenFramebuffersEXT(1, &m_ReflectionFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ReflectionFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ReflectionTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_ReflFboDepthTexture->GetHandle(), 0);
ogl_WarnIfError();
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Reflection framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterReflection(false);
UpdateQuality();
}
m_RefractionFbo = 0;
glGenFramebuffersEXT(1, &m_RefractionFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_RefractionFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_RefractionTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_RefrFboDepthTexture->GetHandle(), 0);
ogl_WarnIfError();
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Refraction framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
glGenFramebuffersEXT(1, &m_FancyEffectsFBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FancyTexture->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_FancyTextureDepth->GetHandle(), 0);
ogl_WarnIfError();
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Fancy Effects framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFbo);
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
#endif
return 0;
}
///////////////////////////////////////////////////////////////////
// Resize: Updates the fancy water textures.
void WaterManager::Resize()
{
// Create the Fancy Effects texture
m_FancyTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
glBindTexture(GL_TEXTURE_2D, m_FancyTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);
m_FancyTextureDepth = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
}
void WaterManager::ReloadWaterNormalTextures()
{
wchar_t pathname[PATH_MAX];
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast(i) + 1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
textureProps.SetMaxAnisotropy(4);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_NormalMap[i] = texture;
}
}
///////////////////////////////////////////////////////////////////
// Unload water textures
void WaterManager::UnloadWaterTextures()
{
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
m_WaterTexture[i].reset();
if (!g_Renderer.GetCapabilities().m_PrettyWater)
return;
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
m_NormalMap[i].reset();
m_ReflectionTexture.reset();
m_RefractionTexture.reset();
glDeleteFramebuffersEXT(1, &m_RefractionFbo);
glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
template
static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel)
{
#define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight)
#define UPDATELOOKAHEAD \
for (; lookahead <= id2+maxLevel && lookahead < SideSize && \
((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead)
// Algorithm:
// We want to know the distance to the closest shore point. Go through each line/column,
// keep track of when we encountered the last shore point and how far ahead the next one is.
for (size_t id1 = 0; id1 < SideSize; ++id1)
{
size_t id2 = 0;
const size_t& x = Transpose ? id1 : id2;
const size_t& z = Transpose ? id2 : id1;
size_t level = ABOVEWATER(x, z) ? 0 : maxLevel;
size_t lookahead = (size_t)(level > 0);
UPDATELOOKAHEAD;
// start moving
for (; id2 < SideSize; ++id2)
{
// update current level
if (ABOVEWATER(x, z))
level = 0;
else
level = std::min(level+1, maxLevel);
// move lookahead
if (lookahead == id2)
++lookahead;
UPDATELOOKAHEAD;
// This is the important bit: set the distance to either:
// - the distance to the previous shore point (level)
// - the distance to the next shore point (lookahead-id2)
distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level));
}
}
#undef ABOVEWATER
#undef UPDATELOOKAHEAD
}
///////////////////////////////////////////////////////////////////
// Calculate our binary heightmap from the terrain heightmap.
void WaterManager::RecomputeDistanceHeightmap()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
size_t SideSize = m_MapSize;
// we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead.
const size_t maxLevel = 5;
if (m_DistanceHeightmap == NULL)
{
m_DistanceHeightmap = new float[SideSize*SideSize];
std::fill(m_DistanceHeightmap, m_DistanceHeightmap + SideSize*SideSize, (float)maxLevel);
}
// Create a manhattan-distance heightmap.
// This could be refined to only be done near the coast itself, but it's probably not necessary.
u16* heightmap = terrain->GetHeightMap();
ComputeDirection(m_DistanceHeightmap, heightmap, m_WaterHeight, SideSize, maxLevel);
ComputeDirection(m_DistanceHeightmap, heightmap, m_WaterHeight, SideSize, maxLevel);
}
// This requires m_DistanceHeightmap to be defined properly.
void WaterManager::CreateWaveMeshes()
{
OGL_SCOPED_DEBUG_GROUP("Create Wave Meshes");
if (m_MapSize == 0)
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
for (WaveObject* const& obj : m_ShoreWaves)
{
if (obj->m_VBvertices)
g_VBMan.Release(obj->m_VBvertices);
delete obj;
}
m_ShoreWaves.clear();
if (m_ShoreWaves_VBIndices)
{
g_VBMan.Release(m_ShoreWaves_VBIndices);
m_ShoreWaves_VBIndices = NULL;
}
if (m_Waviness < 5.0f && m_WaterType != L"ocean")
return;
size_t SideSize = m_MapSize;
// First step: get the points near the coast.
std::set CoastalPointsSet;
for (size_t z = 1; z < SideSize-1; ++z)
for (size_t x = 1; x < SideSize-1; ++x)
// get the points not on the shore but near it, ocean-side
if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f)
CoastalPointsSet.insert((z)*SideSize + x);
// Second step: create chains out of those coastal points.
static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } };
std::vector > CoastalPointsChains;
while (!CoastalPointsSet.empty())
{
int index = *(CoastalPointsSet.begin());
int x = index % SideSize;
int y = (index - x ) / SideSize;
std::deque Chain;
Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4)));
// Erase us.
CoastalPointsSet.erase(CoastalPointsSet.begin());
// We're our starter points. At most we can have 2 points close to us.
// We'll pick the first one and look for its neighbors (he can only have one new)
// Up until we either reach the end of the chain, or ourselves.
// Then go down the other direction if there is any.
int neighbours[2] = { -1, -1 };
int nbNeighb = 0;
for (int i = 0; i < 8; ++i)
{
if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize))
{
if (nbNeighb < 2)
neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize;
++nbNeighb;
}
}
if (nbNeighb > 2)
continue;
for (int i = 0; i < 2; ++i)
{
if (neighbours[i] == -1)
continue;
// Move to our neighboring point
int xx = neighbours[i] % SideSize;
int yy = (neighbours[i] - xx ) / SideSize;
int indexx = xx + yy*SideSize;
int endedChain = false;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
// If there's a loop we'll be the "other" neighboring point already so check for that.
// We'll readd at the end/front the other one to have full squares.
if (CoastalPointsSet.count(indexx) == 0)
break;
CoastalPointsSet.erase(indexx);
// Start checking from there.
while(!endedChain)
{
bool found = false;
nbNeighb = 0;
for (int p = 0; p < 8; ++p)
{
if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize))
{
if (nbNeighb >= 2)
{
CoastalPointsSet.erase(xx + yy*SideSize);
continue;
}
++nbNeighb;
// We've found a new point around us.
// Move there
xx = xx + around[p][0];
yy = yy + around[p][1];
indexx = xx + yy*SideSize;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
CoastalPointsSet.erase(xx + yy*SideSize);
found = true;
break;
}
}
if (!found)
endedChain = true;
}
}
if (Chain.size() > 10)
CoastalPointsChains.push_back(Chain);
}
// (optional) third step: Smooth chains out.
// This is also really dumb.
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
// Bump 1 for smoother.
for (int p = 0; p < 3; ++p)
{
for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j)
{
CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position;
CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f;
}
}
}
// Fourth step: create waves themselves, using those chains. We basically create subchains.
GLushort waveSizes = 14; // maximal size in width.
// Construct indices buffer (we can afford one for all of them)
std::vector water_indices;
for (GLushort a = 0; a < waveSizes - 1; ++a)
{
for (GLushort rect = 0; rect < 7; ++rect)
{
water_indices.push_back(a * 9 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 1 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 10 + rect);
water_indices.push_back(a * 9 + 1 + rect);
}
}
// Generic indexes, max-length
m_ShoreWaves_VBIndices = g_VBMan.Allocate(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
m_ShoreWaves_VBIndices->m_Owner->UpdateChunkVertices(m_ShoreWaves_VBIndices, &water_indices[0]);
float diff = (rand() % 50) / 5.0f;
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j)
{
if (CoastalPointsChains[i].size()- 1 - j < waveSizes)
break;
GLushort width = waveSizes;
// First pass to get some parameters out.
float outmost = 0.0f; // how far to move on the shore.
float avgDepth = 0.0f;
int sign = 1;
CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0);
for (GLushort a = 0; a < waveSizes;++a)
{
lastPerp = perp;
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
if (a == 0)
firstPerp = perp;
if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f)
{
width = a+1;
break;
}
if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight)
sign = -1;
avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight;
float localOutmost = -2.0f;
while (localOutmost < 0.0f)
{
float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight;
if (depth < 0.0f || depth > 0.6f)
localOutmost += 0.2f;
else
break;
}
outmost += localOutmost;
}
if (width < 5)
{
j += 6;
continue;
}
outmost /= width;
if (outmost > -0.5f)
{
j += 3;
continue;
}
outmost = -2.5f + outmost * m_Waviness/10.0f;
avgDepth /= width;
if (avgDepth > -1.3f)
{
j += 3;
continue;
}
// we passed the checks, we can create a wave of size "width".
WaveObject* shoreWave = new WaveObject;
std::vector vertices;
vertices.reserve(9*width);
shoreWave->m_Width = width;
shoreWave->m_TimeDiff = diff;
diff += (rand() % 100) / 25.0f + 4.0f;
for (GLushort a = 0; a < width;++a)
{
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
SWavesVertex point[9];
float baseHeight = 0.04f;
float halfWidth = (width-1.0f)/2.0f;
float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f));
point[0].m_UV[0] = a; point[0].m_UV[1] = 8;
point[1].m_UV[0] = a; point[1].m_UV[1] = 7;
point[2].m_UV[0] = a; point[2].m_UV[1] = 6;
point[3].m_UV[0] = a; point[3].m_UV[1] = 5;
point[4].m_UV[0] = a; point[4].m_UV[1] = 4;
point[5].m_UV[0] = a; point[5].m_UV[1] = 3;
point[6].m_UV[0] = a; point[6].m_UV[1] = 2;
point[7].m_UV[0] = a; point[7].m_UV[1] = 1;
point[8].m_UV[0] = a; point[8].m_UV[1] = 0;
point[0].m_PerpVect = perp;
point[1].m_PerpVect = perp;
point[2].m_PerpVect = perp;
point[3].m_PerpVect = perp;
point[4].m_PerpVect = perp;
point[5].m_PerpVect = perp;
point[6].m_PerpVect = perp;
point[7].m_PerpVect = perp;
point[8].m_PerpVect = perp;
static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f };
static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f };
static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f };
static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f };
static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 };
static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 };
static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 };
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess),
pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
}
vertices.push_back(point[8]);
vertices.push_back(point[7]);
vertices.push_back(point[6]);
vertices.push_back(point[5]);
vertices.push_back(point[4]);
vertices.push_back(point[3]);
vertices.push_back(point[2]);
vertices.push_back(point[1]);
vertices.push_back(point[0]);
shoreWave->m_AABB += point[8].m_SplashPosition;
shoreWave->m_AABB += point[8].m_BasePosition;
shoreWave->m_AABB += point[0].m_SplashPosition;
shoreWave->m_AABB += point[0].m_BasePosition;
shoreWave->m_AABB += point[4].m_ApexPosition;
}
if (sign == 1)
{
// Let's do some fancy reversing.
std::vector reversed;
reversed.reserve(vertices.size());
for (int a = width-1; a >= 0; --a)
{
for (size_t t = 0; t < 9; ++t)
reversed.push_back(vertices[a*9+t]);
}
vertices = reversed;
}
j += width/2-1;
shoreWave->m_VBvertices = g_VBMan.Allocate(sizeof(SWavesVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
shoreWave->m_VBvertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBvertices, &vertices[0]);
m_ShoreWaves.push_back(shoreWave);
}
}
}
void WaterManager::RenderWaves(const CFrustum& frustrum)
{
OGL_SCOPED_DEBUG_GROUP("Render Waves");
#if CONFIG2_GLES
-#warning Fix WaterManager::RenderWaves on GLES
+ UNUSED2(frustrum);
+ #warning Fix WaterManager::RenderWaves on GLES
#else
if (!m_WaterFancyEffects)
return;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
GLuint attachments[1] = { GL_COLOR_ATTACHMENT0_EXT };
glDrawBuffers(1, attachments);
glClearColor(0.0f,0.0f, 0.0f,0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->BindTexture(str_waveTex, m_WaveTex);
shader->BindTexture(str_foamTex, m_FoamTex);
shader->Uniform(str_time, (float)m_WaterTexTimer);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
for (size_t a = 0; a < m_ShoreWaves.size(); ++a)
{
if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB))
continue;
CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBvertices;
SWavesVertex* base = (SWavesVertex*)VBchunk->m_Owner->Bind();
// setup data pointers
GLsizei stride = sizeof(SWavesVertex);
shader->VertexPointer(3, GL_FLOAT, stride, &base[VBchunk->m_Index].m_BasePosition);
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_UNSIGNED_BYTE, stride, &base[VBchunk->m_Index].m_UV);
// NormalPointer(gl_FLOAT, stride, &base[m_VBWater->m_Index].m_UV)
glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2
shader->VertexAttribPointer(str_a_apexPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_ApexPosition);
shader->VertexAttribPointer(str_a_splashPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_SplashPosition);
shader->VertexAttribPointer(str_a_retreatPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_RetreatPosition);
shader->AssertPointersBound();
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff);
shader->Uniform(str_width, (int)m_ShoreWaves[a]->m_Width);
u8* indexBase = m_ShoreWaves_VBIndices->m_Owner->Bind();
glDrawElements(GL_TRIANGLES, (GLsizei) (m_ShoreWaves[a]->m_Width-1)*(7*6),
GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_ShoreWaves_VBIndices->m_Index));
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff + 6.0f);
// TODO: figure out why this doesn't work.
//g_Renderer.m_Stats.m_DrawCalls++;
//g_Renderer.m_Stats.m_WaterTris += m_ShoreWaves_VBIndices->m_Count / 3;
CVertexBuffer::Unbind();
}
tech->EndPass();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
#endif
}
void WaterManager::RecomputeWaterData()
{
if (!m_MapSize)
return;
RecomputeDistanceHeightmap();
RecomputeWindStrength();
CreateWaveMeshes();
}
///////////////////////////////////////////////////////////////////
// Calculate the strength of the wind at a given point on the map.
void WaterManager::RecomputeWindStrength()
{
if (m_MapSize <= 0)
return;
if (m_WindStrength == nullptr)
m_WindStrength = new float[m_MapSize*m_MapSize];
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle));
int stepSize = 10;
ssize_t windX = -round(stepSize * windDir.X);
ssize_t windY = -round(stepSize * windDir.Y);
struct SWindPoint {
SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {}
ssize_t X;
ssize_t Y;
float windStrength;
};
std::vector startingPoints;
std::vector> movement; // Every increment, move each starting point by all of these.
// Compute starting points (one or two edges of the map) and how much to move each computation increment.
if (fabs(windDir.X) < 0.01f)
{
movement.emplace_back(0, windY > 0.f ? 1 : -1);
startingPoints.reserve(m_MapSize);
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
}
else if (fabs(windDir.Y) < 0.01f)
{
movement.emplace_back(windX > 0.f ? 1 : - 1, 0);
startingPoints.reserve(m_MapSize);
size_t start = windX > 0 ? 0 : m_MapSize - 1;
for (size_t z = 0; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
}
else
{
startingPoints.reserve(m_MapSize * 2);
// Points along X.
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
// Points along Z, avoid repeating the corner point.
start = windX > 0 ? 0 : m_MapSize - 1;
if (windY > 0)
for (size_t z = 1; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
else
for (size_t z = 0; z < m_MapSize-1; ++z)
startingPoints.emplace_back(start, z, 0.f);
// Compute movement array.
movement.reserve(std::max(std::abs(windX),std::abs(windY)));
while (windX != 0 || windY != 0)
{
std::pair move = {
windX == 0 ? 0 : windX > 0 ? +1 : -1,
windY == 0 ? 0 : windY > 0 ? +1 : -1
};
windX -= move.first;
windY -= move.second;
movement.push_back(move);
}
}
// We have all starting points ready, move them all until the map is covered.
for (SWindPoint& point : startingPoints)
{
// Starting velocity is 1.0 unless in shallow water.
m_WindStrength[point.Y * m_MapSize + point.X] = 1.f;
float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y);
if (depth > 0.f && depth < 2.f)
m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f;
point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X];
bool onMap = true;
while (onMap)
for (size_t step = 0; step < movement.size(); ++step)
{
// Move wind speed towards the mean.
point.windStrength = 0.15f + point.windStrength * 0.85f;
// Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect)
// and a lower height reduces speed (wind protection from hills/...)
float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) -
std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y));
if (heightDiff > 0.f)
point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f);
else
point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f);
point.X += movement[step].first;
point.Y += movement[step].second;
if (point.X < 0 || point.X >= static_cast(m_MapSize) || point.Y < 0 || point.Y >= static_cast(m_MapSize))
{
onMap = false;
break;
}
m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength;
}
}
// TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit.
}
////////////////////////////////////////////////////////////////////////
// TODO: This will always recalculate for now
void WaterManager::SetMapSize(size_t size)
{
// TODO: Im' blindly trusting the user here.
m_MapSize = size;
m_NeedInfoUpdate = true;
m_updatei0 = 0;
m_updatei1 = size;
m_updatej0 = 0;
m_updatej1 = size;
SAFE_ARRAY_DELETE(m_DistanceHeightmap);
SAFE_ARRAY_DELETE(m_BlurredNormalMap);
SAFE_ARRAY_DELETE(m_WindStrength);
}
////////////////////////////////////////////////////////////////////////
// This will set the bools properly
void WaterManager::UpdateQuality()
{
if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects)
{
m_WaterEffects = g_RenderingOptions.GetWaterEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects)
{
m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth)
{
m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction)
{
m_WaterRefraction = g_RenderingOptions.GetWaterRefraction();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection)
{
m_WaterReflection = g_RenderingOptions.GetWaterReflection();
m_NeedsReloading = true;
}
}
bool WaterManager::WillRenderFancyWater() const
{
return
m_RenderWater && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB &&
g_RenderingOptions.GetWaterEffects() && g_Renderer.GetCapabilities().m_PrettyWater;
}
size_t WaterManager::GetCurrentTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return static_cast(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture);
}
size_t WaterManager::GetNextTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture);
}