From 981f1b6c77d5055717a1c8ea046197b582f33718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Mon, 18 Sep 2023 18:42:48 -0300 Subject: [PATCH] Remove linked list from particles --- src/g_cvars.cpp | 8 +- src/g_levellocals.h | 9 +- src/gamedata/r_defs.h | 2 + src/playsim/p_effect.cpp | 278 +++++++++--------- src/playsim/p_effect.h | 8 +- src/rendering/hwrenderer/scene/hw_bsp.cpp | 9 +- .../swrenderer/scene/r_opaque_pass.cpp | 4 +- 7 files changed, 155 insertions(+), 163 deletions(-) diff --git a/src/g_cvars.cpp b/src/g_cvars.cpp index 14c83256853..efb0ab3f06b 100644 --- a/src/g_cvars.cpp +++ b/src/g_cvars.cpp @@ -126,10 +126,10 @@ CUSTOM_CVAR(Int, r_maxparticles, 4000, CVAR_ARCHIVE | CVAR_NOINITCALL) { if (self == 0) self = 4000; - else if (self > 65535) - self = 65535; - else if (self < 100) - self = 100; + else if (self > ABSOLUTE_MAX_PARTICLES) + self = ABSOLUTE_MAX_PARTICLES; + else if (self < ABSOLUTE_MIN_PARTICLES) + self = ABSOLUTE_MIN_PARTICLES; if (gamestate != GS_STARTUP) { diff --git a/src/g_levellocals.h b/src/g_levellocals.h index b50b19fe40e..727d6a4904d 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -35,6 +35,8 @@ #pragma once +#include + #include "doomdata.h" #include "g_level.h" #include "r_defs.h" @@ -654,11 +656,10 @@ struct FLevelLocals DSeqNode *SequenceListHead; // [RH] particle globals - uint32_t OldestParticle; // [MC] Oldest particle for replacing with PS_REPLACE - uint32_t ActiveParticles; - uint32_t InactiveParticles; + TArray Particles; - TArray ParticlesInSubsec; + std::deque ReplaceableParticles; + FThinkerCollection Thinkers; TArray Scrolls; // NULL if no DScrollers in this level diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index c6284b2f1d2..721afb5b5c8 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -1644,6 +1644,8 @@ struct subsector_t FPortalCoverage portalcoverage[2]; LightmapSurface *lightmap[2]; + + TArray particles; }; diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index a7588d1ba6e..83e75c793a0 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -99,104 +99,66 @@ static const struct ColorList { {NULL, 0, 0, 0 } }; +static_assert(std::is_trivially_copyable_v); + +static int ParticleCount; + +ADD_STAT(particles) +{ + FString str; + str.Format("Particle Count: %d", ParticleCount); + return str; +} + +static int NumParticles; + inline particle_t *NewParticle (FLevelLocals *Level, bool replace = false) { - particle_t *result = nullptr; - // [MC] Thanks to RaveYard and randi for helping me with this addition. - // Array's filled up - if (Level->InactiveParticles == NO_PARTICLE) + int num_parts = Level->Particles.Size() + (int)Level->ReplaceableParticles.size(); + if(replace) { - if (replace) + if(num_parts == NumParticles) { - result = &Level->Particles[Level->OldestParticle]; - - // There should be NO_PARTICLE for the oldest's tnext - if (result->tprev != NO_PARTICLE) - { - // tnext: youngest to oldest - // tprev: oldest to youngest - - // 2nd oldest -> oldest - particle_t *nbottom = &Level->Particles[result->tprev]; - nbottom->tnext = NO_PARTICLE; - - // now oldest becomes youngest - Level->OldestParticle = result->tprev; - result->tnext = Level->ActiveParticles; - result->tprev = NO_PARTICLE; - Level->ActiveParticles = uint32_t(result - Level->Particles.Data()); - - // youngest -> 2nd youngest - particle_t* ntop = &Level->Particles[result->tnext]; - ntop->tprev = Level->ActiveParticles; - } - // [MC] Future proof this by resetting everything when replacing a particle. - auto tnext = result->tnext; - auto tprev = result->tprev; - *result = {}; - result->tnext = tnext; - result->tprev = tprev; + if(Level->ReplaceableParticles.size() == 0) return nullptr; + Level->ReplaceableParticles.pop_front(); } - return result; - } - - // Array isn't full. - uint32_t current = Level->ActiveParticles; - result = &Level->Particles[Level->InactiveParticles]; - Level->InactiveParticles = result->tnext; - result->tnext = current; - result->tprev = NO_PARTICLE; - Level->ActiveParticles = uint32_t(result - Level->Particles.Data()); - - if (current != NO_PARTICLE) // More than one active particles - { - particle_t* next = &Level->Particles[current]; - next->tprev = Level->ActiveParticles; + Level->ReplaceableParticles.push_back({}); + return &Level->ReplaceableParticles[Level->ReplaceableParticles.size() - 1]; } - else // Just one active particle + else if(num_parts < NumParticles) { - Level->OldestParticle = Level->ActiveParticles; + Level->Particles.Reserve(1); + return Level->Particles.Data() + (Level->Particles.Size() - 1); } - return result; + return nullptr; } // // [RH] Particle functions // -void P_InitParticles (FLevelLocals *); void P_InitParticles (FLevelLocals *Level) { - const char *i; int num; - if ((i = Args->CheckValue ("-numparticles"))) + if (const char *i; (i = Args->CheckValue ("-numparticles"))) + { num = atoi (i); - // [BC] Use r_maxparticles now. + } else + { + // [BC] Use r_maxparticles now. num = r_maxparticles; + } // This should be good, but eh... - int NumParticles = clamp(num, 100, 65535); - - Level->Particles.Resize(NumParticles); - P_ClearParticles (Level); + NumParticles = clamp(num, ABSOLUTE_MIN_PARTICLES, ABSOLUTE_MAX_PARTICLES); } void P_ClearParticles (FLevelLocals *Level) { - int i = 0; - Level->OldestParticle = NO_PARTICLE; - Level->ActiveParticles = NO_PARTICLE; - Level->InactiveParticles = 0; - for (auto &p : Level->Particles) - { - p = {}; - p.tprev = i - 1; - p.tnext = ++i; - } - Level->Particles.Last().tnext = NO_PARTICLE; - Level->Particles.Data()->tprev = NO_PARTICLE; + Level->Particles.Clear(); + Level->ReplaceableParticles.clear(); } // Group particles by subsectors. Because particles are always @@ -205,24 +167,30 @@ void P_ClearParticles (FLevelLocals *Level) void P_FindParticleSubsectors (FLevelLocals *Level) { - if (Level->ParticlesInSubsec.Size() < Level->subsectors.Size()) + for(uint32_t i = 0; i < Level->subsectors.Size(); i++) { - Level->ParticlesInSubsec.Reserve (Level->subsectors.Size() - Level->ParticlesInSubsec.Size()); + Level->subsectors[i].particles.Clear(); } - fillshort (&Level->ParticlesInSubsec[0], Level->subsectors.Size(), NO_PARTICLE); - if (!r_particles) { return; } - for (uint16_t i = Level->ActiveParticles; i != NO_PARTICLE; i = Level->Particles[i].tnext) + int n = Level->Particles.Size(); + for (int i = 0; i < n; i++) + { + particle_t * part = Level->Particles.Data() + i; + // Try to reuse the subsector from the last portal check, if still valid. + if (part->subsector == nullptr) part->subsector = Level->PointInRenderSubsector(part->Pos); + part->subsector->particles.Push(part); + } + n = Level->ReplaceableParticles.size(); + for (int i = 0; i < n; i++) { + particle_t * part = &Level->ReplaceableParticles[i]; // Try to reuse the subsector from the last portal check, if still valid. - if (Level->Particles[i].subsector == nullptr) Level->Particles[i].subsector = Level->PointInRenderSubsector(Level->Particles[i].Pos); - int ssnum = Level->Particles[i].subsector->Index(); - Level->Particles[i].snext = Level->ParticlesInSubsec[ssnum]; - Level->ParticlesInSubsec[ssnum] = i; + if (Level->ReplaceableParticles[i].subsector == nullptr) Level->ReplaceableParticles[i].subsector = Level->PointInRenderSubsector(part->Pos); + part->subsector->particles.Push(part); } } @@ -263,75 +231,88 @@ void P_InitEffects () blood2 = ParticleColor(RPART(kind)/3, GPART(kind)/3, BPART(kind)/3); } -void P_ThinkParticles (FLevelLocals *Level) +// [RL0] true = particle should be deleted +bool P_ThinkParticle(FLevelLocals * Level, particle_t * particle) { - int i = Level->ActiveParticles; - particle_t *particle = nullptr, *prev = nullptr; - while (i != NO_PARTICLE) + auto oldtrans = particle->alpha; + particle->alpha -= particle->fadestep; + particle->size += particle->sizestep; + if (particle->alpha <= 0 || oldtrans < particle->alpha || --particle->ttl <= 0 || (particle->size <= 0)) + { // The particle has expired, so free it + return true; + } + + DVector2 newxy = Level->GetPortalOffsetPosition(particle->Pos.X, particle->Pos.Y, particle->Vel.X, particle->Vel.Y); + particle->Pos.X = newxy.X; + particle->Pos.Y = newxy.Y; + particle->Pos.Z += particle->Vel.Z; + particle->Vel += particle->Acc; + + if(particle->flags & PT_DOROLL) + { + particle->Roll += particle->RollVel; + particle->RollVel += particle->RollAcc; + } + + particle->subsector = Level->PointInRenderSubsector(particle->Pos); + sector_t *s = particle->subsector->sector; + // Handle crossing a sector portal. + if (!s->PortalBlocksMovement(sector_t::ceiling)) { - particle = &Level->Particles[i]; - i = particle->tnext; - if (Level->isFrozen() && !(particle->flags &PT_NOTIMEFREEZE)) + if (particle->Pos.Z > s->GetPortalPlaneZ(sector_t::ceiling)) { - prev = particle; - continue; - } - - auto oldtrans = particle->alpha; - particle->alpha -= particle->fadestep; - particle->size += particle->sizestep; - if (particle->alpha <= 0 || oldtrans < particle->alpha || --particle->ttl <= 0 || (particle->size <= 0)) - { // The particle has expired, so free it - *particle = {}; - if (prev) - prev->tnext = i; - else - Level->ActiveParticles = i; - - if (i != NO_PARTICLE) - { - particle_t *next = &Level->Particles[i]; - next->tprev = particle->tprev; - } - particle->tnext = Level->InactiveParticles; - Level->InactiveParticles = (int)(particle - Level->Particles.Data()); - continue; + particle->Pos += s->GetPortalDisplacement(sector_t::ceiling); + particle->subsector = NULL; } + } - // Handle crossing a line portal - DVector2 newxy = Level->GetPortalOffsetPosition(particle->Pos.X, particle->Pos.Y, particle->Vel.X, particle->Vel.Y); - particle->Pos.X = newxy.X; - particle->Pos.Y = newxy.Y; - particle->Pos.Z += particle->Vel.Z; - particle->Vel += particle->Acc; - - if(particle->flags & PT_DOROLL) + else if (!s->PortalBlocksMovement(sector_t::floor)) + { + if (particle->Pos.Z < s->GetPortalPlaneZ(sector_t::floor)) { - particle->Roll += particle->RollVel; - particle->RollVel += particle->RollAcc; + particle->Pos += s->GetPortalDisplacement(sector_t::floor); + particle->subsector = NULL; } - - particle->subsector = Level->PointInRenderSubsector(particle->Pos); - sector_t *s = particle->subsector->sector; - // Handle crossing a sector portal. - if (!s->PortalBlocksMovement(sector_t::ceiling)) + } + return false; +} + +void P_ThinkParticles (FLevelLocals *Level) +{ + particle_t *particle = nullptr, *prev = nullptr; + + TArray expired, expiredReplaceable; + + int n = Level->Particles.Size(); + + int last_alive = Level->Particles.Size() - 1; + + // iterate backwards to allow simple removal from the array + for(int i = last_alive; i > 0; i--) + { + particle_t * p = Level->Particles.Data() + i; + if(P_ThinkParticle(Level, p)) { - if (particle->Pos.Z > s->GetPortalPlaneZ(sector_t::ceiling)) - { - particle->Pos += s->GetPortalDisplacement(sector_t::ceiling); - particle->subsector = NULL; - } + if(i != last_alive) memcpy(p, Level->Particles.Data() + last_alive, sizeof(particle_t)); + last_alive--; } - else if (!s->PortalBlocksMovement(sector_t::floor)) + } + Level->Particles.Resize(last_alive + 1); + + last_alive = Level->ReplaceableParticles.size() - 1; + for(int i = last_alive; i > 0; i--) + { + particle_t * p = &Level->ReplaceableParticles[i]; + if(P_ThinkParticle(Level, p)) { - if (particle->Pos.Z < s->GetPortalPlaneZ(sector_t::floor)) - { - particle->Pos += s->GetPortalDisplacement(sector_t::floor); - particle->subsector = NULL; - } + if(i != last_alive) memcpy(p, &Level->ReplaceableParticles[last_alive], sizeof(particle_t)); + last_alive--; } - prev = particle; } + + Level->ReplaceableParticles.resize(last_alive + 1); + + ParticleCount = Level->Particles.Size() + Level->ReplaceableParticles.size(); } enum PSFlag @@ -353,19 +334,20 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v particle->Pos = pos; particle->Vel = vel; particle->Acc = accel; - particle->color = ParticleColor(color); - particle->alpha = float(startalpha); - if (fadestep < 0) particle->fadestep = FADEFROMTTL(lifetime); - else particle->fadestep = float(fadestep); - particle->ttl = lifetime; - particle->bright = !!(flags & PS_FULLBRIGHT); particle->size = size; particle->sizestep = sizestep; + particle->fadestep = (fadestep < 0) ? FADEFROMTTL(lifetime) : float(fadestep); + particle->alpha = float(startalpha); + particle->subsector = nullptr; + particle->ttl = lifetime; + particle->color = ParticleColor(color); particle->texture = texture; particle->style = style; - particle->Roll = startroll; - particle->RollVel = rollvel; - particle->RollAcc = rollacc; + particle->Roll = (float)startroll; + particle->RollVel = (float)rollvel; + particle->RollAcc = (float)rollacc; + particle->bright = !!(flags & PS_FULLBRIGHT); + particle->flags = 0; if(flags & PS_NOTIMEFREEZE) { particle->flags |= PT_NOTIMEFREEZE; @@ -398,6 +380,8 @@ particle_t *JitterParticle (FLevelLocals *Level, int ttl, double drift) if (particle) { int i; + *particle = {}; + // Set initial velocities for (i = 3; i; i--) particle->Vel[i] = ((1./4096) * (M_Random () - 128) * drift); @@ -624,6 +608,7 @@ void P_DrawSplash2 (FLevelLocals *Level, int count, const DVector3 &pos, DAngle if (!p) break; + *p = {}; p->ttl = 12; p->fadestep = FADEFROMTTL(12); @@ -777,6 +762,7 @@ void P_DrawRailTrail(AActor *source, TArray &portalhits, int color1, if (!p) return; + *p = {}; int spiralduration = (duration == 0) ? TICRATE : duration; diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index 3b542c19ec1..0abc7dcfebc 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -33,6 +33,9 @@ #pragma once +#define ABSOLUTE_MAX_PARTICLES 65535 +#define ABSOLUTE_MIN_PARTICLES 100 + #include "vectors.h" #include "doomdef.h" #include "renderstyle.h" @@ -69,13 +72,12 @@ struct particle_t int color; FTextureID texture; ERenderStyle style; - double Roll, RollVel, RollAcc; - uint16_t tnext, snext, tprev; + float Roll, RollVel, RollAcc; uint8_t bright; uint8_t flags; }; -const uint16_t NO_PARTICLE = 0xffff; +const uint32_t NO_PARTICLE = 0xffffffff; void P_InitParticles(FLevelLocals *); void P_ClearParticles (FLevelLocals *Level); diff --git a/src/rendering/hwrenderer/scene/hw_bsp.cpp b/src/rendering/hwrenderer/scene/hw_bsp.cpp index 822f7c2cadc..6f41c69f26f 100644 --- a/src/rendering/hwrenderer/scene/hw_bsp.cpp +++ b/src/rendering/hwrenderer/scene/hw_bsp.cpp @@ -594,16 +594,17 @@ void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector) void HWDrawInfo::RenderParticles(subsector_t *sub, sector_t *front) { SetupSprite.Clock(); - for (int i = Level->ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Level->Particles[i].snext) + for (uint32_t i = 0; i < sub->particles.Size(); i++) { + particle_t * p = sub->particles[i]; if (mClipPortal) { - int clipres = mClipPortal->ClipPoint(Level->Particles[i].Pos); + int clipres = mClipPortal->ClipPoint(p->Pos); if (clipres == PClip_InFront) continue; } HWSprite sprite; - sprite.ProcessParticle(this, &Level->Particles[i], front); + sprite.ProcessParticle(this, p, front); } SetupSprite.Unclock(); } @@ -666,7 +667,7 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) } // [RH] Add particles - if (gl_render_things && Level->ParticlesInSubsec[sub->Index()] != NO_PARTICLE) + if (gl_render_things && sub->particles.Size() > 0) { if (multithread) { diff --git a/src/rendering/swrenderer/scene/r_opaque_pass.cpp b/src/rendering/swrenderer/scene/r_opaque_pass.cpp index bcd864d05c3..4864790f7a0 100644 --- a/src/rendering/swrenderer/scene/r_opaque_pass.cpp +++ b/src/rendering/swrenderer/scene/r_opaque_pass.cpp @@ -612,9 +612,9 @@ namespace swrenderer if ((unsigned int)(sub->Index()) < Level->subsectors.Size()) { // Only do it for the main BSP. int lightlevel = (floorlightlevel + ceilinglightlevel) / 2; - for (int i = frontsector->Level->ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = frontsector->Level->Particles[i].snext) + for (uint32_t i = 0; i < sub->particles.Size(); i++) { - RenderParticle::Project(Thread, &frontsector->Level->Particles[i], sub->sector, lightlevel, FakeSide, foggy); + RenderParticle::Project(Thread, sub->particles[i], sub->sector, lightlevel, FakeSide, foggy); } }