Index: ps/trunk/source/graphics/ShaderProgram.cpp =================================================================== --- ps/trunk/source/graphics/ShaderProgram.cpp (revision 24832) +++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 24833) @@ -1,943 +1,943 @@ /* 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" #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) { pglGenProgramsARB(1, &m_VertexProgram); pglGenProgramsARB(1, &m_FragmentProgram); } ~CShaderProgramARB() { Unload(); pglDeleteProgramsARB(1, &m_VertexProgram); pglDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); pglBindProgramARB(target, program); ogl_WarnIfError(); pglProgramStringARB(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; } pglBindProgramARB(target, 0); ogl_WarnIfError(); return true; } virtual void Reload() { 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; } virtual void Bind() { glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } virtual void Unbind() { glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); pglBindProgramARB(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; } virtual Binding GetTextureBinding(texture_id_t id) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index == -1) return Binding(); else return Binding((int)fPair.second, index); } virtual void BindTexture(texture_id_t id, Handle tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, h); } } virtual void BindTexture(texture_id_t id, GLuint tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, tex); } } virtual void BindTexture(Binding id, Handle tex) { int index = id.second; if (index != -1) ogl_tex_bind(tex, index); } virtual Binding GetUniformBinding(uniform_id_t id) { return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3); if (id.second != -1) pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3); } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44); } if (id.second != -1) { pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { ENSURE(count == 1); Uniform(id, v[0]); } virtual void Uniform(Binding id, size_t count, const float* v) { ENSURE(count == 4); Uniform(id, v[0], v[1], v[2], v[3]); } virtual 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 = pglCreateShaderObjectARB(GL_VERTEX_SHADER); m_FragmentShader = pglCreateShaderObjectARB(GL_FRAGMENT_SHADER); m_FileDependencies = {m_VertexFile, m_FragmentFile}; } ~CShaderProgramGLSL() { Unload(); pglDeleteShader(m_VertexShader); pglDeleteShader(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(); pglShaderSourceARB(shader, 1, &code_string, &code_length); pglCompileShaderARB(shader); GLint ok = 0; pglGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; pglGetShaderiv(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]; pglGetShaderInfoLog(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 = pglCreateProgramObjectARB(); pglAttachObjectARB(m_Program, m_VertexShader); ogl_WarnIfError(); pglAttachObjectARB(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) pglBindAttribLocationARB(m_Program, it->second, it->first.c_str()); pglLinkProgramARB(m_Program); GLint ok = 0; pglGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; pglGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; pglGetProgramInfoLog(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; pglGetProgramiv(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; pglGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); GLint loc = pglGetUniformLocationARB(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; pglUniform1iARB(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; } virtual void Reload() { 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) pglDeleteProgram(m_Program); m_Program = 0; // The shader objects can be reused and don't need to be deleted here } virtual void Bind() { pglUseProgramObjectARB(m_Program); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglEnableVertexAttribArrayARB(it->second); } virtual void Unbind() { pglUseProgramObjectARB(0); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglDisableVertexAttribArrayARB(it->second); // TODO: should unbind textures, probably } virtual Binding GetTextureBinding(texture_id_t id) { 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); } virtual void BindTexture(texture_id_t id, Handle tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, h); } virtual void BindTexture(texture_id_t id, GLuint tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, tex); } virtual void BindTexture(Binding id, Handle tex) { if (id.second == -1) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + id.second); glBindTexture(id.first, h); } virtual Binding GetUniformBinding(uniform_id_t id) { 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); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) { if (id.second == GL_FLOAT) pglUniform1fARB(id.first, v0); else if (id.second == GL_FLOAT_VEC2) pglUniform2fARB(id.first, v0, v1); else if (id.second == GL_FLOAT_VEC3) pglUniform3fARB(id.first, v0, v1, v2); else if (id.second == GL_FLOAT_VEC4) pglUniform4fARB(id.first, v0, v1, v2, v3); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)"); } } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } virtual void Uniform(Binding id, size_t count, const float* v) { if (id.first != -1) { if (id.second == GL_FLOAT) pglUniform1fvARB(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): virtual void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_POS; } virtual void NormalPointer(GLenum type, GLsizei stride, const void* pointer) { - pglVertexAttribPointerARB(2, 3, type, GL_TRUE, stride, pointer); + pglVertexAttribPointerARB(2, 3, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer); m_ValidStreams |= STREAM_NORMAL; } virtual void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { - pglVertexAttribPointerARB(3, size, type, GL_TRUE, stride, pointer); + pglVertexAttribPointerARB(3, size, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer); m_ValidStreams |= STREAM_COLOR; } virtual void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } virtual void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { pglVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer); } } virtual void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { #if CONFIG2_GLES debug_warn(L"glVertexAttribIPointer not supported on GLES"); #else pglVertexAttribIPointerEXT(it->second, size, type, stride, pointer); #endif } } virtual std::vector GetFileDependencies() const { 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, CTexturePtr 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) { pglClientActiveTextureARB(texture); glTexCoordPointer(size, type, stride, pointer); pglClientActiveTextureARB(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) { pglClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(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) { pglClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } Index: ps/trunk/source/renderer/InstancingModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 24832) +++ ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 24833) @@ -1,384 +1,384 @@ /* Copyright (C) 2020 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/InstancingModelRenderer.h" #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "lib/ogl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/VertexArray.h" #include "third_party/mikktspace/weldmesh.h" struct IModelDef : public CModelDefRPrivate { /// Static per-CModel vertex array VertexArray m_Array; /// Position and normals are static VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; VertexArray::Attribute m_Tangent; VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true /// The number of UVs is determined by the model std::vector m_UVs; /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents); }; IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents) : m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW) { size_t numVertices = mdef->GetNumVertices(); m_Position.type = GL_FLOAT; m_Position.elems = 3; m_Array.AddAttribute(&m_Position); m_Normal.type = GL_FLOAT; m_Normal.elems = 3; m_Array.AddAttribute(&m_Normal); m_UVs.resize(mdef->GetNumUVsPerVertex()); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) { m_UVs[i].type = GL_FLOAT; m_UVs[i].elems = 2; m_Array.AddAttribute(&m_UVs[i]); } if (gpuSkinning) { m_BlendJoints.type = GL_UNSIGNED_BYTE; m_BlendJoints.elems = 4; m_Array.AddAttribute(&m_BlendJoints); m_BlendWeights.type = GL_UNSIGNED_BYTE; m_BlendWeights.elems = 4; m_Array.AddAttribute(&m_BlendWeights); } if (calculateTangents) { // Generate tangents for the geometry:- m_Tangent.type = GL_FLOAT; m_Tangent.elems = 4; m_Array.AddAttribute(&m_Tangent); // floats per vertex; position + normal + tangent + UV*sets [+ GPUskinning] int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex(); if (gpuSkinning) { numVertexAttrs += 8; } // the tangent generation can increase the number of vertices temporarily // so reserve a bit more memory to avoid reallocations in GenTangents (in most cases) std::vector newVertices; newVertices.reserve(numVertexAttrs * numVertices * 2); // Generate the tangents ModelRenderer::GenTangents(mdef, newVertices, gpuSkinning); // how many vertices do we have after generating tangents? int newNumVert = newVertices.size() / numVertexAttrs; std::vector remapTable(newNumVert); std::vector vertexDataOut(newNumVert * numVertexAttrs); // re-weld the mesh to remove duplicated vertices int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0], &newVertices[0], newNumVert, numVertexAttrs); // Copy the model data to graphics memory:- m_Array.SetNumVertices(numVertices2); m_Array.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); VertexArrayIterator Normal = m_Normal.GetIterator(); VertexArrayIterator Tangent = m_Tangent.GetIterator(); VertexArrayIterator BlendJoints; VertexArrayIterator BlendWeights; if (gpuSkinning) { BlendJoints = m_BlendJoints.GetIterator(); BlendWeights = m_BlendWeights.GetIterator(); } // copy everything into the vertex array for (int i = 0; i < numVertices2; i++) { int q = numVertexAttrs * i; Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]); q += 3; Normal[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]); q += 3; Tangent[i] = CVector4D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2], vertexDataOut[q + 3]); q += 4; if (gpuSkinning) { for (size_t j = 0; j < 4; ++j) { BlendJoints[i][j] = (u8)vertexDataOut[q + 0 + 2 * j]; BlendWeights[i][j] = (u8)vertexDataOut[q + 1 + 2 * j]; } q += 8; } for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++) { VertexArrayIterator UVit = m_UVs[j].GetIterator(); UVit[i][0] = vertexDataOut[q + 0 + 2 * j]; UVit[i][1] = vertexDataOut[q + 1 + 2 * j]; } } // upload vertex data m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumVertices(mdef->GetNumFaces() * 3); m_IndexArray.Layout(); VertexArrayIterator Indices = m_IndexArray.GetIterator(); size_t idxidx = 0; // reindex geometry and upload index for (size_t j = 0; j < mdef->GetNumFaces(); ++j) { Indices[idxidx++] = remapTable[j * 3 + 0]; Indices[idxidx++] = remapTable[j * 3 + 1]; Indices[idxidx++] = remapTable[j * 3 + 2]; } m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } else { // Upload model without calculating tangents:- m_Array.SetNumVertices(numVertices); m_Array.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); VertexArrayIterator Normal = m_Normal.GetIterator(); ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); i++) { VertexArrayIterator UVit = m_UVs[i].GetIterator(); ModelRenderer::BuildUV(mdef, UVit, i); } if (gpuSkinning) { VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); for (size_t i = 0; i < numVertices; ++i) { const SModelVertex& vtx = mdef->GetVertices()[i]; for (size_t j = 0; j < 4; ++j) { BlendJoints[i][j] = vtx.m_Blend.m_Bone[j]; BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]); } } } m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3); m_IndexArray.Layout(); ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } } struct InstancingModelRendererInternals { bool gpuSkinning; bool calculateTangents; /// Previously prepared modeldef IModelDef* imodeldef; /// Index base for imodeldef u8* imodeldefIndexBase; }; // Construction and Destruction InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) { m = new InstancingModelRendererInternals; m->gpuSkinning = gpuSkinning; m->calculateTangents = calculateTangents; m->imodeldef = 0; } InstancingModelRenderer::~InstancingModelRenderer() { delete m; } // Build modeldef data if necessary - we have no per-CModel data CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); if (m->gpuSkinning) ENSURE(model->IsSkinned()); else ENSURE(!model->IsSkinned()); if (!imodeldef) { imodeldef = new IModelDef(mdef, m->gpuSkinning, m->calculateTangents); mdef->SetRenderData(m, imodeldef); } return new CModelRData(key); } void InstancingModelRenderer::UpdateModelData(CModel* UNUSED(model), CModelRData* UNUSED(data), int UNUSED(updateflags)) { // We have no per-CModel data } // Setup one rendering pass. void InstancingModelRenderer::BeginPass(int streamflags) { ENSURE(streamflags == (streamflags & (STREAM_POS|STREAM_NORMAL|STREAM_UV0|STREAM_UV1))); } // Cleanup rendering pass. void InstancingModelRenderer::EndPass(int UNUSED(streamflags)) { CVertexBuffer::Unbind(); } // Prepare UV coordinates for this modeldef void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) { m->imodeldef = (IModelDef*)def.GetRenderData(m); ENSURE(m->imodeldef); u8* base = m->imodeldef->m_Array.Bind(); GLsizei stride = (GLsizei)m->imodeldef->m_Array.GetStride(); m->imodeldefIndexBase = m->imodeldef->m_IndexArray.Bind(); if (streamflags & STREAM_POS) shader->VertexPointer(3, GL_FLOAT, stride, base + m->imodeldef->m_Position.offset); if (streamflags & STREAM_NORMAL) shader->NormalPointer(GL_FLOAT, stride, base + m->imodeldef->m_Normal.offset); if (m->calculateTangents) - shader->VertexAttribPointer(str_a_tangent, 4, GL_FLOAT, GL_TRUE, stride, base + m->imodeldef->m_Tangent.offset); + shader->VertexAttribPointer(str_a_tangent, 4, GL_FLOAT, GL_FALSE, stride, base + m->imodeldef->m_Tangent.offset); // The last UV set is STREAM_UV3 for (size_t uv = 0; uv < 4; ++uv) if (streamflags & (STREAM_UV0 << uv)) { if (def.GetNumUVsPerVertex() >= uv + 1) shader->TexCoordPointer(GL_TEXTURE0 + uv, 2, GL_FLOAT, stride, base + m->imodeldef->m_UVs[uv].offset); else ONCE(LOGERROR("Model '%s' has no UV%d set.", def.GetName().string8().c_str(), uv)); } // GPU skinning requires extra attributes to compute positions/normals if (m->gpuSkinning) { shader->VertexAttribPointer(str_a_skinJoints, 4, GL_UNSIGNED_BYTE, GL_FALSE, stride, base + m->imodeldef->m_BlendJoints.offset); shader->VertexAttribPointer(str_a_skinWeights, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, base + m->imodeldef->m_BlendWeights.offset); } shader->AssertPointersBound(); } // Render one model void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data)) { const CModelDefPtr& mdldef = model->GetModelDef(); if (m->gpuSkinning) { // Bind matrices for current animation state. // Add 1 to NumBones because of the special 'root' bone. // HACK: NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; // try uploading both names since one of them should work, and this is easier than // canonicalising the uniform names in CShaderProgramGLSL shader->Uniform(str_skinBlendMatrices_0, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); } // render the lot size_t numFaces = mdldef->GetNumFaces(); if (!g_Renderer.m_SkipSubmit) { // Draw with DrawRangeElements where available, since it might be more efficient #if CONFIG2_GLES glDrawElements(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); #else pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)m->imodeldef->m_Array.GetNumVertices()-1, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); #endif } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numFaces; } Index: ps/trunk/source/renderer/PatchRData.cpp =================================================================== --- ps/trunk/source/renderer/PatchRData.cpp (revision 24832) +++ ps/trunk/source/renderer/PatchRData.cpp (revision 24833) @@ -1,1464 +1,1473 @@ /* 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 "renderer/PatchRData.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TextRenderer.h" #include "lib/allocators/DynamicArena.h" #include "lib/allocators/STLAllocators.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/World.h" #include "renderer/AlphaMapCalculator.h" #include "renderer/Renderer.h" #include "renderer/TerrainRenderer.h" #include "renderer/WaterManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include #include #include const ssize_t BlendOffsets[9][2] = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, 0 } }; /////////////////////////////////////////////////////////////////// // CPatchRData constructor CPatchRData::CPatchRData(CPatch* patch, CSimulation2* simulation) : m_Patch(patch), m_VBSides(0), m_VBBase(0), m_VBBaseIndices(0), m_VBBlends(0), m_VBBlendIndices(0), m_VBWater(0), m_VBWaterIndices(0), m_VBWaterShore(0), m_VBWaterIndicesShore(0), m_Simulation(simulation) { ENSURE(patch); Build(); } /////////////////////////////////////////////////////////////////// // CPatchRData destructor CPatchRData::~CPatchRData() { // release vertex buffer chunks if (m_VBSides) g_VBMan.Release(m_VBSides); if (m_VBBase) g_VBMan.Release(m_VBBase); if (m_VBBaseIndices) g_VBMan.Release(m_VBBaseIndices); if (m_VBBlends) g_VBMan.Release(m_VBBlends); if (m_VBBlendIndices) g_VBMan.Release(m_VBBlendIndices); if (m_VBWater) g_VBMan.Release(m_VBWater); if (m_VBWaterIndices) g_VBMan.Release(m_VBWaterIndices); if (m_VBWaterShore) g_VBMan.Release(m_VBWaterShore); if (m_VBWaterIndicesShore) g_VBMan.Release(m_VBWaterIndicesShore); } /** * Represents a blend for a single tile, texture and shape. */ struct STileBlend { CTerrainTextureEntry* m_Texture; int m_Priority; u16 m_TileMask; // bit n set if this blend contains neighbour tile BlendOffsets[n] struct DecreasingPriority { bool operator()(const STileBlend& a, const STileBlend& b) const { if (a.m_Priority > b.m_Priority) return true; if (a.m_Priority < b.m_Priority) return false; if (a.m_Texture && b.m_Texture) return a.m_Texture->GetTag() > b.m_Texture->GetTag(); return false; } }; struct CurrentTile { bool operator()(const STileBlend& a) const { return (a.m_TileMask & (1 << 8)) != 0; } }; }; /** * Represents the ordered collection of blends drawn on a particular tile. */ struct STileBlendStack { u8 i, j; std::vector blends; // back of vector is lowest-priority texture }; /** * Represents a batched collection of blends using the same texture. */ struct SBlendLayer { struct Tile { u8 i, j; u8 shape; }; CTerrainTextureEntry* m_Texture; std::vector m_Tiles; }; void CPatchRData::BuildBlends() { PROFILE3("build blends"); m_BlendSplats.clear(); std::vector blendVertices; std::vector blendIndices; CTerrain* terrain = m_Patch->m_Parent; std::vector blendStacks; blendStacks.reserve(PATCH_SIZE*PATCH_SIZE); + std::vector blends; + blends.reserve(9); + // For each tile in patch .. for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; - std::vector blends; - blends.reserve(9); + blends.clear(); // Compute a blend for every tile in the 3x3 square around this tile for (size_t n = 0; n < 9; ++n) { ssize_t ox = gx + BlendOffsets[n][1]; ssize_t oz = gz + BlendOffsets[n][0]; CMiniPatch* nmp = terrain->GetTile(ox, oz); if (!nmp) continue; STileBlend blend; blend.m_Texture = nmp->GetTextureEntry(); blend.m_Priority = nmp->GetPriority(); blend.m_TileMask = 1 << n; blends.push_back(blend); } // Sort the blends, highest priority first std::sort(blends.begin(), blends.end(), STileBlend::DecreasingPriority()); STileBlendStack blendStack; blendStack.i = i; blendStack.j = j; // Put the blends into the tile's stack, merging any adjacent blends with the same texture for (size_t k = 0; k < blends.size(); ++k) { if (!blendStack.blends.empty() && blendStack.blends.back().m_Texture == blends[k].m_Texture) blendStack.blends.back().m_TileMask |= blends[k].m_TileMask; else blendStack.blends.push_back(blends[k]); } // Remove blends that are after (i.e. lower priority than) the current tile // (including the current tile), since we don't want to render them on top of // the tile's base texture blendStack.blends.erase( std::find_if(blendStack.blends.begin(), blendStack.blends.end(), STileBlend::CurrentTile()), blendStack.blends.end()); blendStacks.push_back(blendStack); } } // Given the blend stack per tile, we want to batch together as many blends as possible. // Group them into a series of layers (each of which has a single texture): // (This is effectively a topological sort / linearisation of the partial order induced // by the per-tile stacks, preferring to make tiles with equal textures adjacent.) std::vector blendLayers; while (true) { if (!blendLayers.empty()) { // Try to grab as many tiles as possible that match our current layer, // from off the blend stacks of all the tiles CTerrainTextureEntry* tex = blendLayers.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { if (!blendStacks[k].blends.empty() && blendStacks[k].blends.back().m_Texture == tex) { SBlendLayer::Tile t = { blendStacks[k].i, blendStacks[k].j, (u8)blendStacks[k].blends.back().m_TileMask }; blendLayers.back().m_Tiles.push_back(t); blendStacks[k].blends.pop_back(); } // (We've already merged adjacent entries of the same texture in each stack, // so we don't need to bother looping to check the next entry in this stack again) } } // We've grabbed as many tiles as possible; now we need to start a new layer. // The new layer's texture could come from the back of any non-empty stack; // choose the longest stack as a heuristic to reduce the number of layers CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { if (blendStacks[k].blends.size() > bestStackSize) { bestStackSize = blendStacks[k].blends.size(); bestTex = blendStacks[k].blends.back().m_Texture; } } // If all our stacks were empty, we're done if (bestStackSize == 0) break; // Otherwise add the new layer, then loop back and start filling it in SBlendLayer layer; layer.m_Texture = bestTex; blendLayers.push_back(layer); } // Now build outgoing splats m_BlendSplats.resize(blendLayers.size()); for (size_t k = 0; k < blendLayers.size(); ++k) { SSplat& splat = m_BlendSplats[k]; splat.m_IndexStart = blendIndices.size(); splat.m_Texture = blendLayers[k].m_Texture; for (size_t t = 0; t < blendLayers[k].m_Tiles.size(); ++t) { SBlendLayer::Tile& tile = blendLayers[k].m_Tiles[t]; AddBlend(blendVertices, blendIndices, tile.i, tile.j, tile.shape, splat.m_Texture); } splat.m_IndexCount = blendIndices.size() - splat.m_IndexStart; } // Release existing vertex buffer chunks if (m_VBBlends) { g_VBMan.Release(m_VBBlends); m_VBBlends = 0; } if (m_VBBlendIndices) { g_VBMan.Release(m_VBBlendIndices); m_VBBlendIndices = 0; } if (blendVertices.size()) { // Construct vertex buffer m_VBBlends = g_VBMan.Allocate(sizeof(SBlendVertex), blendVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends, &blendVertices[0]); // Update the indices to include the base offset of the vertex data for (size_t k = 0; k < blendIndices.size(); ++k) blendIndices[k] += static_cast(m_VBBlends->m_Index); m_VBBlendIndices = g_VBMan.Allocate(sizeof(u16), blendIndices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBBlendIndices->m_Owner->UpdateChunkVertices(m_VBBlendIndices, &blendIndices[0]); } } void CPatchRData::AddBlend(std::vector& blendVertices, std::vector& blendIndices, u16 i, u16 j, u8 shape, CTerrainTextureEntry* texture) { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; // uses the current neighbour texture BlendShape8 shape8; for (size_t m = 0; m < 8; ++m) shape8[m] = (shape & (1 << m)) ? 0 : 1; // calculate the required alphamap and the required rotation of the alphamap from blendshape unsigned int alphamapflags; int alphamap = CAlphaMapCalculator::Calculate(shape8, alphamapflags); // now actually render the blend tile (if we need one) if (alphamap == -1) return; float u0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u0; float u1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u1; float v0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v0; float v1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v1; if (alphamapflags & BLENDMAP_FLIPU) std::swap(u0, u1); if (alphamapflags & BLENDMAP_FLIPV) std::swap(v0, v1); int base = 0; if (alphamapflags & BLENDMAP_ROTATE90) base = 1; else if (alphamapflags & BLENDMAP_ROTATE180) base = 2; else if (alphamapflags & BLENDMAP_ROTATE270) base = 3; SBlendVertex vtx[4]; vtx[(base + 0) % 4].m_AlphaUVs[0] = u0; vtx[(base + 0) % 4].m_AlphaUVs[1] = v0; vtx[(base + 1) % 4].m_AlphaUVs[0] = u1; vtx[(base + 1) % 4].m_AlphaUVs[1] = v0; vtx[(base + 2) % 4].m_AlphaUVs[0] = u1; vtx[(base + 2) % 4].m_AlphaUVs[1] = v1; vtx[(base + 3) % 4].m_AlphaUVs[0] = u0; vtx[(base + 3) % 4].m_AlphaUVs[1] = v1; SBlendVertex dst; CVector3D normal; u16 index = static_cast(blendVertices.size()); terrain->CalcPosition(gx, gz, dst.m_Position); terrain->CalcNormal(gx, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[0].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[0].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz, dst.m_Position); terrain->CalcNormal(gx + 1, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[1].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[1].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz + 1, dst.m_Position); terrain->CalcNormal(gx + 1, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[2].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[2].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx, gz + 1, dst.m_Position); terrain->CalcNormal(gx, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[3].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[3].m_AlphaUVs[1]; blendVertices.push_back(dst); bool dir = terrain->GetTriangulationDir(gx, gz); if (dir) { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+3); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+3); } else { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+2); blendIndices.push_back(index+3); blendIndices.push_back(index+0); } } void CPatchRData::BuildIndices() { PROFILE3("build indices"); CTerrain* terrain = m_Patch->m_Parent; ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // must have allocated some vertices before trying to build corresponding indices ENSURE(m_VBBase); // number of vertices in each direction in each patch ssize_t vsize=PATCH_SIZE+1; // PATCH_SIZE must be 2^8-2 or less to not overflow u16 indices buffer. Thankfully this is always true. ENSURE(vsize*vsize < 65536); std::vector indices; indices.reserve(PATCH_SIZE * PATCH_SIZE * 4); // release existing splats m_Splats.clear(); // build grid of textures on this patch std::vector textures; CTerrainTextureEntry* texgrid[PATCH_SIZE][PATCH_SIZE]; for (ssize_t j=0;jm_MiniPatches[j][i].GetTextureEntry(); texgrid[j][i]=tex; if (std::find(textures.begin(),textures.end(),tex)==textures.end()) { textures.push_back(tex); } } } // now build base splats from interior textures m_Splats.resize(textures.size()); // build indices for base splats size_t base=m_VBBase->m_Index; for (size_t k = 0; k < m_Splats.size(); ++k) { CTerrainTextureEntry* tex = textures[k]; SSplat& splat=m_Splats[k]; splat.m_Texture=tex; splat.m_IndexStart=indices.size(); for (ssize_t j = 0; j < PATCH_SIZE; j++) { for (ssize_t i = 0; i < PATCH_SIZE; i++) { if (texgrid[j][i] == tex) { bool dir = terrain->GetTriangulationDir(px+i, pz+j); if (dir) { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); } else { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+0))+base)); } } } } splat.m_IndexCount=indices.size()-splat.m_IndexStart; } // Release existing vertex buffer chunk if (m_VBBaseIndices) { g_VBMan.Release(m_VBBaseIndices); m_VBBaseIndices = 0; } ENSURE(indices.size()); // Construct vertex buffer m_VBBaseIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBBaseIndices->m_Owner->UpdateChunkVertices(m_VBBaseIndices, &indices[0]); } void CPatchRData::BuildVertices() { PROFILE3("build vertices"); // create both vertices and lighting colors // number of vertices in each direction in each patch ssize_t vsize = PATCH_SIZE + 1; std::vector vertices; vertices.resize(vsize * vsize); // get index of this patch ssize_t px = m_Patch->m_X; ssize_t pz = m_Patch->m_Z; CTerrain* terrain = m_Patch->m_Parent; // build vertices for (ssize_t j = 0; j < vsize; ++j) { for (ssize_t i = 0; i < vsize; ++i) { ssize_t ix = px * PATCH_SIZE + i; ssize_t iz = pz * PATCH_SIZE + j; ssize_t v = j * vsize + i; // calculate vertex data terrain->CalcPosition(ix, iz, vertices[v].m_Position); CVector3D normal; terrain->CalcNormal(ix, iz, normal); vertices[v].m_Normal = normal; } } // upload to vertex buffer if (!m_VBBase) m_VBBase = g_VBMan.Allocate(sizeof(SBaseVertex), vsize * vsize, GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase, &vertices[0]); } void CPatchRData::BuildSide(std::vector& vertices, CPatchSideFlags side) { ssize_t vsize = PATCH_SIZE + 1; CTerrain* terrain = m_Patch->m_Parent; CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); for (ssize_t k = 0; k < vsize; k++) { ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; switch (side) { case CPATCH_SIDE_NEGX: gz += k; break; case CPATCH_SIDE_POSX: gx += PATCH_SIZE; gz += PATCH_SIZE-k; break; case CPATCH_SIDE_NEGZ: gx += PATCH_SIZE-k; break; case CPATCH_SIDE_POSZ: gz += PATCH_SIZE; gx += k; break; } CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Clamp the height to the water level float waterHeight = 0.f; if (cmpWaterManager) waterHeight = cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z); pos.Y = std::max(pos.Y, waterHeight); SSideVertex v0, v1; v0.m_Position = pos; v1.m_Position = pos; v1.m_Position.Y = 0; // If this is the start of this tristrip, but we've already got a partial // tristrip, add a couple of degenerate triangles to join the strips properly if (k == 0 && !vertices.empty()) { vertices.push_back(vertices.back()); vertices.push_back(v1); } // Now add the new triangles vertices.push_back(v1); vertices.push_back(v0); } } void CPatchRData::BuildSides() { PROFILE3("build sides"); std::vector sideVertices; int sideFlags = m_Patch->GetSideFlags(); // If no sides are enabled, we don't need to do anything if (!sideFlags) return; // For each side, generate a tristrip by adding a vertex at ground/water // level and a vertex underneath at height 0. if (sideFlags & CPATCH_SIDE_NEGX) BuildSide(sideVertices, CPATCH_SIDE_NEGX); if (sideFlags & CPATCH_SIDE_POSX) BuildSide(sideVertices, CPATCH_SIDE_POSX); if (sideFlags & CPATCH_SIDE_NEGZ) BuildSide(sideVertices, CPATCH_SIDE_NEGZ); if (sideFlags & CPATCH_SIDE_POSZ) BuildSide(sideVertices, CPATCH_SIDE_POSZ); if (sideVertices.empty()) return; if (!m_VBSides) m_VBSides = g_VBMan.Allocate(sizeof(SSideVertex), sideVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBSides->m_Owner->UpdateChunkVertices(m_VBSides, &sideVertices[0]); } void CPatchRData::Build() { BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); } void CPatchRData::Update(CSimulation2* simulation) { m_Simulation = simulation; if (m_UpdateFlags!=0) { // TODO,RC 11/04/04 - need to only rebuild necessary bits of renderdata rather // than everything; it's complicated slightly because the blends are dependent // on both vertex and index data BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); m_UpdateFlags=0; } } // Types used for glMultiDrawElements batching: // To minimise the cost of memory allocations, everything used for computing // batches uses a arena allocator. (All allocations are short-lived so we can // just throw away the whole arena at the end of each frame.) using Arena = Allocators::DynamicArena<1 * MiB>; // std::map types with appropriate arena allocators and default comparison operator template using PooledBatchMap = std::map, ProxyAllocator, Arena>>; // Equivalent to "m[k]", when it returns a arena-allocated std::map (since we can't // use the default constructor in that case) template typename M::mapped_type& PooledMapGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, typename M::mapped_type(typename M::mapped_type::key_compare(), typename M::mapped_type::allocator_type(arena)) )).first->second; } // Equivalent to "m[k]", when it returns a std::pair of arena-allocated std::vectors template typename M::mapped_type& PooledPairGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, std::make_pair( typename M::mapped_type::first_type(typename M::mapped_type::first_type::allocator_type(arena)), typename M::mapped_type::second_type(typename M::mapped_type::second_type::allocator_type(arena)) ))).first->second; } // Each multidraw batch has a list of index counts, and a list of pointers-to-first-indexes using BatchElements = std::pair>, std::vector>>; // Group batches by index buffer using IndexBufferBatches = PooledBatchMap; // Group batches by vertex buffer using VertexBufferBatches = PooledBatchMap; // Group batches by texture using TextureBatches = PooledBatchMap; // Group batches by shaders. using ShaderTechniqueBatches = PooledBatchMap; void CPatchRData::RenderBases( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { Arena arena; ShaderTechniqueBatches batches(ShaderTechniqueBatches::key_compare(), (ShaderTechniqueBatches::allocator_type(arena))); PROFILE_START("compute batches"); // Collect all the patches' base splats into their appropriate batches for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; for (size_t j = 0; j < patch->m_Splats.size(); ++j) { SSplat& splat = patch->m_Splats[j]; const CMaterial& material = splat.m_Texture->GetMaterial(); if (material.GetShaderEffect().empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; } CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect( material.GetShaderEffect(), context, material.GetShaderDefines(0)); BatchElements& batch = PooledPairGet( PooledMapGet( PooledMapGet( PooledMapGet(batches, techBase, arena), splat.m_Texture, arena ), patch->m_VBBase->m_Owner, arena ), patch->m_VBBaseIndices->m_Owner, arena ); batch.first.push_back(splat.m_IndexCount); u8* indexBase = patch->m_VBBaseIndices->m_Owner->GetBindAddress(); batch.second.push_back(indexBase + sizeof(u16)*(patch->m_VBBaseIndices->m_Index + splat.m_IndexStart)); } } PROFILE_END("compute batches"); // Render each batch for (ShaderTechniqueBatches::iterator itTech = batches.begin(); itTech != batches.end(); ++itTech) { const CShaderTechniquePtr& techBase = itTech->first; const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { techBase->BeginPass(pass); const CShaderProgramPtr& shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(shader, shadow); TextureBatches& textureBatches = itTech->second; for (TextureBatches::iterator itt = textureBatches.begin(); itt != textureBatches.end(); ++itt) { if (itt->first->GetMaterial().GetSamplers().size() != 0) { const CMaterial::SamplersVector& samplers = itt->first->GetMaterial().GetSamplers(); for(const CMaterial::TextureSampler& samp : samplers) shader->BindTexture(samp.Name, samp.Sampler); itt->first->GetMaterial().GetStaticUniforms().BindUniforms(shader); float c = itt->first->GetTextureMatrix()[0]; float ms = itt->first->GetTextureMatrix()[8]; shader->Uniform(str_textureTransform, c, ms, -ms, 0.f); } else { shader->BindTexture(str_baseTex, g_Renderer.GetTextureManager().GetErrorTexture()); } for (VertexBufferBatches::iterator itv = itt->second.begin(); itv != itt->second.end(); ++itv) { GLsizei stride = sizeof(SBaseVertex); SBaseVertex *base = (SBaseVertex *)itv->first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); shader->NormalPointer(GL_FLOAT, stride, &base->m_Normal[0]); shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position[0]); shader->AssertPointersBound(); for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->Bind(); BatchElements& batch = it->second; if (!g_Renderer.m_SkipSubmit) { // Don't use glMultiDrawElements here since it doesn't have a significant // performance impact and it suffers from various driver bugs (e.g. it breaks // in Mesa 7.10 swrast with index VBOs) for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } techBase->EndPass(); } } CVertexBuffer::Unbind(); } /** * Helper structure for RenderBlends. */ struct SBlendBatch { SBlendBatch(Arena& arena) : m_Batches(VertexBufferBatches::key_compare(), VertexBufferBatches::allocator_type(arena)) { } CTerrainTextureEntry* m_Texture; CShaderTechniquePtr m_ShaderTech; VertexBufferBatches m_Batches; }; /** * Helper structure for RenderBlends. */ struct SBlendStackItem { SBlendStackItem(CVertexBuffer::VBChunk* v, CVertexBuffer::VBChunk* i, const std::vector& s, Arena& arena) : vertices(v), indices(i), splats(s.begin(), s.end(), SplatStack::allocator_type(arena)) { } using SplatStack = std::vector>; CVertexBuffer::VBChunk* vertices; CVertexBuffer::VBChunk* indices; SplatStack splats; }; void CPatchRData::RenderBlends( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { Arena arena; using BatchesStack = std::vector>; BatchesStack batches((BatchesStack::allocator_type(arena))); CShaderDefines contextBlend = context; contextBlend.Add(str_BLEND, str_1); PROFILE_START("compute batches"); // Reserve an arbitrary size that's probably big enough in most cases, // to avoid heavy reallocations batches.reserve(256); using BlendStacks = std::vector>; BlendStacks blendStacks((BlendStacks::allocator_type(arena))); blendStacks.reserve(patches.size()); // Extract all the blend splats from each patch for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; if (!patch->m_BlendSplats.empty()) { blendStacks.push_back(SBlendStackItem(patch->m_VBBlends, patch->m_VBBlendIndices, patch->m_BlendSplats, arena)); // Reverse the splats so the first to be rendered is at the back of the list std::reverse(blendStacks.back().splats.begin(), blendStacks.back().splats.end()); } } // Rearrange the collection of splats to be grouped by texture, preserving // order of splats within each patch: // (This is exactly the same algorithm used in CPatchRData::BuildBlends, // but applied to patch-sized splats rather than to tile-sized splats; // see that function for comments on the algorithm.) while (true) { if (!batches.empty()) { CTerrainTextureEntry* tex = batches.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (!splats.empty() && splats.back().m_Texture == tex) { CVertexBuffer::VBChunk* vertices = blendStacks[k].vertices; CVertexBuffer::VBChunk* indices = blendStacks[k].indices; BatchElements& batch = PooledPairGet(PooledMapGet(batches.back().m_Batches, vertices->m_Owner, arena), indices->m_Owner, arena); batch.first.push_back(splats.back().m_IndexCount); u8* indexBase = indices->m_Owner->GetBindAddress(); batch.second.push_back(indexBase + sizeof(u16)*(indices->m_Index + splats.back().m_IndexStart)); splats.pop_back(); } } } CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (splats.size() > bestStackSize) { bestStackSize = splats.size(); bestTex = splats.back().m_Texture; } } if (bestStackSize == 0) break; SBlendBatch layer(arena); layer.m_Texture = bestTex; if (!bestTex->GetMaterial().GetSamplers().empty()) { layer.m_ShaderTech = g_Renderer.GetShaderManager().LoadEffect( bestTex->GetMaterial().GetShaderEffect(), contextBlend, bestTex->GetMaterial().GetShaderDefines(0)); } batches.push_back(layer); } PROFILE_END("compute batches"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CVertexBuffer* lastVB = nullptr; CShaderProgramPtr previousShader; for (BatchesStack::iterator itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd) { while (itTechEnd != batches.end() && itTechEnd->m_ShaderTech == itTechBegin->m_ShaderTech) ++itTechEnd; const CShaderTechniquePtr& techBase = itTechBegin->m_ShaderTech; const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { techBase->BeginPass(pass); const CShaderProgramPtr& shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(shader, shadow); + Handle lastBlendTex = 0; + for (BatchesStack::iterator itt = itTechBegin; itt != itTechEnd; ++itt) { if (itt->m_Texture->GetMaterial().GetSamplers().empty()) continue; if (itt->m_Texture) { const CMaterial::SamplersVector& samplers = itt->m_Texture->GetMaterial().GetSamplers(); for (const CMaterial::TextureSampler& samp : samplers) shader->BindTexture(samp.Name, samp.Sampler); - shader->BindTexture(str_blendTex, itt->m_Texture->m_TerrainAlpha->second.m_hCompositeAlphaMap); + Handle currentBlendTex = itt->m_Texture->m_TerrainAlpha->second.m_hCompositeAlphaMap; + if (currentBlendTex != lastBlendTex) + { + shader->BindTexture(str_blendTex, currentBlendTex); + lastBlendTex = currentBlendTex; + } itt->m_Texture->GetMaterial().GetStaticUniforms().BindUniforms(shader); float c = itt->m_Texture->GetTextureMatrix()[0]; float ms = itt->m_Texture->GetTextureMatrix()[8]; shader->Uniform(str_textureTransform, c, ms, -ms, 0.f); } else { shader->BindTexture(str_baseTex, g_Renderer.GetTextureManager().GetErrorTexture()); } for (VertexBufferBatches::iterator itv = itt->m_Batches.begin(); itv != itt->m_Batches.end(); ++itv) { // Rebind the VB only if it changed since the last batch if (itv->first != lastVB || shader != previousShader) { lastVB = itv->first; previousShader = shader; GLsizei stride = sizeof(SBlendVertex); SBlendVertex *base = (SBlendVertex *)itv->first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); shader->NormalPointer(GL_FLOAT, stride, &base->m_Normal[0]); shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position[0]); shader->TexCoordPointer(GL_TEXTURE1, 2, GL_FLOAT, stride, &base->m_AlphaUVs[0]); } shader->AssertPointersBound(); for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->Bind(); BatchElements& batch = it->second; if (!g_Renderer.m_SkipSubmit) { for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_BlendSplats++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } techBase->EndPass(); } } glDisable(GL_BLEND); CVertexBuffer::Unbind(); } void CPatchRData::RenderStreams(const std::vector& patches, const CShaderProgramPtr& shader, int streamflags) { // Each batch has a list of index counts, and a list of pointers-to-first-indexes using StreamBatchElements = std::pair, std::vector > ; // Group batches by index buffer using StreamIndexBufferBatches = std::map ; // Group batches by vertex buffer using StreamVertexBufferBatches = std::map ; StreamVertexBufferBatches batches; PROFILE_START("compute batches"); // Collect all the patches into their appropriate batches for (const CPatchRData* patch : patches) { StreamBatchElements& batch = batches[patch->m_VBBase->m_Owner][patch->m_VBBaseIndices->m_Owner]; batch.first.push_back(patch->m_VBBaseIndices->m_Count); u8* indexBase = patch->m_VBBaseIndices->m_Owner->GetBindAddress(); batch.second.push_back(indexBase + sizeof(u16)*(patch->m_VBBaseIndices->m_Index)); } PROFILE_END("compute batches"); ENSURE(!(streamflags & ~(STREAM_POS|STREAM_POSTOUV0|STREAM_POSTOUV1))); // Render each batch for (const std::pair& streamBatch : batches) { GLsizei stride = sizeof(SBaseVertex); SBaseVertex *base = (SBaseVertex *)streamBatch.first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position); if (streamflags & STREAM_POSTOUV0) shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position); if (streamflags & STREAM_POSTOUV1) shader->TexCoordPointer(GL_TEXTURE1, 3, GL_FLOAT, stride, &base->m_Position); shader->AssertPointersBound(); for (const std::pair& batchIndexBuffer : streamBatch.second) { batchIndexBuffer.first->Bind(); const StreamBatchElements& batch = batchIndexBuffer.second; if (!g_Renderer.m_SkipSubmit) { for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } CVertexBuffer::Unbind(); } void CPatchRData::RenderOutline() { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; CVector3D pos; std::vector line; for (ssize_t i = 0, j = 0; i <= PATCH_SIZE; ++i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE, j = 1; j <= PATCH_SIZE; ++j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE-1, j = PATCH_SIZE; i >= 0; --i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = 0, j = PATCH_SIZE-1; j >= 0; --j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } #if CONFIG2_GLES #warning TODO: implement CPatchRData::RenderOutlines for GLES #else glVertexPointer(3, GL_FLOAT, sizeof(CVector3D), &line[0]); glDrawArrays(GL_LINE_STRIP, 0, line.size()); #endif } void CPatchRData::RenderSides(CShaderProgramPtr& shader) { ENSURE(m_UpdateFlags==0); if (!m_VBSides) return; glDisable(GL_CULL_FACE); SSideVertex *base = (SSideVertex *)m_VBSides->m_Owner->Bind(); // setup data pointers GLsizei stride = sizeof(SSideVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLE_STRIP, m_VBSides->m_Index, (GLsizei)m_VBSides->m_Count); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += m_VBSides->m_Count - 2; CVertexBuffer::Unbind(); glEnable(GL_CULL_FACE); } void CPatchRData::RenderPriorities(CTextRenderer& textRenderer) { CTerrain* terrain = m_Patch->m_Parent; const CCamera& camera = *(g_Game->GetView()->GetCamera()); for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Move a bit towards the center of the tile pos.X += TERRAIN_TILE_SIZE/4.f; pos.Z += TERRAIN_TILE_SIZE/4.f; float x, y; camera.GetScreenCoordinates(pos, x, y); textRenderer.PrintfAt(x, y, L"%d", m_Patch->m_MiniPatches[j][i].Priority); } } } // // Water build and rendering // // Build vertex buffer for water vertices over our patch void CPatchRData::BuildWater() { PROFILE3("build water"); // Number of vertices in each direction in each patch ENSURE(PATCH_SIZE % water_cell_size == 0); if (m_VBWater) { g_VBMan.Release(m_VBWater); m_VBWater = nullptr; } if (m_VBWaterIndices) { g_VBMan.Release(m_VBWaterIndices); m_VBWaterIndices = nullptr; } if (m_VBWaterShore) { g_VBMan.Release(m_VBWaterShore); m_VBWaterShore = nullptr; } if (m_VBWaterIndicesShore) { g_VBMan.Release(m_VBWaterIndicesShore); m_VBWaterIndicesShore = nullptr; } m_WaterBounds.SetEmpty(); // We need to use this to access the water manager or we may not have the // actual values but some compiled-in defaults CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); if (!cmpWaterManager) return; // Build data for water std::vector water_vertex_data; std::vector water_indices; u16 water_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_index_map, 0xFF, sizeof(water_index_map)); // Build data for shore std::vector water_vertex_data_shore; std::vector water_indices_shore; u16 water_shore_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_shore_index_map, 0xFF, sizeof(water_shore_index_map)); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); CPatch* patch = m_Patch; CTerrain* terrain = patch->m_Parent; ssize_t mapSize = terrain->GetVerticesPerSide(); // Top-left coordinates of our patch. ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // To whoever implements different water heights, this is a TODO: water height) float waterHeight = cmpWaterManager->GetExactWaterLevel(0.0f,0.0f); // The 4 points making a water tile. int moves[4][2] = { {0, 0}, {water_cell_size, 0}, {0, water_cell_size}, {water_cell_size, water_cell_size} }; // Where to look for when checking for water for shore tiles. int check[10][2] = { {0, 0}, {water_cell_size, 0}, {water_cell_size*2, 0}, {0, water_cell_size}, {0, water_cell_size*2}, {water_cell_size, water_cell_size}, {water_cell_size*2, water_cell_size*2}, {-water_cell_size, 0}, {0, -water_cell_size}, {-water_cell_size, -water_cell_size} }; // build vertices, uv, and shader varying for (ssize_t z = 0; z < PATCH_SIZE; z += water_cell_size) { for (ssize_t x = 0; x < PATCH_SIZE; x += water_cell_size) { // Check that this tile is close to water bool nearWater = false; for (size_t test = 0; test < 10; ++test) if (terrain->GetVertexGroundLevel(x + px + check[test][0], z + pz + check[test][1]) < waterHeight) nearWater = true; if (!nearWater) continue; // This is actually lying and I should call CcmpTerrain /*if (!terrain->IsOnMap(x+x1, z+z1) && !terrain->IsOnMap(x+x1, z+z1 + water_cell_size) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1 + water_cell_size)) continue;*/ for (int i = 0; i < 4; ++i) { if (water_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); float depth = waterHeight - vertex.m_Position.Y; vertex.m_Position.Y = waterHeight; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(WaterMgr->m_WindStrength[xx + zz*mapSize], depth); water_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data.size()); water_vertex_data.push_back(vertex); } water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices.push_back(water_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); // Check id this tile is partly over land. // If so add a square over the terrain. This is necessary to render waves that go on shore. if (terrain->GetVertexGroundLevel(x+px, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px, z+pz+water_cell_size) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz+water_cell_size) < waterHeight) continue; for (int i = 0; i < 4; ++i) { if (water_shore_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); vertex.m_Position.Y += 0.02f; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(0.0f, -5.0f); water_shore_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data_shore.size()); water_vertex_data_shore.push_back(vertex); } if (terrain->GetTriangulationDir(x + px, z + pz)) { water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); } else { water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); } } } // No vertex buffers if no data generated if (!water_indices.empty()) { m_VBWater = g_VBMan.Allocate(sizeof(SWaterVertex), water_vertex_data.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBWater->m_Owner->UpdateChunkVertices(m_VBWater, &water_vertex_data[0]); m_VBWaterIndices = g_VBMan.Allocate(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBWaterIndices->m_Owner->UpdateChunkVertices(m_VBWaterIndices, &water_indices[0]); } if (!water_indices_shore.empty()) { m_VBWaterShore = g_VBMan.Allocate(sizeof(SWaterVertex), water_vertex_data_shore.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); m_VBWaterShore->m_Owner->UpdateChunkVertices(m_VBWaterShore, &water_vertex_data_shore[0]); // Construct indices buffer m_VBWaterIndicesShore = g_VBMan.Allocate(sizeof(GLushort), water_indices_shore.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); m_VBWaterIndicesShore->m_Owner->UpdateChunkVertices(m_VBWaterIndicesShore, &water_indices_shore[0]); } } void CPatchRData::RenderWater(CShaderProgramPtr& shader, bool onlyShore, bool fixedPipeline) { ASSERT(m_UpdateFlags==0); if (g_Renderer.m_SkipSubmit || (!m_VBWater && !m_VBWaterShore)) return; #if !CONFIG2_GLES if (g_Renderer.GetWaterRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endif if (m_VBWater != 0x0 && !onlyShore) { SWaterVertex *base=(SWaterVertex *)m_VBWater->m_Owner->Bind(); // setup data pointers GLsizei stride = sizeof(SWaterVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWater->m_Index].m_Position); if (!fixedPipeline) shader->VertexAttribPointer(str_a_waterInfo, 2, GL_FLOAT, false, stride, &base[m_VBWater->m_Index].m_WaterData); shader->AssertPointersBound(); u8* indexBase = m_VBWaterIndices->m_Owner->Bind(); glDrawElements(GL_TRIANGLES, (GLsizei) m_VBWaterIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_VBWaterIndices->m_Index)); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndices->m_Count / 3; } if (m_VBWaterShore != 0x0 && g_Renderer.GetWaterManager()->m_WaterEffects && g_Renderer.GetWaterManager()->m_WaterFancyEffects) { SWaterVertex *base=(SWaterVertex *)m_VBWaterShore->m_Owner->Bind(); GLsizei stride = sizeof(SWaterVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWaterShore->m_Index].m_Position); if (!fixedPipeline) shader->VertexAttribPointer(str_a_waterInfo, 2, GL_FLOAT, false, stride, &base[m_VBWaterShore->m_Index].m_WaterData); shader->AssertPointersBound(); u8* indexBase = m_VBWaterIndicesShore->m_Owner->Bind(); glDrawElements(GL_TRIANGLES, (GLsizei) m_VBWaterIndicesShore->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_VBWaterIndicesShore->m_Index)); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndicesShore->m_Count / 3; } CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_Renderer.GetWaterRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } Index: ps/trunk/source/renderer/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp (revision 24832) +++ ps/trunk/source/renderer/WaterManager.cpp (revision 24833) @@ -1,1096 +1,1096 @@ /* 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 . */ /* * Water settings (speed, height) and texture management */ #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/tex/tex.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/WaterManager.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.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_WaterCurrentTex = 0; m_ReflectionTexture = 0; m_RefractionTexture = 0; 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_FancyTexture = 0; m_FancyTextureDepth = 0; m_ReflFboDepthTexture = 0; m_RefrFboDepthTexture = 0; 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; glDeleteTextures(1, &m_FancyTexture); glDeleteTextures(1, &m_FancyTextureDepth); glDeleteTextures(1, &m_ReflFboDepthTexture); glDeleteTextures(1, &m_RefrFboDepthTexture); pglDeleteFramebuffersEXT(1, &m_FancyEffectsFBO); pglDeleteFramebuffersEXT(1, &m_RefractionFbo); pglDeleteFramebuffersEXT(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 glGenTextures(1, &m_ReflectionTexture); glBindTexture(GL_TEXTURE_2D, m_ReflectionTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Create refraction texture glGenTextures(1, &m_RefractionTexture); glBindTexture(GL_TEXTURE_2D, m_RefractionTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Create depth textures glGenTextures(1, &m_ReflFboDepthTexture); glBindTexture(GL_TEXTURE_2D, m_ReflFboDepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); glGenTextures(1, &m_RefrFboDepthTexture); glBindTexture(GL_TEXTURE_2D, m_RefrFboDepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, (GLsizei)m_RefTextureSize, (GLsizei)m_RefTextureSize, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); // Create the Fancy Effects texture glGenTextures(1, &m_FancyTexture); glBindTexture(GL_TEXTURE_2D, m_FancyTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenTextures(1, &m_FancyTextureDepth); glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); Resize(); // Create the water framebuffers GLint currentFbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFbo); m_ReflectionFbo = 0; pglGenFramebuffersEXT(1, &m_ReflectionFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ReflectionFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ReflectionTexture, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_ReflFboDepthTexture, 0); ogl_WarnIfError(); GLenum status = pglCheckFramebufferStatusEXT(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; pglGenFramebuffersEXT(1, &m_RefractionFbo); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_RefractionFbo); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_RefractionTexture, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_RefrFboDepthTexture, 0); ogl_WarnIfError(); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Refraction framebuffer object incomplete: 0x%04X", status); g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } pglGenFramebuffersEXT(1, &m_FancyEffectsFBO); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FancyTexture, 0); pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_FancyTextureDepth, 0); ogl_WarnIfError(); status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Fancy Effects framebuffer object incomplete: 0x%04X", status); g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } pglBindFramebufferEXT(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() { glBindTexture(GL_TEXTURE_2D, m_FancyTexture); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, (GLsizei)g_Renderer.GetWidth(), (GLsizei)g_Renderer.GetHeight(), 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL); glBindTexture(GL_TEXTURE_2D, m_FancyTextureDepth); 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(); glDeleteTextures(1, &m_ReflectionTexture); glDeleteTextures(1, &m_RefractionTexture); pglDeleteFramebuffersEXT(1, &m_RefractionFbo); pglDeleteFramebuffersEXT(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() { 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) { #if CONFIG2_GLES #warning Fix WaterManager::RenderWaves on GLES #else if (g_Renderer.m_SkipSubmit || !m_WaterFancyEffects) return; pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO); GLuint attachments[1] = { GL_COLOR_ATTACHMENT0_EXT }; pglDrawBuffers(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); CShaderDefines none; CShaderProgramPtr shader = g_Renderer.GetShaderManager().LoadProgram("glsl/waves", none); shader->Bind(); 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.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) - pglVertexAttribPointerARB(2, 2, GL_FLOAT, GL_TRUE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2 + pglVertexAttribPointerARB(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(); } shader->Unbind(); pglBindFramebufferEXT(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)); ssize_t windX = round(1.f / windDir.X); ssize_t windY = round(1.f / 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); 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); 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() { return m_RenderWater && g_RenderingOptions.GetWaterEffects() && g_Renderer.GetCapabilities().m_PrettyWater; }