Batched rendering of particles (#14489)

Co-authored-by: x2048 <codeforsmile@gmail.com>
Co-authored-by: Desour <ds.desour@proton.me>
This commit is contained in:
sfan5 2024-04-03 13:56:49 +02:00 committed by GitHub
parent ff88ed7c75
commit f8bff346f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 395 additions and 213 deletions

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "particles.h" #include "particles.h"
#include <cmath> #include <cmath>
#include <array>
#include "client.h" #include "client.h"
#include "collision.h" #include "collision.h"
#include "client/content_cao.h" #include "client/content_cao.h"
@ -26,21 +27,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "light.h" #include "light.h"
#include "localplayer.h"
#include "environment.h" #include "environment.h"
#include "clientmap.h" #include "clientmap.h"
#include "mapnode.h" #include "mapnode.h"
#include "nodedef.h" #include "nodedef.h"
#include "client.h" #include "client.h"
#include "settings.h" #include "settings.h"
#include "profiler.h"
ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc)
{
tex = p;
// note: getTextureForMesh not needed here because we don't use texture filtering
ref = tsrc->getTexture(p.string);
}
/* /*
Particle Particle
*/ */
Particle::Particle( Particle::Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p, const ParticleParameters &p,
const ClientParticleTexRef &texture, const ClientParticleTexRef &texture,
v2f texpos, v2f texpos,
@ -49,14 +56,10 @@ Particle::Particle(
ParticleSpawner *parent, ParticleSpawner *parent,
std::unique_ptr<ClientParticleTexture> owned_texture std::unique_ptr<ClientParticleTexture> owned_texture
) : ) :
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager()),
m_expiration(p.expirationtime), m_expiration(p.expirationtime),
m_env(env), m_base_color(color),
m_gamedef(gamedef),
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
m_texture(texture), m_texture(texture),
m_texpos(texpos), m_texpos(texpos),
m_texsize(texsize), m_texsize(texsize),
@ -64,102 +67,30 @@ Particle::Particle(
m_velocity(p.vel), m_velocity(p.vel),
m_acceleration(p.acc), m_acceleration(p.acc),
m_p(p), m_p(p),
m_player(player),
m_base_color(color),
m_color(color),
m_parent(parent), m_parent(parent),
m_owned_texture(std::move(owned_texture)) m_owned_texture(std::move(owned_texture))
{ {
// Set material }
{
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
const auto blendmode = texture.tex != nullptr
? texture.tex->blendmode
: ParticleParamTypes::BlendMode::alpha;
switch (blendmode) { Particle::~Particle()
case ParticleParamTypes::BlendMode::add: {
bfsrc = video::EBF_SRC_ALPHA; if (m_buffer)
bfdst = video::EBF_DST_ALPHA; m_buffer->release(m_index);
blendop = video::EBO_ADD; }
break;
case ParticleParamTypes::BlendMode::sub: bool Particle::attachToBuffer(ParticleBuffer *buffer)
bfsrc = video::EBF_SRC_ALPHA; {
bfdst = video::EBF_DST_ALPHA; auto index_opt = buffer->allocate();
blendop = video::EBO_REVSUBTRACT; if (index_opt.has_value()) {
break; m_index = index_opt.value();
m_buffer = buffer;
case ParticleParamTypes::BlendMode::screen: return true;
bfsrc = video::EBF_ONE;
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
blendop = video::EBO_ADD;
break;
default: // includes ParticleParamTypes::BlendMode::alpha
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
blendop = video::EBO_ADD;
break;
}
// Texture
m_material.Lighting = false;
m_material.BackfaceCulling = false;
m_material.FogEnable = true;
m_material.forEachTexture([] (auto &tex) {
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
tex.MagFilter = video::ETMAGF_NEAREST;
});
// correctly render layered transparent particles -- see #10398
m_material.ZWriteEnable = video::EZW_AUTO;
// enable alpha blending and set blend mode
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
m_material.BlendOperation = blendop;
m_material.setTexture(0, m_texture.ref);
} }
return false;
// Irrlicht stuff
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
updateLight();
// Init model
updateVertices();
} }
void Particle::OnRegisterSceneNode() void Particle::step(float dtime, ClientEnvironment *env)
{
if (IsVisible)
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
ISceneNode::OnRegisterSceneNode();
}
void Particle::render()
{
video::IVideoDriver *driver = SceneManager->getVideoDriver();
driver->setMaterial(m_material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
u16 indices[] = {0,1,2, 2,3,0};
driver->drawVertexPrimitiveList(m_vertices, 4,
indices, 2, video::EVT_STANDARD,
scene::EPT_TRIANGLES, video::EIT_16BIT);
}
void Particle::step(float dtime)
{ {
m_time += dtime; m_time += dtime;
@ -169,10 +100,10 @@ void Particle::step(float dtime)
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime; m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
if (m_p.collisiondetection) { if (m_p.collisiondetection) {
aabb3f box = m_collisionbox; aabb3f box(v3f(-m_p.size / 2.0f), v3f(m_p.size / 2.0f));
v3f p_pos = m_pos * BS; v3f p_pos = m_pos * BS;
v3f p_velocity = m_velocity * BS; v3f p_velocity = m_velocity * BS;
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f, collisionMoveResult r = collisionMoveSimple(env, env->getGameDef(), BS * 0.5f,
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr, box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
m_p.object_collision); m_p.object_collision);
@ -215,7 +146,7 @@ void Particle::step(float dtime)
m_animation_time += dtime; m_animation_time += dtime;
int frame_length_i = 0; int frame_length_i = 0;
m_p.animation.determineParams( m_p.animation.determineParams(
m_material.getTexture(0)->getSize(), m_texture.ref->getSize(),
NULL, &frame_length_i, NULL); NULL, &frame_length_i, NULL);
float frame_length = frame_length_i / 1000.0; float frame_length = frame_length_i / 1000.0;
while (m_animation_time > frame_length) { while (m_animation_time > frame_length) {
@ -225,23 +156,19 @@ void Particle::step(float dtime)
} }
// animate particle alpha in accordance with settings // animate particle alpha in accordance with settings
float alpha = 1.f;
if (m_texture.tex != nullptr) if (m_texture.tex != nullptr)
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f)); alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
else
m_alpha = 1.f;
// Update lighting // Update lighting
updateLight(); auto col = updateLight(env);
col.setAlpha(255 * alpha);
// Update model // Update model
updateVertices(); updateVertices(env, col);
// Update position -- see #10398
v3s16 camera_offset = m_env->getCameraOffset();
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
} }
void Particle::updateLight() video::SColor Particle::updateLight(ClientEnvironment *env)
{ {
u8 light = 0; u8 light = 0;
bool pos_ok; bool pos_ok;
@ -251,32 +178,37 @@ void Particle::updateLight()
floor(m_pos.Y+0.5), floor(m_pos.Y+0.5),
floor(m_pos.Z+0.5) floor(m_pos.Z+0.5)
); );
MapNode n = m_env->getClientMap().getNode(p, &pos_ok); MapNode n = env->getClientMap().getNode(p, &pos_ok);
if (pos_ok) if (pos_ok)
light = n.getLightBlend(m_env->getDayNightRatio(), light = n.getLightBlend(env->getDayNightRatio(),
m_gamedef->ndef()->getLightingFlags(n)); env->getGameDef()->ndef()->getLightingFlags(n));
else else
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0); light = blend_light(env->getDayNightRatio(), LIGHT_SUN, 0);
u8 m_light = decode_light(light + m_p.glow); u8 m_light = decode_light(light + m_p.glow);
m_color.set(m_alpha*255, return video::SColor(255,
m_light * m_base_color.getRed() / 255, m_light * m_base_color.getRed() / 255,
m_light * m_base_color.getGreen() / 255, m_light * m_base_color.getGreen() / 255,
m_light * m_base_color.getBlue() / 255); m_light * m_base_color.getBlue() / 255);
} }
void Particle::updateVertices() void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
{ {
f32 tx0, tx1, ty0, ty1; f32 tx0, tx1, ty0, ty1;
v2f scale; v2f scale;
if (!m_buffer)
return;
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
if (m_texture.tex != nullptr) if (m_texture.tex != nullptr)
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1)); scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
else else
scale = v2f(1.f, 1.f); scale = v2f(1.f, 1.f);
if (m_p.animation.type != TAT_NONE) { if (m_p.animation.type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture(0)->getSize(); const v2u32 texsize = m_texture.ref->getSize();
v2f texcoord, framesize_f; v2f texcoord, framesize_f;
v2u32 framesize; v2u32 framesize;
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame); texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
@ -297,31 +229,30 @@ void Particle::updateVertices()
auto half = m_p.size * .5f, auto half = m_p.size * .5f,
hx = half * scale.X, hx = half * scale.X,
hy = half * scale.Y; hy = half * scale.Y;
m_vertices[0] = video::S3DVertex(-hx, -hy, vertices[0] = video::S3DVertex(-hx, -hy,
0, 0, 0, 0, m_color, tx0, ty1); 0, 0, 0, 0, color, tx0, ty1);
m_vertices[1] = video::S3DVertex(hx, -hy, vertices[1] = video::S3DVertex(hx, -hy,
0, 0, 0, 0, m_color, tx1, ty1); 0, 0, 0, 0, color, tx1, ty1);
m_vertices[2] = video::S3DVertex(hx, hy, vertices[2] = video::S3DVertex(hx, hy,
0, 0, 0, 0, m_color, tx1, ty0); 0, 0, 0, 0, color, tx1, ty0);
m_vertices[3] = video::S3DVertex(-hx, hy, vertices[3] = video::S3DVertex(-hx, hy,
0, 0, 0, 0, m_color, tx0, ty0); 0, 0, 0, 0, color, tx0, ty0);
// Update position -- see #10398
auto *player = env->getLocalPlayer();
v3s16 camera_offset = env->getCameraOffset();
// see #10398 for (u16 i = 0; i < 4; i++) {
// v3s16 camera_offset = m_env->getCameraOffset(); video::S3DVertex &vertex = vertices[i];
// particle position is now handled by step()
m_box.reset(v3f());
for (video::S3DVertex &vertex : m_vertices) {
if (m_p.vertical) { if (m_p.vertical) {
v3f ppos = m_player->getPosition()/BS; v3f ppos = player->getPosition() / BS;
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) / vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
core::DEGTORAD + 90); core::DEGTORAD + 90);
} else { } else {
vertex.Pos.rotateYZBy(m_player->getPitch()); vertex.Pos.rotateYZBy(player->getPitch());
vertex.Pos.rotateXZBy(m_player->getYaw()); vertex.Pos.rotateXZBy(player->getYaw());
} }
m_box.addInternalPoint(vertex.Pos); vertex.Pos += m_pos * BS - intToFloat(camera_offset, BS);
} }
} }
@ -330,7 +261,6 @@ void Particle::updateVertices()
*/ */
ParticleSpawner::ParticleSpawner( ParticleSpawner::ParticleSpawner(
IGameDef *gamedef,
LocalPlayer *player, LocalPlayer *player,
const ParticleSpawnerParameters &params, const ParticleSpawnerParameters &params,
u16 attached_id, u16 attached_id,
@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner(
m_active(0), m_active(0),
m_particlemanager(p_manager), m_particlemanager(p_manager),
m_time(0.0f), m_time(0.0f),
m_gamedef(gamedef),
m_player(player), m_player(player),
p(params), p(params),
m_texpool(std::move(texpool)), m_texpool(std::move(texpool)),
@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
++m_active; ++m_active;
m_particlemanager->addParticle(std::make_unique<Particle>( m_particlemanager->addParticle(std::make_unique<Particle>(
m_gamedef,
m_player,
env,
pp, pp,
texture, texture,
texpos, texpos,
@ -624,6 +550,109 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
} }
} }
/*
ParticleBuffer
*/
ParticleBuffer::ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material)
: scene::ISceneNode(
env->getGameDef()->getSceneManager()->getRootSceneNode(),
env->getGameDef()->getSceneManager()),
m_mesh_buffer(make_irr<scene::SMeshBuffer>())
{
m_mesh_buffer->getMaterial() = material;
}
static constexpr u16 quad_indices[] = { 0, 1, 2, 2, 3, 0 };
std::optional<u16> ParticleBuffer::allocate()
{
u16 index;
m_usage_timer = 0;
if (!m_free_list.empty()) {
index = m_free_list.back();
m_free_list.pop_back();
auto *vertices = static_cast<video::S3DVertex*>(m_mesh_buffer->getVertices());
u16 *indices = m_mesh_buffer->getIndices();
// reset vertices, because it is only written in Particle::step()
for (u16 i = 0; i < 4; i++)
vertices[4 * index + i] = video::S3DVertex();
for (u16 i = 0; i < 6; i++)
indices[6 * index + i] = 4 * index + quad_indices[i];
return index;
}
if (m_count >= MAX_PARTICLES_PER_BUFFER)
return std::nullopt;
// append new vertices
// note: Our buffer never gets smaller, but ParticleManager will delete
// us after a while.
std::array<video::S3DVertex, 4> vertices {};
m_mesh_buffer->append(&vertices.front(), 4, quad_indices, 6);
index = m_count++;
return index;
}
void ParticleBuffer::release(u16 index)
{
assert(index < m_count);
u16 *indices = m_mesh_buffer->getIndices();
for (u16 i = 0; i < 6; i++)
indices[6 * index + i] = 0;
m_free_list.push_back(index);
}
video::S3DVertex *ParticleBuffer::getVertices(u16 index)
{
if (index >= m_count)
return nullptr;
m_bounding_box_dirty = true;
return &(static_cast<video::S3DVertex *>(m_mesh_buffer->getVertices())[4 * index]);
}
void ParticleBuffer::OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
scene::ISceneNode::OnRegisterSceneNode();
}
const core::aabbox3df &ParticleBuffer::getBoundingBox() const
{
if (!m_bounding_box_dirty)
return m_mesh_buffer->BoundingBox;
core::aabbox3df box;
for (u16 i = 0; i < m_count; i++) {
// check if this index is used
static_assert(quad_indices[1] != 0);
if (m_mesh_buffer->getIndices()[6 * i + 1] == 0)
continue;
for (u16 j = 0; j < 4; j++)
box.addInternalPoint(m_mesh_buffer->getPosition(i * 4 + j));
}
m_mesh_buffer->BoundingBox = box;
m_bounding_box_dirty = false;
return m_mesh_buffer->BoundingBox;
}
void ParticleBuffer::render()
{
video::IVideoDriver *driver = SceneManager->getVideoDriver();
if (isEmpty())
return;
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(m_mesh_buffer->getMaterial());
driver->drawMeshBuffer(m_mesh_buffer.get());
}
/* /*
ParticleManager ParticleManager
*/ */
@ -639,8 +668,9 @@ ParticleManager::~ParticleManager()
void ParticleManager::step(float dtime) void ParticleManager::step(float dtime)
{ {
stepParticles (dtime); stepParticles(dtime);
stepSpawners (dtime); stepSpawners(dtime);
stepBuffers(dtime);
} }
void ParticleManager::stepSpawners(float dtime) void ParticleManager::stepSpawners(float dtime)
@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime)
assert(parent->hasActive()); assert(parent->hasActive());
parent->decrActive(); parent->decrActive();
} }
// remove scene node
p.remove();
// delete // delete
m_particles[i] = std::move(m_particles.back()); m_particles[i] = std::move(m_particles.back());
m_particles.pop_back(); m_particles.pop_back();
} else { } else {
p.step(dtime); p.step(dtime, m_env);
++i; ++i;
} }
} }
} }
void ParticleManager::stepBuffers(float dtime)
{
constexpr float INTERVAL = 0.5f;
if (!m_buffer_gc.step(dtime, INTERVAL))
return;
MutexAutoLock lock(m_particle_list_lock);
// remove buffers that have been unused for 5 seconds
size_t alloc = 0;
for (size_t i = 0; i < m_particle_buffers.size(); ) {
auto &buf = m_particle_buffers[i];
buf->m_usage_timer += INTERVAL;
if (buf->isEmpty() && buf->m_usage_timer > 5.0f) {
// delete and swap with last
buf->remove();
buf = std::move(m_particle_buffers.back());
m_particle_buffers.pop_back();
} else {
i++;
alloc += buf->m_count;
}
}
g_profiler->avg("ParticleManager: particle buffer count [#]", m_particle_buffers.size());
if (!m_particle_buffers.empty())
g_profiler->avg("ParticleManager: buffer allocated size [#]", alloc);
}
void ParticleManager::clearAll() void ParticleManager::clearAll()
{ {
MutexAutoLock lock(m_spawner_list_lock); MutexAutoLock lock(m_spawner_list_lock);
MutexAutoLock lock2(m_particle_list_lock); MutexAutoLock lock2(m_particle_list_lock);
// clear particle spawners
m_particle_spawners.clear(); m_particle_spawners.clear();
m_dying_particle_spawners.clear(); m_dying_particle_spawners.clear();
// clear particles
for (std::unique_ptr<Particle> &p : m_particles) {
// remove scene node
p->remove();
// delete
p.reset();
}
m_particles.clear(); m_particles.clear();
// have to remove from scene first because it keeps a reference
for (auto &it : m_particle_buffers)
it->remove();
m_particle_buffers.clear();
} }
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
addParticleSpawner(event->add_particlespawner.id, addParticleSpawner(event->add_particlespawner.id,
std::make_unique<ParticleSpawner>( std::make_unique<ParticleSpawner>(
client,
player, player,
p, p,
event->add_particlespawner.attached_id, event->add_particlespawner.attached_id,
@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
p.size = oldsize; p.size = oldsize;
if (texture.ref) { if (texture.ref) {
addParticle(std::make_unique<Particle>(client, player, m_env, addParticle(std::make_unique<Particle>(
p, texture, texpos, texsize, color, nullptr, p, texture, texpos, texsize, color, nullptr,
std::move(texstore))); std::move(texstore)));
} }
@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
); );
addParticle(std::make_unique<Particle>( addParticle(std::make_unique<Particle>(
gamedef,
player,
m_env,
p, p,
ClientParticleTexRef(ref), ClientParticleTexRef(ref),
texpos, texpos,
@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate)
m_particles.reserve(m_particles.size() + max_estimate); m_particles.reserve(m_particles.size() + max_estimate);
} }
void ParticleManager::addParticle(std::unique_ptr<Particle> toadd) video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture)
{
// translate blend modes to GL blend functions
video::E_BLEND_FACTOR bfsrc, bfdst;
video::E_BLEND_OPERATION blendop;
const auto blendmode = texture.tex ? texture.tex->blendmode :
ParticleParamTypes::BlendMode::alpha;
switch (blendmode) {
case ParticleParamTypes::BlendMode::add:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_ADD;
break;
case ParticleParamTypes::BlendMode::sub:
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_DST_ALPHA;
blendop = video::EBO_REVSUBTRACT;
break;
case ParticleParamTypes::BlendMode::screen:
bfsrc = video::EBF_ONE;
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
blendop = video::EBO_ADD;
break;
default: // includes ParticleParamTypes::BlendMode::alpha
bfsrc = video::EBF_SRC_ALPHA;
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
blendop = video::EBO_ADD;
break;
}
video::SMaterial material;
// Texture
material.Lighting = false;
material.BackfaceCulling = false;
material.FogEnable = true;
material.forEachTexture([] (auto &tex) {
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
tex.MagFilter = video::ETMAGF_NEAREST;
});
// We don't have working transparency sorting. Disable Z-Write for
// correct results for clipped-alpha at least.
material.ZWriteEnable = video::EZW_OFF;
// enable alpha blending and set blend mode
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
material.MaterialTypeParam = video::pack_textureBlendFunc(
bfsrc, bfdst,
video::EMFN_MODULATE_1X,
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
material.BlendOperation = blendop;
assert(texture.ref);
material.setTexture(0, texture.ref);
return material;
}
bool ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
{ {
MutexAutoLock lock(m_particle_list_lock); MutexAutoLock lock(m_particle_list_lock);
m_particles.push_back(std::move(toadd)); auto material = getMaterialForParticle(toadd->getTextureRef());
}
ParticleBuffer *found = nullptr;
// simple shortcut when multiple particles of the same type get added
if (!m_particles.empty()) {
auto &last = m_particles.back();
if (last->getBuffer() && last->getBuffer()->getMaterial(0) == material)
found = last->getBuffer();
}
// search fitting buffer
if (!found) {
for (auto &buffer : m_particle_buffers) {
if (buffer->getMaterial(0) == material) {
found = buffer.get();
break;
}
}
}
// or create a new one
if (!found) {
auto tmp = make_irr<ParticleBuffer>(m_env, material);
found = tmp.get();
m_particle_buffers.push_back(std::move(tmp));
}
if (!toadd->attachToBuffer(found)) {
infostream << "ParticleManager: buffer full, dropping particle" << std::endl;
return false;
}
m_particles.push_back(std::move(toadd));
return true;
}
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd) void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
{ {

@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include <iostream> #include <vector>
#include <unordered_map>
#include "irrlichttypes_extrabloated.h" #include "irrlichttypes_extrabloated.h"
#include "localplayer.h" #include "irr_ptr.h"
#include "../particles.h" #include "../particles.h"
struct ClientEvent; struct ClientEvent;
@ -29,6 +30,10 @@ class ParticleManager;
class ClientEnvironment; class ClientEnvironment;
struct MapNode; struct MapNode;
struct ContentFeatures; struct ContentFeatures;
class LocalPlayer;
class ITextureSource;
class IGameDef;
class Client;
struct ClientParticleTexture struct ClientParticleTexture
{ {
@ -38,9 +43,7 @@ struct ClientParticleTexture
video::ITexture *ref = nullptr; video::ITexture *ref = nullptr;
ClientParticleTexture() = default; ClientParticleTexture() = default;
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t): ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc);
tex(p),
ref(t->getTextureForMesh(p.string)) {};
}; };
struct ClientParticleTexRef struct ClientParticleTexRef
@ -61,14 +64,12 @@ struct ClientParticleTexRef
}; };
class ParticleSpawner; class ParticleSpawner;
class ParticleBuffer;
class Particle : public scene::ISceneNode class Particle
{ {
public: public:
Particle( Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p, const ParticleParameters &p,
const ClientParticleTexRef &texture, const ClientParticleTexRef &texture,
v2f texpos, v2f texpos,
@ -78,61 +79,46 @@ public:
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
); );
virtual const aabb3f &getBoundingBox() const ~Particle();
{
return m_box;
}
virtual u32 getMaterialCount() const DISABLE_CLASS_COPY(Particle)
{
return 1;
}
virtual video::SMaterial& getMaterial(u32 i) void step(float dtime, ClientEnvironment *env);
{
return m_material;
}
virtual void OnRegisterSceneNode(); bool isExpired () const
virtual void render();
void step(float dtime);
bool isExpired ()
{ return m_expiration < m_time; } { return m_expiration < m_time; }
ParticleSpawner *getParent() { return m_parent; } ParticleSpawner *getParent() const { return m_parent; }
const ClientParticleTexRef &getTextureRef() const { return m_texture; }
ParticleBuffer *getBuffer() const { return m_buffer; }
bool attachToBuffer(ParticleBuffer *buffer);
private: private:
void updateLight(); video::SColor updateLight(ClientEnvironment *env);
void updateVertices(); void updateVertices(ClientEnvironment *env, video::SColor color);
void setVertexAlpha(float a);
ParticleBuffer *m_buffer = nullptr;
u16 m_index; // index in m_buffer
video::S3DVertex m_vertices[4];
float m_time = 0.0f; float m_time = 0.0f;
float m_expiration; float m_expiration;
ClientEnvironment *m_env; // Color without lighting
IGameDef *m_gamedef; video::SColor m_base_color;
aabb3f m_box;
aabb3f m_collisionbox;
ClientParticleTexRef m_texture; ClientParticleTexRef m_texture;
video::SMaterial m_material;
v2f m_texpos; v2f m_texpos;
v2f m_texsize; v2f m_texsize;
v3f m_pos; v3f m_pos;
v3f m_velocity; v3f m_velocity;
v3f m_acceleration; v3f m_acceleration;
const ParticleParameters m_p;
LocalPlayer *m_player;
//! Color without lighting const ParticleParameters m_p;
video::SColor m_base_color;
//! Final rendered color
video::SColor m_color;
float m_animation_time = 0.0f; float m_animation_time = 0.0f;
int m_animation_frame = 0; int m_animation_frame = 0;
float m_alpha = 0.0f;
ParticleSpawner *m_parent = nullptr; ParticleSpawner *m_parent = nullptr;
// Used if not spawned from a particlespawner // Used if not spawned from a particlespawner
@ -142,8 +128,7 @@ private:
class ParticleSpawner class ParticleSpawner
{ {
public: public:
ParticleSpawner(IGameDef *gamedef, ParticleSpawner(LocalPlayer *player,
LocalPlayer *player,
const ParticleSpawnerParameters &params, const ParticleSpawnerParameters &params,
u16 attached_id, u16 attached_id,
std::vector<ClientParticleTexture> &&texpool, std::vector<ClientParticleTexture> &&texpool,
@ -164,7 +149,6 @@ private:
size_t m_active; size_t m_active;
ParticleManager *m_particlemanager; ParticleManager *m_particlemanager;
float m_time; float m_time;
IGameDef *m_gamedef;
LocalPlayer *m_player; LocalPlayer *m_player;
ParticleSpawnerParameters p; ParticleSpawnerParameters p;
std::vector<ClientParticleTexture> m_texpool; std::vector<ClientParticleTexture> m_texpool;
@ -172,12 +156,61 @@ private:
u16 m_attached_id; u16 m_attached_id;
}; };
class ParticleBuffer : public scene::ISceneNode
{
friend class ParticleManager;
public:
ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material);
// for pointer stability
DISABLE_CLASS_COPY(ParticleBuffer)
/// Reserves one more slot for a particle (4 vertices, 6 indices)
/// @return particle index within buffer
std::optional<u16> allocate();
/// Frees the particle at `index`
void release(u16 index);
/// @return video::S3DVertex[4]
video::S3DVertex *getVertices(u16 index);
inline bool isEmpty() const {
return m_free_list.size() == m_count;
}
virtual video::SMaterial &getMaterial(u32 num) override {
return m_mesh_buffer->getMaterial();
}
virtual u32 getMaterialCount() const override {
return 1;
}
virtual const core::aabbox3df &getBoundingBox() const override;
virtual void render() override;
virtual void OnRegisterSceneNode() override;
// we have 16-bit indices
static constexpr u16 MAX_PARTICLES_PER_BUFFER = 16000;
private:
irr_ptr<scene::SMeshBuffer> m_mesh_buffer;
// unused (e.g. expired) particle indices for re-use
std::vector<u16> m_free_list;
// for automatic deletion when unused for a while. is reset on allocate().
float m_usage_timer = 0;
// total count of contained particles
u16 m_count = 0;
mutable bool m_bounding_box_dirty = true;
};
/** /**
* Class doing particle as well as their spawners handling * Class doing particle as well as their spawners handling
*/ */
class ParticleManager class ParticleManager
{ {
friend class ParticleSpawner; friend class ParticleSpawner;
public: public:
ParticleManager(ClientEnvironment* env); ParticleManager(ClientEnvironment* env);
DISABLE_CLASS_COPY(ParticleManager) DISABLE_CLASS_COPY(ParticleManager)
@ -213,7 +246,9 @@ protected:
ParticleParameters &p, video::ITexture **texture, v2f &texpos, ParticleParameters &p, video::ITexture **texture, v2f &texpos,
v2f &texsize, video::SColor *color, u8 tilenum = 0); v2f &texsize, video::SColor *color, u8 tilenum = 0);
void addParticle(std::unique_ptr<Particle> toadd); static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture);
bool addParticle(std::unique_ptr<Particle> toadd);
private: private:
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd); void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
@ -221,17 +256,23 @@ private:
void stepParticles(float dtime); void stepParticles(float dtime);
void stepSpawners(float dtime); void stepSpawners(float dtime);
void stepBuffers(float dtime);
void clearAll(); void clearAll();
std::vector<std::unique_ptr<Particle>> m_particles; std::vector<std::unique_ptr<Particle>> m_particles;
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners; std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners; std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
// Start the particle spawner ids generated from here after u32_max. lower values are std::vector<irr_ptr<ParticleBuffer>> m_particle_buffers;
// for server sent spawners.
u64 m_next_particle_spawner_id = U32_MAX + 1; // Start the particle spawner ids generated from here after u32_max.
// lower values are for server sent spawners.
u64 m_next_particle_spawner_id = static_cast<u64>(U32_MAX) + 1;
ClientEnvironment *m_env; ClientEnvironment *m_env;
IntervalLimiter m_buffer_gc;
std::mutex m_particle_list_lock; std::mutex m_particle_list_lock;
std::mutex m_spawner_list_lock; std::mutex m_spawner_list_lock;
}; };

@ -31,7 +31,7 @@ enum TileAnimationType : u8
struct TileAnimationParams struct TileAnimationParams
{ {
enum TileAnimationType type; enum TileAnimationType type = TileAnimationType::TAT_NONE;
union union
{ {
// struct { // struct {