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