stk-code_catmod/src/graphics/cpu_particle_manager.cpp
2018-08-31 22:33:55 +02:00

341 lines
12 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 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/cpu_particle_manager.hpp"
#include "graphics/stk_particle.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "utils/log.hpp"
#include <algorithm>
#ifndef SERVER_ONLY
#include "graphics/texture_shader.hpp"
// ============================================================================
/** A Shader to render particles.
*/
class ParticleRenderer : public TextureShader<ParticleRenderer, 2, int, float>
{
public:
ParticleRenderer()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "simple_particle.vert",
GL_FRAGMENT_SHADER, "simple_particle.frag");
assignUniforms("flips", "billboard");
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED,
1, "dtex", ST_NEAREST_FILTERED);
} // ParticleRenderer
}; // ParticleRenderer
// ============================================================================
/** A Shader to render alpha-test particles.
*/
class AlphaTestParticleRenderer : public TextureShader
<AlphaTestParticleRenderer, 1, int>
{
public:
AlphaTestParticleRenderer()
{
loadProgram(PARTICLES_RENDERING,
GL_VERTEX_SHADER, "alphatest_particle.vert",
GL_FRAGMENT_SHADER, "alphatest_particle.frag");
assignUniforms("flips");
assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED);
} // AlphaTestParticleRenderer
}; // AlphaTestParticleRenderer
// ============================================================================
void CPUParticleManager::addParticleNode(STKParticle* node)
{
if (node->getMaterialCount() != 1)
{
Log::error("CPUParticleManager", "More than 1 material");
return;
}
video::ITexture* t = node->getMaterial(0).getTexture(0);
assert(t != NULL);
std::string tex_name = t->getName().getPtr();
Material* m = NULL;
if (m_material_map.find(tex_name) == m_material_map.end())
{
m = material_manager->getMaterialFor(t);
m_material_map[tex_name] = m;
if (m == NULL)
{
Log::error("CPUParticleManager", "Missing material for particle");
}
}
m = m_material_map.at(tex_name);
if (m == NULL)
{
return;
}
if (node->getFlips())
{
m_flips_material.insert(tex_name);
}
m_particles_queue[tex_name].push_back(node);
} // addParticleNode
// ============================================================================
GLuint CPUParticleManager::m_particle_quad = 0;
// ----------------------------------------------------------------------------
CPUParticleManager::GLParticle::GLParticle(bool flips)
{
m_size = 1;
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, m_size * 20, NULL,
GL_DYNAMIC_DRAW);
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, 0);
glVertexAttribDivisorARB(0, 1);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 20,
(void*)12);
glVertexAttribDivisorARB(1, 1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_HALF_FLOAT, GL_FALSE, 20,
(void*)16);
glVertexAttribDivisorARB(2, 1);
glBindBuffer(GL_ARRAY_BUFFER, m_particle_quad);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, 16, 0);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 16, (void*)8);
if (flips)
{
glBindBuffer(GL_ARRAY_BUFFER, STKParticle::getFlipsBuffer());
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 1, GL_FLOAT, GL_FALSE, 4, 0);
glVertexAttribDivisorARB(6, 1);
}
glBindVertexArray(0);
} // GLParticle
// ----------------------------------------------------------------------------
CPUParticleManager::CPUParticleManager()
{
const float vertices[] =
{
-0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 1.0f, 1.0f,
};
glGenBuffers(1, &m_particle_quad);
glBindBuffer(GL_ARRAY_BUFFER, m_particle_quad);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// For preloading shaders
if (CVS->isGLSL())
{
ParticleRenderer::getInstance();
AlphaTestParticleRenderer::getInstance();
}
} // CPUParticleManager
// ----------------------------------------------------------------------------
void CPUParticleManager::addBillboardNode(scene::IBillboardSceneNode* node)
{
video::ITexture* t = node->getMaterial(0).getTexture(0);
if (t == NULL)
{
return;
}
std::string tex_name = t->getName().getPtr();
tex_name = std::string("_bb_") + tex_name;
Material* m = NULL;
if (m_material_map.find(tex_name) == m_material_map.end())
{
m = material_manager->getMaterialFor(t);
m_material_map[tex_name] = m;
if (m == NULL)
{
Log::error("CPUParticleManager", "Missing material for billboard");
}
}
m = m_material_map.at(tex_name);
if (m == NULL)
{
return;
}
m_billboards_queue[tex_name].push_back(node);
} // addBillboardNode
// ----------------------------------------------------------------------------
void CPUParticleManager::generateAll()
{
for (auto& p : m_particles_queue)
{
if (p.second.empty())
{
continue;
}
for (auto& q : p.second)
{
q->generate(&m_particles_generated[p.first]);
}
if (isFlipsMaterial(p.first))
{
STKParticle::updateFlips(unsigned
(m_particles_queue.at(p.first).size() *
m_particles_queue.at(p.first)[0]->getMaxCount()));
}
}
for (auto& p : m_billboards_queue)
{
if (p.second.empty())
{
continue;
}
for (auto& q : p.second)
{
m_particles_generated[p.first].emplace_back(q);
}
}
} // generateAll
// ----------------------------------------------------------------------------
void CPUParticleManager::uploadAll()
{
for (auto& p : m_particles_generated)
{
if (p.second.empty())
{
continue;
}
unsigned vbo_size = (unsigned)(m_particles_generated[p.first].size());
if (m_gl_particles.find(p.first) == m_gl_particles.end())
{
m_gl_particles[p.first] = std::unique_ptr<GLParticle>
(new GLParticle(isFlipsMaterial(p.first)));
}
glBindBuffer(GL_ARRAY_BUFFER, m_gl_particles.at(p.first)->m_vbo);
// Check "real" particle buffer size in opengl
if (m_gl_particles.at(p.first)->m_size < vbo_size)
{
m_gl_particles.at(p.first)->m_size = vbo_size * 2;
m_particles_generated.at(p.first).reserve(vbo_size * 2);
glBufferData(GL_ARRAY_BUFFER, vbo_size * 2 * 20,
m_particles_generated.at(p.first).data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
continue;
}
void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, vbo_size * 20,
GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT);
memcpy(ptr, m_particles_generated[p.first].data(), vbo_size * 20);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
} // uploadAll
// ----------------------------------------------------------------------------
void CPUParticleManager::drawAll()
{
using namespace SP;
std::vector<std::pair<Material*, std::string> > particle_drawn;
for (auto& p : m_particles_generated)
{
if (!p.second.empty())
{
particle_drawn.emplace_back(m_material_map.at(p.first), p.first);
}
}
std::sort(particle_drawn.begin(), particle_drawn.end(),
[](const std::pair<Material*, std::string>& a,
const std::pair<Material*, std::string>& b)->bool
{
return a.first->getShaderName() > b.first->getShaderName();
});
std::string shader_name;
for (auto& p : particle_drawn)
{
const bool flips = isFlipsMaterial(p.second);
const float billboard = p.second.substr(0, 4) == "_bb_" ? 1.0f : 0.0f;
Material* cur_mat = p.first;
if (cur_mat->getShaderName() != shader_name)
{
shader_name = cur_mat->getShaderName();
if (cur_mat->getShaderName() == "additive")
{
ParticleRenderer::getInstance()->use();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
}
else if (cur_mat->getShaderName() == "alphablend")
{
ParticleRenderer::getInstance()->use();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
AlphaTestParticleRenderer::getInstance()->use();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
}
}
if (cur_mat->getShaderName() == "additive" ||
cur_mat->getShaderName() == "alphablend")
{
ParticleRenderer::getInstance()->setTextureUnits
(cur_mat->getTexture()->getOpenGLTextureName(),
CVS->isDeferredEnabled() ?
irr_driver->getDepthStencilTexture() : 0);
ParticleRenderer::getInstance()->setUniforms(flips, billboard);
}
else
{
AlphaTestParticleRenderer::getInstance()->setTextureUnits
(cur_mat->getTexture()->getOpenGLTextureName());
AlphaTestParticleRenderer::getInstance()->setUniforms(flips);
}
glBindVertexArray(m_gl_particles.at(p.second)->m_vao);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4,
(unsigned)m_particles_generated.at(p.second).size());
}
} // drawAll
#endif