From f8bff346f41005098f54b0ba4835a72ebb84f536 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 3 Apr 2024 13:56:49 +0200 Subject: [PATCH] Batched rendering of particles (#14489) Co-authored-by: x2048 Co-authored-by: Desour --- src/client/particles.cpp | 461 +++++++++++++++++++++++++-------------- src/client/particles.h | 145 +++++++----- src/tileanimation.h | 2 +- 3 files changed, 395 insertions(+), 213 deletions(-) diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 14384f3b8..638505846 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "particles.h" #include +#include #include "client.h" #include "collision.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 "util/numeric.h" #include "light.h" +#include "localplayer.h" #include "environment.h" #include "clientmap.h" #include "mapnode.h" #include "nodedef.h" #include "client.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( - IGameDef *gamedef, - LocalPlayer *player, - ClientEnvironment *env, const ParticleParameters &p, const ClientParticleTexRef &texture, v2f texpos, @@ -49,14 +56,10 @@ Particle::Particle( ParticleSpawner *parent, std::unique_ptr owned_texture ) : - scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(), - ((Client *)gamedef)->getSceneManager()), - m_expiration(p.expirationtime), - m_env(env), - m_gamedef(gamedef), - m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))), + m_base_color(color), + m_texture(texture), m_texpos(texpos), m_texsize(texsize), @@ -64,102 +67,30 @@ Particle::Particle( m_velocity(p.vel), m_acceleration(p.acc), m_p(p), - m_player(player), - - m_base_color(color), - m_color(color), m_parent(parent), 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) { - case ParticleParamTypes::BlendMode::add: - bfsrc = video::EBF_SRC_ALPHA; - bfdst = video::EBF_DST_ALPHA; - blendop = video::EBO_ADD; - break; +Particle::~Particle() +{ + if (m_buffer) + m_buffer->release(m_index); +} - 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; - } - - // 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); +bool Particle::attachToBuffer(ParticleBuffer *buffer) +{ + auto index_opt = buffer->allocate(); + if (index_opt.has_value()) { + m_index = index_opt.value(); + m_buffer = buffer; + return true; } - - // Irrlicht stuff - this->setAutomaticCulling(scene::EAC_OFF); - - // Init lighting - updateLight(); - - // Init model - updateVertices(); + return false; } -void Particle::OnRegisterSceneNode() -{ - 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) +void Particle::step(float dtime, ClientEnvironment *env) { m_time += dtime; @@ -169,10 +100,10 @@ void Particle::step(float dtime) m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime; 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_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, m_p.object_collision); @@ -215,7 +146,7 @@ void Particle::step(float dtime) m_animation_time += dtime; int frame_length_i = 0; m_p.animation.determineParams( - m_material.getTexture(0)->getSize(), + m_texture.ref->getSize(), NULL, &frame_length_i, NULL); float frame_length = frame_length_i / 1000.0; while (m_animation_time > frame_length) { @@ -225,23 +156,19 @@ void Particle::step(float dtime) } // animate particle alpha in accordance with settings + float alpha = 1.f; if (m_texture.tex != nullptr) - m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f)); - else - m_alpha = 1.f; + alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f)); // Update lighting - updateLight(); + auto col = updateLight(env); + col.setAlpha(255 * alpha); // Update model - updateVertices(); - - // Update position -- see #10398 - v3s16 camera_offset = m_env->getCameraOffset(); - setPosition(m_pos*BS - intToFloat(camera_offset, BS)); + updateVertices(env, col); } -void Particle::updateLight() +video::SColor Particle::updateLight(ClientEnvironment *env) { u8 light = 0; bool pos_ok; @@ -251,32 +178,37 @@ void Particle::updateLight() floor(m_pos.Y+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) - light = n.getLightBlend(m_env->getDayNightRatio(), - m_gamedef->ndef()->getLightingFlags(n)); + light = n.getLightBlend(env->getDayNightRatio(), + env->getGameDef()->ndef()->getLightingFlags(n)); 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); - m_color.set(m_alpha*255, + return video::SColor(255, m_light * m_base_color.getRed() / 255, m_light * m_base_color.getGreen() / 255, m_light * m_base_color.getBlue() / 255); } -void Particle::updateVertices() +void Particle::updateVertices(ClientEnvironment *env, video::SColor color) { f32 tx0, tx1, ty0, ty1; v2f scale; + if (!m_buffer) + return; + + video::S3DVertex *vertices = m_buffer->getVertices(m_index); + if (m_texture.tex != nullptr) scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1)); else scale = v2f(1.f, 1.f); 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; v2u32 framesize; texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame); @@ -297,31 +229,30 @@ void Particle::updateVertices() auto half = m_p.size * .5f, hx = half * scale.X, hy = half * scale.Y; - m_vertices[0] = video::S3DVertex(-hx, -hy, - 0, 0, 0, 0, m_color, tx0, ty1); - m_vertices[1] = video::S3DVertex(hx, -hy, - 0, 0, 0, 0, m_color, tx1, ty1); - m_vertices[2] = video::S3DVertex(hx, hy, - 0, 0, 0, 0, m_color, tx1, ty0); - m_vertices[3] = video::S3DVertex(-hx, hy, - 0, 0, 0, 0, m_color, tx0, ty0); + vertices[0] = video::S3DVertex(-hx, -hy, + 0, 0, 0, 0, color, tx0, ty1); + vertices[1] = video::S3DVertex(hx, -hy, + 0, 0, 0, 0, color, tx1, ty1); + vertices[2] = video::S3DVertex(hx, hy, + 0, 0, 0, 0, color, tx1, ty0); + vertices[3] = video::S3DVertex(-hx, hy, + 0, 0, 0, 0, color, tx0, ty0); + // Update position -- see #10398 + auto *player = env->getLocalPlayer(); + v3s16 camera_offset = env->getCameraOffset(); - // see #10398 - // v3s16 camera_offset = m_env->getCameraOffset(); - // particle position is now handled by step() - m_box.reset(v3f()); - - for (video::S3DVertex &vertex : m_vertices) { + for (u16 i = 0; i < 4; i++) { + video::S3DVertex &vertex = vertices[i]; 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) / core::DEGTORAD + 90); } else { - vertex.Pos.rotateYZBy(m_player->getPitch()); - vertex.Pos.rotateXZBy(m_player->getYaw()); + vertex.Pos.rotateYZBy(player->getPitch()); + 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( - IGameDef *gamedef, LocalPlayer *player, const ParticleSpawnerParameters ¶ms, u16 attached_id, @@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner( m_active(0), m_particlemanager(p_manager), m_time(0.0f), - m_gamedef(gamedef), m_player(player), p(params), m_texpool(std::move(texpool)), @@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, ++m_active; m_particlemanager->addParticle(std::make_unique( - m_gamedef, - m_player, - env, pp, texture, 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()) +{ + m_mesh_buffer->getMaterial() = material; +} + +static constexpr u16 quad_indices[] = { 0, 1, 2, 2, 3, 0 }; + +std::optional 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(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 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(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 */ @@ -639,8 +668,9 @@ ParticleManager::~ParticleManager() void ParticleManager::step(float dtime) { - stepParticles (dtime); - stepSpawners (dtime); + stepParticles(dtime); + stepSpawners(dtime); + stepBuffers(dtime); } void ParticleManager::stepSpawners(float dtime) @@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime) assert(parent->hasActive()); parent->decrActive(); } - // remove scene node - p.remove(); // delete m_particles[i] = std::move(m_particles.back()); m_particles.pop_back(); } else { - p.step(dtime); + p.step(dtime, m_env); ++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() { MutexAutoLock lock(m_spawner_list_lock); MutexAutoLock lock2(m_particle_list_lock); - // clear particle spawners m_particle_spawners.clear(); m_dying_particle_spawners.clear(); - // clear particles - for (std::unique_ptr &p : m_particles) { - // remove scene node - p->remove(); - // delete - p.reset(); - } 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, @@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, addParticleSpawner(event->add_particlespawner.id, std::make_unique( - client, player, p, event->add_particlespawner.attached_id, @@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client, p.size = oldsize; if (texture.ref) { - addParticle(std::make_unique(client, player, m_env, + addParticle(std::make_unique( p, texture, texpos, texsize, color, nullptr, std::move(texstore))); } @@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef, ); addParticle(std::make_unique( - gamedef, - player, - m_env, p, ClientParticleTexRef(ref), texpos, @@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate) m_particles.reserve(m_particles.size() + max_estimate); } -void ParticleManager::addParticle(std::unique_ptr 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 toadd) { 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(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 toadd) { diff --git a/src/client/particles.h b/src/client/particles.h index 2c8ceafa4..92ea8fb67 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include +#include +#include #include "irrlichttypes_extrabloated.h" -#include "localplayer.h" +#include "irr_ptr.h" #include "../particles.h" struct ClientEvent; @@ -29,6 +30,10 @@ class ParticleManager; class ClientEnvironment; struct MapNode; struct ContentFeatures; +class LocalPlayer; +class ITextureSource; +class IGameDef; +class Client; struct ClientParticleTexture { @@ -38,9 +43,7 @@ struct ClientParticleTexture video::ITexture *ref = nullptr; ClientParticleTexture() = default; - ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t): - tex(p), - ref(t->getTextureForMesh(p.string)) {}; + ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc); }; struct ClientParticleTexRef @@ -61,14 +64,12 @@ struct ClientParticleTexRef }; class ParticleSpawner; +class ParticleBuffer; -class Particle : public scene::ISceneNode +class Particle { public: Particle( - IGameDef *gamedef, - LocalPlayer *player, - ClientEnvironment *env, const ParticleParameters &p, const ClientParticleTexRef &texture, v2f texpos, @@ -78,61 +79,46 @@ public: std::unique_ptr owned_texture = nullptr ); - virtual const aabb3f &getBoundingBox() const - { - return m_box; - } + ~Particle(); - virtual u32 getMaterialCount() const - { - return 1; - } + DISABLE_CLASS_COPY(Particle) - virtual video::SMaterial& getMaterial(u32 i) - { - return m_material; - } + void step(float dtime, ClientEnvironment *env); - virtual void OnRegisterSceneNode(); - virtual void render(); - - void step(float dtime); - - bool isExpired () + bool isExpired () const { 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: - void updateLight(); - void updateVertices(); - void setVertexAlpha(float a); + video::SColor updateLight(ClientEnvironment *env); + void updateVertices(ClientEnvironment *env, video::SColor color); + + ParticleBuffer *m_buffer = nullptr; + u16 m_index; // index in m_buffer - video::S3DVertex m_vertices[4]; float m_time = 0.0f; float m_expiration; - ClientEnvironment *m_env; - IGameDef *m_gamedef; - aabb3f m_box; - aabb3f m_collisionbox; + // Color without lighting + video::SColor m_base_color; + ClientParticleTexRef m_texture; - video::SMaterial m_material; v2f m_texpos; v2f m_texsize; v3f m_pos; v3f m_velocity; v3f m_acceleration; - const ParticleParameters m_p; - LocalPlayer *m_player; - //! Color without lighting - video::SColor m_base_color; - //! Final rendered color - video::SColor m_color; + const ParticleParameters m_p; + float m_animation_time = 0.0f; int m_animation_frame = 0; - float m_alpha = 0.0f; ParticleSpawner *m_parent = nullptr; // Used if not spawned from a particlespawner @@ -142,8 +128,7 @@ private: class ParticleSpawner { public: - ParticleSpawner(IGameDef *gamedef, - LocalPlayer *player, + ParticleSpawner(LocalPlayer *player, const ParticleSpawnerParameters ¶ms, u16 attached_id, std::vector &&texpool, @@ -164,7 +149,6 @@ private: size_t m_active; ParticleManager *m_particlemanager; float m_time; - IGameDef *m_gamedef; LocalPlayer *m_player; ParticleSpawnerParameters p; std::vector m_texpool; @@ -172,12 +156,61 @@ private: 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 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 m_mesh_buffer; + // unused (e.g. expired) particle indices for re-use + std::vector 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 ParticleManager { -friend class ParticleSpawner; + friend class ParticleSpawner; public: ParticleManager(ClientEnvironment* env); DISABLE_CLASS_COPY(ParticleManager) @@ -213,7 +246,9 @@ protected: ParticleParameters &p, video::ITexture **texture, v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum = 0); - void addParticle(std::unique_ptr toadd); + static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture); + + bool addParticle(std::unique_ptr toadd); private: void addParticleSpawner(u64 id, std::unique_ptr toadd); @@ -221,17 +256,23 @@ private: void stepParticles(float dtime); void stepSpawners(float dtime); + void stepBuffers(float dtime); void clearAll(); std::vector> m_particles; std::unordered_map> m_particle_spawners; std::vector> m_dying_particle_spawners; - // Start the particle spawner ids generated from here after u32_max. lower values are - // for server sent spawners. - u64 m_next_particle_spawner_id = U32_MAX + 1; + std::vector> m_particle_buffers; + + // 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(U32_MAX) + 1; ClientEnvironment *m_env; + + IntervalLimiter m_buffer_gc; + std::mutex m_particle_list_lock; std::mutex m_spawner_list_lock; }; diff --git a/src/tileanimation.h b/src/tileanimation.h index e7cf1a088..db258e240 100644 --- a/src/tileanimation.h +++ b/src/tileanimation.h @@ -31,7 +31,7 @@ enum TileAnimationType : u8 struct TileAnimationParams { - enum TileAnimationType type; + enum TileAnimationType type = TileAnimationType::TAT_NONE; union { // struct {