1 //  SuperTuxKart - a fun racing game with go-kart
2 //  Copyright (C) 2017 SuperTuxKart-Team
3 //
4 //  This program is free software; you can redistribute it and/or
5 //  modify it under the terms of the GNU General Public License
6 //  as published by the Free Software Foundation; either version 3
7 //  of the License, or (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 
19 #include "graphics/cpu_particle_manager.hpp"
20 #include "graphics/stk_particle.hpp"
21 #include "graphics/irr_driver.hpp"
22 #include "graphics/material.hpp"
23 #include "graphics/material_manager.hpp"
24 #include "utils/log.hpp"
25 
26 #include <algorithm>
27 
28 #ifndef SERVER_ONLY
29 #include "graphics/texture_shader.hpp"
30 
31 // ============================================================================
32 /** A Shader to render particles.
33 */
34 class ParticleRenderer : public TextureShader<ParticleRenderer, 2, int, float>
35 {
36 public:
ParticleRenderer()37     ParticleRenderer()
38     {
39         loadProgram(PARTICLES_RENDERING,
40                     GL_VERTEX_SHADER,   "simple_particle.vert",
41                     GL_FRAGMENT_SHADER, "simple_particle.frag");
42         assignUniforms("flips", "billboard");
43         assignSamplerNames(0, "tex",  ST_TRILINEAR_ANISOTROPIC_FILTERED,
44                            1, "dtex", ST_NEAREST_FILTERED);
45     }   // ParticleRenderer
46 };   // ParticleRenderer
47 
48 // ============================================================================
49 /** A Shader to render alpha-test particles.
50 */
51 class AlphaTestParticleRenderer : public TextureShader
52     <AlphaTestParticleRenderer, 1, int>
53 {
54 public:
AlphaTestParticleRenderer()55     AlphaTestParticleRenderer()
56     {
57         loadProgram(PARTICLES_RENDERING,
58                     GL_VERTEX_SHADER,   "alphatest_particle.vert",
59                     GL_FRAGMENT_SHADER, "alphatest_particle.frag");
60         assignUniforms("flips");
61         assignSamplerNames(0, "tex",  ST_TRILINEAR_ANISOTROPIC_FILTERED);
62     }   // AlphaTestParticleRenderer
63 };   // AlphaTestParticleRenderer
64 
65 // ============================================================================
addParticleNode(STKParticle * node)66 void CPUParticleManager::addParticleNode(STKParticle* node)
67 {
68     if (node->getMaterialCount() != 1)
69     {
70         Log::error("CPUParticleManager", "More than 1 material");
71         return;
72     }
73     video::ITexture* t = node->getMaterial(0).getTexture(0);
74     assert(t != NULL);
75     std::string tex_name = t->getName().getPtr();
76     Material* m = NULL;
77     if (m_material_map.find(tex_name) == m_material_map.end())
78     {
79         m = material_manager->getMaterialFor(t);
80         m_material_map[tex_name] = m;
81         if (m == NULL)
82         {
83             Log::error("CPUParticleManager", "Missing material for particle");
84         }
85     }
86     m = m_material_map.at(tex_name);
87     if (m == NULL)
88     {
89         return;
90     }
91     if (node->getFlips())
92     {
93         m_flips_material.insert(tex_name);
94     }
95     m_particles_queue[tex_name].push_back(node);
96 }   // addParticleNode
97 
98 // ============================================================================
99 GLuint CPUParticleManager::m_particle_quad = 0;
100 // ----------------------------------------------------------------------------
GLParticle(bool flips)101 CPUParticleManager::GLParticle::GLParticle(bool flips)
102 {
103     m_size = 1;
104     glGenBuffers(1, &m_vbo);
105     glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
106     glBufferData(GL_ARRAY_BUFFER, m_size * 20, NULL,
107         GL_DYNAMIC_DRAW);
108     glGenVertexArrays(1, &m_vao);
109     glBindVertexArray(m_vao);
110     glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
111     glEnableVertexAttribArray(0);
112     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, 0);
113     glVertexAttribDivisorARB(0, 1);
114     glEnableVertexAttribArray(1);
115     glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 20,
116         (void*)12);
117     glVertexAttribDivisorARB(1, 1);
118     glEnableVertexAttribArray(2);
119     glVertexAttribPointer(2, 2, GL_HALF_FLOAT, GL_FALSE, 20,
120         (void*)16);
121     glVertexAttribDivisorARB(2, 1);
122     glBindBuffer(GL_ARRAY_BUFFER, m_particle_quad);
123     glEnableVertexAttribArray(4);
124     glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, 16, 0);
125     glEnableVertexAttribArray(3);
126     glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 16, (void*)8);
127     if (flips)
128     {
129         glBindBuffer(GL_ARRAY_BUFFER, STKParticle::getFlipsBuffer());
130         glEnableVertexAttribArray(6);
131         glVertexAttribPointer(6, 1, GL_FLOAT, GL_FALSE, 4, 0);
132         glVertexAttribDivisorARB(6, 1);
133     }
134     glBindVertexArray(0);
135 }   // GLParticle
136 
137 // ----------------------------------------------------------------------------
CPUParticleManager()138 CPUParticleManager::CPUParticleManager()
139 {
140     assert(CVS->isGLSL());
141 
142     const float vertices[] =
143     {
144         -0.5f, 0.5f, 0.0f, 0.0f,
145         0.5f, 0.5f, 1.0f, 0.0f,
146         -0.5f, -0.5f, 0.0f, 1.0f,
147         0.5f, -0.5f, 1.0f, 1.0f,
148     };
149     glGenBuffers(1, &m_particle_quad);
150     glBindBuffer(GL_ARRAY_BUFFER, m_particle_quad);
151     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
152     glBindBuffer(GL_ARRAY_BUFFER, 0);
153 
154     // For preloading shaders
155     ParticleRenderer::getInstance();
156     AlphaTestParticleRenderer::getInstance();
157 }   // CPUParticleManager
158 
159 // ----------------------------------------------------------------------------
addBillboardNode(scene::IBillboardSceneNode * node)160 void CPUParticleManager::addBillboardNode(scene::IBillboardSceneNode* node)
161 {
162     video::ITexture* t = node->getMaterial(0).getTexture(0);
163     if (t == NULL)
164     {
165         return;
166     }
167     std::string tex_name = t->getName().getPtr();
168     tex_name = std::string("_bb_") + tex_name;
169     Material* m = NULL;
170     if (m_material_map.find(tex_name) == m_material_map.end())
171     {
172         m = material_manager->getMaterialFor(t);
173         m_material_map[tex_name] = m;
174         if (m == NULL)
175         {
176             Log::error("CPUParticleManager", "Missing material for billboard");
177         }
178     }
179     m = m_material_map.at(tex_name);
180     if (m == NULL)
181     {
182         return;
183     }
184     m_billboards_queue[tex_name].push_back(node);
185 }   // addBillboardNode
186 
187 // ----------------------------------------------------------------------------
generateAll()188 void CPUParticleManager::generateAll()
189 {
190     for (auto& p : m_particles_queue)
191     {
192         if (p.second.empty())
193         {
194             continue;
195         }
196         for (auto& q : p.second)
197         {
198             q->generate(&m_particles_generated[p.first]);
199         }
200         if (isFlipsMaterial(p.first))
201         {
202             STKParticle::updateFlips(unsigned
203                 (m_particles_queue.at(p.first).size() *
204                 m_particles_queue.at(p.first)[0]->getMaxCount()));
205         }
206     }
207     for (auto& p : m_billboards_queue)
208     {
209         if (p.second.empty())
210         {
211             continue;
212         }
213         for (auto& q : p.second)
214         {
215             m_particles_generated[p.first].emplace_back(q);
216         }
217     }
218 }   // generateAll
219 
220 // ----------------------------------------------------------------------------
uploadAll()221 void CPUParticleManager::uploadAll()
222 {
223     for (auto& p : m_particles_generated)
224     {
225         if (p.second.empty())
226         {
227             continue;
228         }
229         unsigned vbo_size = (unsigned)(m_particles_generated[p.first].size());
230         if (m_gl_particles.find(p.first) == m_gl_particles.end())
231         {
232             m_gl_particles[p.first] = std::unique_ptr<GLParticle>
233                 (new GLParticle(isFlipsMaterial(p.first)));
234         }
235         glBindBuffer(GL_ARRAY_BUFFER, m_gl_particles.at(p.first)->m_vbo);
236 
237         // Check "real" particle buffer size in opengl
238         if (m_gl_particles.at(p.first)->m_size < vbo_size)
239         {
240             m_gl_particles.at(p.first)->m_size = vbo_size * 2;
241             m_particles_generated.at(p.first).reserve(vbo_size * 2);
242             glBufferData(GL_ARRAY_BUFFER, vbo_size * 2 * 20,
243                 m_particles_generated.at(p.first).data(), GL_DYNAMIC_DRAW);
244             glBindBuffer(GL_ARRAY_BUFFER, 0);
245             continue;
246         }
247         void* ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, vbo_size * 20,
248             GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT |
249             GL_MAP_INVALIDATE_BUFFER_BIT);
250         memcpy(ptr, m_particles_generated[p.first].data(), vbo_size * 20);
251         glUnmapBuffer(GL_ARRAY_BUFFER);
252         glBindBuffer(GL_ARRAY_BUFFER, 0);
253     }
254 }   // uploadAll
255 
256 // ----------------------------------------------------------------------------
drawAll()257 void CPUParticleManager::drawAll()
258 {
259     using namespace SP;
260     std::vector<std::pair<Material*, std::string> > particle_drawn;
261     for (auto& p : m_particles_generated)
262     {
263         if (!p.second.empty())
264         {
265             particle_drawn.emplace_back(m_material_map.at(p.first), p.first);
266         }
267     }
268     std::sort(particle_drawn.begin(), particle_drawn.end(),
269         [](const std::pair<Material*, std::string>& a,
270            const std::pair<Material*, std::string>& b)->bool
271         {
272             return a.first->getShaderName() > b.first->getShaderName();
273         });
274 
275     std::string shader_name;
276     for (auto& p : particle_drawn)
277     {
278         const bool flips = isFlipsMaterial(p.second);
279         const float billboard = p.second.substr(0, 4) == "_bb_" ? 1.0f : 0.0f;
280         Material* cur_mat = p.first;
281         if (cur_mat->getShaderName() != shader_name)
282         {
283             shader_name = cur_mat->getShaderName();
284             if (cur_mat->getShaderName() == "additive")
285             {
286                 ParticleRenderer::getInstance()->use();
287                 glEnable(GL_DEPTH_TEST);
288                 glDepthFunc(GL_LEQUAL);
289                 glDepthMask(GL_FALSE);
290                 glDisable(GL_CULL_FACE);
291                 glEnable(GL_BLEND);
292                 glBlendEquation(GL_FUNC_ADD);
293                 glBlendFunc(GL_ONE, GL_ONE);
294             }
295             else if (cur_mat->getShaderName() == "alphablend")
296             {
297                 ParticleRenderer::getInstance()->use();
298                 glEnable(GL_DEPTH_TEST);
299                 glDepthFunc(GL_LEQUAL);
300                 glDepthMask(GL_FALSE);
301                 glDisable(GL_CULL_FACE);
302                 glEnable(GL_BLEND);
303                 glBlendEquation(GL_FUNC_ADD);
304                 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
305             }
306             else
307             {
308                 AlphaTestParticleRenderer::getInstance()->use();
309                 glEnable(GL_DEPTH_TEST);
310                 glDepthFunc(GL_LEQUAL);
311                 glDepthMask(GL_TRUE);
312                 glDisable(GL_CULL_FACE);
313                 glDisable(GL_BLEND);
314             }
315         }
316 
317         if (cur_mat->getShaderName() == "additive" ||
318             cur_mat->getShaderName() == "alphablend")
319         {
320             ParticleRenderer::getInstance()->setTextureUnits
321                 (cur_mat->getTexture()->getOpenGLTextureName(),
322                 CVS->isDeferredEnabled() ?
323                 irr_driver->getDepthStencilTexture() : 0);
324             ParticleRenderer::getInstance()->setUniforms(flips, billboard);
325         }
326         else
327         {
328             AlphaTestParticleRenderer::getInstance()->setTextureUnits
329                 (cur_mat->getTexture()->getOpenGLTextureName());
330             AlphaTestParticleRenderer::getInstance()->setUniforms(flips);
331         }
332         glBindVertexArray(m_gl_particles.at(p.second)->m_vao);
333         glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4,
334             (unsigned)m_particles_generated.at(p.second).size());
335     }
336 
337 }   // drawAll
338 
339 #endif
340