491 lines
15 KiB
C++
491 lines
15 KiB
C++
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2018 SuperTuxKart-Team
|
|
//
|
|
// This program 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 3
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program 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 this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "graphics/sp/sp_mesh_buffer.hpp"
|
|
#include "graphics/sp/sp_texture.hpp"
|
|
#include "graphics/central_settings.hpp"
|
|
#include "graphics/graphics_restrictions.hpp"
|
|
#include "graphics/material.hpp"
|
|
#include "graphics/sp/sp_shader.hpp"
|
|
#include "graphics/sp/sp_shader_manager.hpp"
|
|
#include "graphics/sp/sp_texture_manager.hpp"
|
|
#include "race/race_manager.hpp"
|
|
#include "utils/mini_glm.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
|
|
namespace SP
|
|
{
|
|
// ----------------------------------------------------------------------------
|
|
SPMeshBuffer::~SPMeshBuffer()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
for (unsigned i = 0; i < DCT_FOR_VAO; i++)
|
|
{
|
|
if (m_vao[i] != 0)
|
|
{
|
|
glDeleteVertexArrays(1, &m_vao[i]);
|
|
}
|
|
if (m_ins_array[i] != 0)
|
|
{
|
|
if (CVS->isARBBufferStorageUsable())
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]);
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
glDeleteBuffers(1, &m_ins_array[i]);
|
|
}
|
|
}
|
|
if (m_ibo != 0)
|
|
{
|
|
glDeleteBuffers(1, &m_ibo);
|
|
}
|
|
if (m_vbo != 0)
|
|
{
|
|
glDeleteBuffers(1, &m_vbo);
|
|
}
|
|
#endif
|
|
} // ~SPMeshBuffer
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::initDrawMaterial()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
Material* m = std::get<2>(m_stk_material[0]);
|
|
if (race_manager->getReverseTrack() && m->getMirrorAxisInReverse() != ' ')
|
|
{
|
|
for (unsigned i = 0; i < getVertexCount(); i++)
|
|
{
|
|
using namespace MiniGLM;
|
|
if (m->getMirrorAxisInReverse() == 'V')
|
|
{
|
|
m_vertices[i].m_all_uvs[1] =
|
|
toFloat16(1.0f - toFloat32(m_vertices[i].m_all_uvs[1]));
|
|
}
|
|
else
|
|
{
|
|
m_vertices[i].m_all_uvs[0] =
|
|
toFloat16(1.0f - toFloat32(m_vertices[i].m_all_uvs[0]));
|
|
}
|
|
}
|
|
} // reverse track and texture needs mirroring
|
|
#endif
|
|
} // initDrawMaterial
|
|
|
|
// ----------------------------------------------------------------------------
|
|
bool SPMeshBuffer::initTexture()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
for (unsigned i = 0; i < m_stk_material.size(); i++)
|
|
{
|
|
for (unsigned j = 0; j < 6; j++)
|
|
{
|
|
if (!m_textures[i][j]->initialized())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
} // initTexture
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::uploadGLMesh()
|
|
{
|
|
if (m_uploaded_gl)
|
|
{
|
|
return;
|
|
}
|
|
m_uploaded_gl = true;
|
|
#ifndef SERVER_ONLY
|
|
if (!m_shaders[0])
|
|
{
|
|
Log::warn("SPMeshBuffer", "%s shader is missing",
|
|
std::get<2>(m_stk_material[0])->getShaderName().c_str());
|
|
return;
|
|
}
|
|
|
|
m_textures.resize(m_stk_material.size());
|
|
for (unsigned i = 0; i < m_stk_material.size(); i++)
|
|
{
|
|
for (unsigned j = 0; j < 6; j++)
|
|
{
|
|
m_textures[i][j] = SPTextureManager::get()->getTexture
|
|
(m_shaders[0]->hasTextureLayer(j) ?
|
|
std::get<2>(m_stk_material[i])->getSamplerPath(j) : "",
|
|
j == 0 ? std::get<2>(m_stk_material[i]) : NULL,
|
|
m_shaders[0]->isSrgbForTextureLayer(j),
|
|
std::get<2>(m_stk_material[i])->getContainerId());
|
|
}
|
|
// Use the original spm uv texture 1 and 2 for compare in scene manager
|
|
m_tex_cmp[std::get<2>(m_stk_material[i])->getSamplerPath(0) +
|
|
std::get<2>(m_stk_material[i])->getSamplerPath(1)] = i;
|
|
}
|
|
|
|
bool use_2_uv = std::get<2>(m_stk_material[0])->use2UV();
|
|
bool use_tangents = m_shaders[0]->useTangents();
|
|
const unsigned pitch = 48 - (use_tangents ? 0 : 4) - (use_2_uv ? 0 : 4) -
|
|
(m_skinned ? 0 : 16);
|
|
m_pitch = pitch;
|
|
|
|
if (m_vbo != 0)
|
|
{
|
|
glDeleteBuffers(1, &m_vbo);
|
|
}
|
|
glGenBuffers(1, &m_vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
|
|
|
unsigned v_size = (unsigned)m_vertices.size() * pitch;
|
|
glBufferData(GL_ARRAY_BUFFER, v_size, NULL, GL_DYNAMIC_DRAW);
|
|
size_t offset = 0;
|
|
char* ptr = (char*)glMapBufferRange(GL_ARRAY_BUFFER, 0, v_size,
|
|
GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT |
|
|
GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
v_size = 0;
|
|
for (unsigned i = 0 ; i < m_vertices.size(); i++)
|
|
{
|
|
offset = 0;
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_position.X, 12);
|
|
offset += 12;
|
|
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_normal, 12);
|
|
offset += 4;
|
|
|
|
video::SColor vc = m_vertices[i].m_color;
|
|
if (CVS->isDeferredEnabled())
|
|
{
|
|
vc.setRed(srgb255ToLinear(vc.getRed()));
|
|
vc.setGreen(srgb255ToLinear(vc.getGreen()));
|
|
vc.setBlue(srgb255ToLinear(vc.getBlue()));
|
|
}
|
|
memcpy(ptr + v_size + offset, &vc, 4);
|
|
offset += 4;
|
|
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_all_uvs[0], 4);
|
|
offset += 4;
|
|
if (use_2_uv)
|
|
{
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_all_uvs[2], 4);
|
|
offset += 4;
|
|
}
|
|
if (use_tangents)
|
|
{
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_tangent, 4);
|
|
offset += 4;
|
|
}
|
|
if (m_skinned)
|
|
{
|
|
memcpy(ptr + v_size + offset, &m_vertices[i].m_joint_idx[0], 16);
|
|
}
|
|
v_size += pitch;
|
|
}
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
|
|
SPTextureManager::get()->increaseGLCommandFunctionCount(1);
|
|
SPTextureManager::get()->addGLCommandFunction
|
|
(std::bind(&SPMeshBuffer::initTexture, this));
|
|
|
|
if (m_ibo != 0)
|
|
{
|
|
glDeleteBuffers(1, &m_ibo);
|
|
}
|
|
glGenBuffers(1, &m_ibo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * 2,
|
|
m_indices.data(), GL_STATIC_DRAW);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
#endif
|
|
} // uploadGLMesh
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::recreateVAO(unsigned i)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
if (!m_shaders[0])
|
|
{
|
|
return;
|
|
}
|
|
bool use_2_uv = std::get<2>(m_stk_material[0])->use2UV();
|
|
bool use_tangents = m_shaders[0]->useTangents();
|
|
const unsigned pitch = m_pitch;
|
|
|
|
size_t offset = 0;
|
|
|
|
if (m_ins_array[i] == 0)
|
|
{
|
|
glGenBuffers(1, &m_ins_array[i]);
|
|
}
|
|
else
|
|
{
|
|
if (CVS->isARBBufferStorageUsable())
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]);
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
glDeleteBuffers(1, &m_ins_array[i]);
|
|
glGenBuffers(1, &m_ins_array[i]);
|
|
}
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]);
|
|
#ifndef USE_GLES2
|
|
if (CVS->isARBBufferStorageUsable())
|
|
{
|
|
glBufferStorage(GL_ARRAY_BUFFER, m_gl_instance_size[i] * 44, NULL,
|
|
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
|
|
m_ins_dat_mapped_ptr[i] = glMapBufferRange(GL_ARRAY_BUFFER, 0,
|
|
m_gl_instance_size[i] * 44,
|
|
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
glBufferData(GL_ARRAY_BUFFER, m_gl_instance_size[i] * 44, NULL,
|
|
GL_DYNAMIC_DRAW);
|
|
}
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
if (m_vao[i] != 0)
|
|
{
|
|
glDeleteVertexArrays(1, &m_vao[i]);
|
|
}
|
|
glGenVertexArrays(1, &m_vao[i]);
|
|
glBindVertexArray(m_vao[i]);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
|
// Position
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, pitch, (void*)offset);
|
|
offset += 12;
|
|
|
|
// Normal, if 10bit vector normalization is wrongly done by drivers, use
|
|
// original value and normalize ourselves in shader
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 4, GL_INT_2_10_10_10_REV,
|
|
GraphicsRestrictions::isDisabled
|
|
(GraphicsRestrictions::GR_CORRECT_10BIT_NORMALIZATION) ?
|
|
GL_FALSE : GL_TRUE, pitch, (void*)offset);
|
|
offset += 4;
|
|
|
|
// Vertex color
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, pitch,
|
|
(void*)offset);
|
|
offset += 4;
|
|
|
|
// 1st texture coordinates
|
|
glEnableVertexAttribArray(3);
|
|
glVertexAttribPointer(3, 2, GL_HALF_FLOAT, GL_FALSE, pitch, (void*)offset);
|
|
offset += 4;
|
|
if (use_2_uv)
|
|
{
|
|
// 2nd texture coordinates
|
|
glEnableVertexAttribArray(4);
|
|
glVertexAttribPointer(4, 2, GL_HALF_FLOAT, GL_FALSE, pitch,
|
|
(void*)offset);
|
|
offset += 4;
|
|
}
|
|
else
|
|
{
|
|
glDisableVertexAttribArray(4);
|
|
}
|
|
|
|
if (use_tangents)
|
|
{
|
|
// Tangent and bi-tanget sign
|
|
glEnableVertexAttribArray(5);
|
|
glVertexAttribPointer(5, 4, GL_INT_2_10_10_10_REV,
|
|
GraphicsRestrictions::isDisabled
|
|
(GraphicsRestrictions::GR_CORRECT_10BIT_NORMALIZATION) ?
|
|
GL_FALSE : GL_TRUE, pitch, (void*)offset);
|
|
offset += 4;
|
|
}
|
|
else
|
|
{
|
|
glDisableVertexAttribArray(5);
|
|
}
|
|
|
|
if (m_skinned)
|
|
{
|
|
// 4 Joint indices
|
|
glEnableVertexAttribArray(6);
|
|
glVertexAttribIPointer(6, 4, GL_SHORT, pitch, (void*)offset);
|
|
offset += 8;
|
|
// 4 Joint weights
|
|
glEnableVertexAttribArray(7);
|
|
glVertexAttribPointer(7, 4, GL_HALF_FLOAT, GL_FALSE, pitch,
|
|
(void*)offset);
|
|
offset += 8;
|
|
}
|
|
else
|
|
{
|
|
glDisableVertexAttribArray(6);
|
|
glDisableVertexAttribArray(7);
|
|
}
|
|
glDisableVertexAttribArray(13);
|
|
glDisableVertexAttribArray(14);
|
|
glDisableVertexAttribArray(15);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]);
|
|
// Origin
|
|
glEnableVertexAttribArray(8);
|
|
glVertexAttribPointer(8, 3, GL_FLOAT, GL_FALSE, 44, (void*)0);
|
|
glVertexAttribDivisorARB(8, 1);
|
|
// Rotation (quaternion in 4 32bit floats)
|
|
glEnableVertexAttribArray(9);
|
|
glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, 44, (void*)12);
|
|
glVertexAttribDivisorARB(9, 1);
|
|
// Scale (3 half floats and .w unused)
|
|
glEnableVertexAttribArray(10);
|
|
glVertexAttribPointer(10, 4, GL_HALF_FLOAT, GL_FALSE, 44, (void*)28);
|
|
glVertexAttribDivisorARB(10, 1);
|
|
// Texture translation
|
|
glEnableVertexAttribArray(11);
|
|
glVertexAttribPointer(11, 2, GL_SHORT, GL_TRUE, 44, (void*)36);
|
|
glVertexAttribDivisorARB(11, 1);
|
|
// Misc data (skinning offset and hue change)
|
|
glEnableVertexAttribArray(12);
|
|
glVertexAttribIPointer(12, 2, GL_SHORT, 44, (void*)40);
|
|
glVertexAttribDivisorARB(12, 1);
|
|
|
|
glBindVertexArray(0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
#endif
|
|
} // uploadGLMesh
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::uploadInstanceData()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
for (unsigned i = 0; i < DCT_FOR_VAO; i++)
|
|
{
|
|
if (m_ins_dat[i].empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
unsigned new_size =
|
|
m_gl_instance_size[i] == 0 ? 1 : m_gl_instance_size[i];
|
|
while (m_ins_dat[i].size() > new_size)
|
|
{
|
|
// Power of 2 allocation strategy, like std::vector in gcc
|
|
new_size <<= 1;
|
|
}
|
|
if (new_size != m_gl_instance_size[i])
|
|
{
|
|
m_gl_instance_size[i] = new_size;
|
|
recreateVAO(i);
|
|
}
|
|
if (CVS->isARBBufferStorageUsable())
|
|
{
|
|
memcpy(m_ins_dat_mapped_ptr[i], m_ins_dat[i].data(),
|
|
m_ins_dat[i].size() * 44);
|
|
}
|
|
else
|
|
{
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ins_array[i]);
|
|
void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0,
|
|
m_ins_dat[i].size() * 44, GL_MAP_WRITE_BIT |
|
|
GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
memcpy(ptr, m_ins_dat[i].data(), m_ins_dat[i].size() * 44);
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
}
|
|
#endif
|
|
m_uploaded_instance = true;
|
|
} // uploadInstanceData
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::enableTextureMatrix(unsigned mat_id)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
assert(mat_id < m_stk_material.size());
|
|
// Make the 31 bit in normal to be 1
|
|
uploadGLMesh();
|
|
if (m_vbo == 0 || m_ibo == 0)
|
|
return;
|
|
auto& ret = m_stk_material[mat_id];
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
|
std::set<uint16_t> used_vertices;
|
|
for (unsigned int j = 0; j < std::get<1>(ret); j += 3)
|
|
{
|
|
for (unsigned int k = 0; k < 3; k++)
|
|
{
|
|
const uint16_t vertex_id = m_indices[std::get<0>(ret) + j + k];
|
|
auto ret = used_vertices.find(vertex_id);
|
|
if (ret == used_vertices.end())
|
|
{
|
|
if ((m_vertices[vertex_id].m_normal & (1 << 30)) != 0)
|
|
{
|
|
// Already enabled
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
return;
|
|
}
|
|
used_vertices.insert(vertex_id);
|
|
m_vertices[vertex_id].m_normal |= 1 << 30;
|
|
glBufferSubData(GL_ARRAY_BUFFER,
|
|
(vertex_id * m_pitch) + 12 /*3 position*/, 4,
|
|
&m_vertices[vertex_id].m_normal);
|
|
}
|
|
}
|
|
}
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
#endif
|
|
} // enableTextureMatrix
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::reloadTextureCompare()
|
|
{
|
|
assert(!m_textures.empty());
|
|
m_tex_cmp.clear();
|
|
for (unsigned i = 0; i < m_stk_material.size(); i++)
|
|
{
|
|
const std::string name =
|
|
m_textures[i][0]->getPath() + m_textures[i][1]->getPath();
|
|
m_tex_cmp[name] = i;
|
|
}
|
|
} // reloadTextureCompare
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void SPMeshBuffer::setSTKMaterial(Material* m)
|
|
{
|
|
m_stk_material[0] = std::make_tuple(0u, getIndexCount(), m);
|
|
const std::string shader_name =
|
|
std::get<2>(m_stk_material[0])->getShaderName();
|
|
const std::string skinned_shader_name =
|
|
std::get<2>(m_stk_material[0])->getShaderName() + "_skinned";
|
|
|
|
m_shaders[0] = SPShaderManager::get()->getSPShader(shader_name);
|
|
if (!m_shaders[0])
|
|
{
|
|
Log::warn("SPMeshBuffer", "%s shader is missing, fallback to solid",
|
|
shader_name.c_str());
|
|
m_shaders[0] = SPShaderManager::get()->getSPShader("solid");
|
|
}
|
|
|
|
m_shaders[1] = SPShaderManager::get()->getSPShader(skinned_shader_name);
|
|
if (!m_shaders[1])
|
|
m_shaders[1] = SPShaderManager::get()->getSPShader("solid_skinned");
|
|
} // setSTKMaterial
|
|
|
|
}
|