From 81f6f09085e8d1472958393902c008e727801777 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Fri, 14 Aug 2026 20:07:57 +0100 Subject: [PATCH 1/4] chore(particlesys): Refactor point count into a class variable, ready for further optimisations --- .../W3DDevice/GameClient/W3DParticleSys.cpp | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index c2a1b9c6fc4..8ea4049ddfc 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -144,6 +144,9 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) TheSmudgeManager->resetDraw(); } + // Number of particle/points being rendered + UnsignedInt pointCount = 0; + ParticleSystemManager::ParticleSystemList &particleSysList = TheParticleSystemManager->getAllParticleSystems(); for( ParticleSystemManager::ParticleSystemListIt it = particleSysList.begin(); it != particleSysList.end(); ++it) { @@ -189,7 +192,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) /// @todo lorenzen sez: declare these outside the sys loop, and put some in registers // initialize them here still, of course // build W3D particle buffer - Int count = 0; + pointCount = 0; Vector3 *posArray = m_posBuffer->Get_Array(); Real *sizeArray = m_sizeBuffer->Get_Array(); Vector4 *RGBAArray = m_RGBABuffer->Get_Array(); @@ -219,32 +222,32 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_isGroundAligned != FALSE ); //@todo lorenzen sez: use pointer arithmetic for these arrays - personalities[count] = p->getPersonality(); + personalities[pointCount] = p->getPersonality(); - posArray[count].X = pos->x; - posArray[count].Y = pos->y; - posArray[count].Z = pos->z; + posArray[pointCount].X = pos->x; + posArray[pointCount].Y = pos->y; + posArray[pointCount].Z = pos->z; - sizeArray[count] = psize; + sizeArray[pointCount] = psize; color = p->getColor(); - RGBAArray[count].X = color->red; - RGBAArray[count].Y = color->green; - RGBAArray[count].Z = color->blue; - RGBAArray[count].W = p->getAlpha(); + RGBAArray[pointCount].X = color->red; + RGBAArray[pointCount].Y = color->green; + RGBAArray[pointCount].Z = color->blue; + RGBAArray[pointCount].W = p->getAlpha(); - angleArray[count] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); + angleArray[pointCount] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); - if (++count == MAX_POINTS_PER_GROUP) + if (++pointCount == MAX_POINTS_PER_GROUP) break; } - if ( count == 0 ) + if ( pointCount == 0 ) continue; //this system has no particles to render TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); - if ( m_streakLine && sys->isUsingStreak() && (count >= 2) ) + if ( m_streakLine && sys->isUsingStreak() && (pointCount >= 2) ) { m_streakLine->Reset_Line(); @@ -268,14 +271,14 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //UPDATE THE STREAK'S ARRAYS m_streakLine->Set_LocsWidthsColors( - count, + pointCount, m_posBuffer->Get_Array(), m_sizeBuffer->Get_Array(), m_RGBABuffer->Get_Array(), &personalities[0] ); - //WWASSERT( m_streakLine->Get_Num_Points() == count ); + //WWASSERT( m_streakLine->Get_Num_Points() == pointCount ); // This is the happy place for this! RGBAArray[0].X = 0;//eliminates the scissor edge on the trailing edge of the streak @@ -318,7 +321,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) /// @todo Use both QUADS and TRIS for particles m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); - m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, count ); + m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount); m_pointGroup->Set_Billboard(sys->shouldBillboard()); /// @todo Support animated texture particles @@ -339,7 +342,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) /// @todo lorenzen sez: this should be debug only: //add particle count to total - m_onScreenParticleCount += count; + m_onScreenParticleCount += pointCount; /* // draw the wind vector for this particle system on the screen From 02f76301552561ea93e4679c11554cad18b304f6 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:10:15 +0100 Subject: [PATCH 2/4] perf(particlesys): Implement batched rendering for particles (#) --- .../Include/GameClient/ParticleSys.h | 5 +- .../W3DDevice/GameClient/W3DParticleSys.h | 6 + .../W3DDevice/GameClient/W3DParticleSys.cpp | 243 +++++++++++++----- 3 files changed, 188 insertions(+), 66 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index acd504f6838..9ebed6da9f4 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -59,6 +59,7 @@ enum ParticleSystemID CPP_11(: Int) #define MAX_VOLUME_PARTICLE_DEPTH ( 16 ) #define DEFAULT_VOLUME_PARTICLE_DEPTH ( 0 )//The Default is not to do the volume thing! +#define MIN_VOLUME_PARTICLE_DEPTH ( 2 ) #define OPTIMUM_VOLUME_PARTICLE_DEPTH ( 6 ) // TheSuperHackers @info The X and Y angles are not necessary for particles because there are only 2 placement modes: @@ -511,7 +512,7 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem public: ParticleSystemTemplate( const AsciiString &name ); - AsciiString getName() const { return m_name; } + const AsciiString& getName() const { return m_name; } // This function was made const because of update modules' module data being all const. ParticleSystem *createSlaveSystem( Bool createSlaves = TRUE ) const ; ///< if returns non-null, it is a slave system for use @@ -602,7 +603,7 @@ class ParticleSystem : public MemoryPoolObject, void setInitialDelay( UnsignedInt delay ) { m_delayLeft = delay; } - AsciiString getParticleTypeName() { return m_particleTypeName; } ///< return the name of the particles + const AsciiString& getParticleTypeName() const { return m_particleTypeName; } ///< return the name of the particles Bool isUsingDrawables() { return (m_particleType == DRAWABLE) ? true : false; } Bool isUsingStreak() { return (m_particleType == STREAK) ? true : false; } Bool isUsingSmudge() { return (m_particleType == SMUDGE) ? true : false; } diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h index 41481701474..36ff3636780 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h @@ -50,13 +50,19 @@ class W3DParticleSystemManager : public ParticleSystemManager virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; } private: + void flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount); + enum { MAX_POINTS_PER_GROUP = 512 }; + TextureClass *m_batchTexture; ///< the texture used as the drawing surface for batched particle draws PointGroupClass *m_pointGroup; ///< the point group that contains all of the particles StreakLineClass *m_streakLine; ///< the streak class that contains all of the streaks ShareBufferClass *m_posBuffer; ///< array of particle positions ShareBufferClass *m_RGBABuffer; ///< array of particle color and alpha ShareBufferClass *m_sizeBuffer; ///< array of particle sizes ShareBufferClass *m_angleBuffer; ///< array of particle orientations + + ParticleSystemInfo::ParticleShaderType m_batchShaderType; Bool m_readyToRender; ///< if true, it is OK to render + Bool m_batchBillboard; }; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index 8ea4049ddfc..d9e89fb53a3 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -46,6 +46,10 @@ W3DParticleSystemManager::W3DParticleSystemManager() { + m_batchBillboard = true; + m_batchShaderType = ParticleSystemInfo::INVALID_SHADER; + m_batchTexture = nullptr; + m_pointGroup = nullptr; m_streakLine = nullptr; m_posBuffer = nullptr; @@ -77,6 +81,11 @@ W3DParticleSystemManager::~W3DParticleSystemManager() REF_PTR_RELEASE(m_streakLine); } + if (m_batchTexture) + { + REF_PTR_RELEASE(m_batchTexture); + } + REF_PTR_RELEASE(m_posBuffer); REF_PTR_RELEASE(m_RGBABuffer); REF_PTR_RELEASE(m_sizeBuffer); @@ -144,7 +153,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) TheSmudgeManager->resetDraw(); } - // Number of particle/points being rendered + // Number of particles/points being rendered. UnsignedInt pointCount = 0; ParticleSystemManager::ParticleSystemList &particleSysList = TheParticleSystemManager->getAllParticleSystems(); @@ -159,6 +168,31 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) if (sys->isUsingDrawables()) continue; + // TheSuperHackers @performance Mauller 16/08/2026 Test if the particle system has any visible particles that can be drawn. + // Earlier visibility testing prevents the particle texture lookup which can cause a batch flush. + int particleCount = 0; + for (Particle* vp = sys->getFirstParticle(); vp; vp = vp->m_systemNext) + { + const Coord3D* pos = vp->getPosition(); + Real psize = vp->getSize(); + + //Test if particle is at the screen or terrain edges. + if (WWMath::Fabs(pos->x - bcX) > (beX + psize) || + WWMath::Fabs(pos->y - bcY) > (beY + psize) || + WWMath::Fabs(pos->z - bcZ) > (beZ + psize)) + { + vp->setIsCulled(true); + continue; + } + + vp->setIsCulled(false); + particleCount++; + } + + // Particle system has no particles on screen + if (particleCount == 0) + continue; + // Handle smudge type particles if (sys->isUsingSmudge()) { @@ -167,17 +201,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) { - const Coord3D *pos = p->getPosition(); - Real psize = p->getSize(); - - //Cull particle to edges of screen and terrain. - if (WWMath::Fabs( pos->x - bcX ) > ( beX + psize ) ) - continue; - - if (WWMath::Fabs( pos->y - bcY ) > ( beY + psize ) ) - continue; - - if (WWMath::Fabs( pos->z - bcZ ) > ( beZ + psize ) ) + if (p->isCulled()) continue; if (Smudge *smudge = TheSmudgeManager->findSmudge(p)) @@ -189,10 +213,31 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) continue; } - /// @todo lorenzen sez: declare these outside the sys loop, and put some in registers - // initialize them here still, of course + // TheSuperHackers @performance Ronin/Mauller 09/08/2026 Implement batched rendering for similar particles. + // Particles with the same blending will now be batched onto a single texture surface before being drawn. + // If a different particle type appears before the batch is filled, the previous batch will be drawn first. + TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + const Bool canBatch = !sys->isUsingStreak() && sys->getVolumeParticleDepth() < MIN_VOLUME_PARTICLE_DEPTH; + if (!canBatch || + texture != m_batchTexture || + sys->getShaderType() != m_batchShaderType || + sys->shouldBillboard() != m_batchBillboard) + { + flushParticleBatch(rinfo, pointCount); + } + + // setup a new particle batch texture if prior batch was flushed. + if (canBatch && m_batchTexture == nullptr) + { + m_batchTexture = texture; + m_batchTexture->Add_Ref(); + m_batchShaderType = sys->getShaderType(); + m_batchBillboard = sys->shouldBillboard(); + } + + Int startCount = pointCount; + // build W3D particle buffer - pointCount = 0; Vector3 *posArray = m_posBuffer->Get_Array(); Real *sizeArray = m_sizeBuffer->Get_Array(); Vector4 *RGBAArray = m_RGBABuffer->Get_Array(); @@ -206,18 +251,11 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //set-up all the per-particle for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) { - pos = p->getPosition(); - psize = p->getSize(); - - //Cull particle to edges of screen and terrain. - if (WWMath::Fabs(pos->x - bcX) > (beX + psize)) - continue; - - if (WWMath::Fabs(pos->y - bcY) > (beY + psize)) + if (p->isCulled()) continue; - if (WWMath::Fabs(pos->z - bcZ) > (beZ + psize)) - continue; + pos = p->getPosition(); + psize = p->getSize(); m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_isGroundAligned != FALSE ); @@ -239,13 +277,29 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) angleArray[pointCount] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); if (++pointCount == MAX_POINTS_PER_GROUP) - break; + { + if (!canBatch) + { + break; + } + + // TheSuperHackers @info The Buffer is full mid-system so draw what we have and carry on with the SAME system. + // This prevents particles being dropped. Bank the stats first as the flush resets count to 0. + m_onScreenParticleCount += (pointCount - startCount); + flushParticleBatch(rinfo, pointCount); + m_batchTexture = texture; + m_batchTexture->Add_Ref(); + m_batchShaderType = sys->getShaderType(); + m_batchBillboard = sys->shouldBillboard(); + startCount = 0; + } } - if ( pointCount == 0 ) + if (pointCount == startCount) + { + texture->Release_Ref(); continue; //this system has no particles to render - - TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + } if ( m_streakLine && sys->isUsingStreak() && (pointCount >= 2) ) { @@ -298,51 +352,70 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) if ( m_pointGroup ) // this catches the particle and volumeparticle cases { - // render all the systems' particles - m_pointGroup->Set_Texture( texture ); - texture->Release_Ref();//release reference since it's held by pointGroup - m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - - switch( sys->getShaderType() ) + if ( sys->getVolumeParticleDepth() >= MIN_VOLUME_PARTICLE_DEPTH ) { - case ParticleSystemInfo::ADDITIVE: - m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); - break; - case ParticleSystemInfo::ALPHA: - m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); - break; - case ParticleSystemInfo::ALPHA_TEST: - m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); - break; - case ParticleSystemInfo::MULTIPLY: - m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); - break; - } - - /// @todo Use both QUADS and TRIS for particles - m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); - m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount); - m_pointGroup->Set_Billboard(sys->shouldBillboard()); - - /// @todo Support animated texture particles - /// @todo lorenzen sez: unimplemented code wastes cpu cycles - m_pointGroup->Set_Point_Frame( 0 ); + m_pointGroup->Set_Texture( texture ); + texture->Release_Ref();//release reference since it's held by pointGroup + m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space + + switch( sys->getShaderType() ) + { + case ParticleSystemInfo::ADDITIVE: + m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); + break; + case ParticleSystemInfo::ALPHA: + m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); + break; + case ParticleSystemInfo::ALPHA_TEST: + m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); + break; + case ParticleSystemInfo::MULTIPLY: + m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); + break; + } + + /// @todo Use both QUADS and TRIS for particles + m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); + m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount ); + m_pointGroup->Set_Billboard(sys->shouldBillboard()); + + /// @todo Support animated texture particles + /// @todo lorenzen sez: unimplemented code wastes cpu cycles + m_pointGroup->Set_Point_Frame( 0 ); - //RENDER IT! - if( sys->getVolumeParticleDepth() > 1 ) - { m_pointGroup->RenderVolumeParticle( rinfo, sys->getVolumeParticleDepth() ); - } + m_onScreenParticleCount += (pointCount - startCount); + pointCount = startCount; + } else - m_pointGroup->Render( rinfo ); - + { + if ( m_batchTexture == nullptr ) + { + m_batchTexture = texture; + m_batchShaderType = sys->getShaderType(); + m_batchBillboard = sys->shouldBillboard(); + } + else + { + texture->Release_Ref(); // same key as the pending batch so drop the duplicate ref + } + + if ( pointCount >= MAX_POINTS_PER_GROUP ) + { + flushParticleBatch(rinfo, pointCount); + } + } + } + else + { + texture->Release_Ref(); } } /// @todo lorenzen sez: this should be debug only: //add particle count to total - m_onScreenParticleCount += pointCount; + m_onScreenParticleCount += (pointCount - startCount); /* // draw the wind vector for this particle system on the screen @@ -364,6 +437,9 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) } + // TheSuperHackers @info Flush the last batch if one is pending. + flushParticleBatch(rinfo, pointCount); + /// @todo lorenzen sez: this should be debug only: TheParticleSystemManager->setOnScreenParticleCount(m_onScreenParticleCount); @@ -377,3 +453,42 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) ((W3DSmudgeManager *)TheSmudgeManager)->render(rinfo); } } + +void W3DParticleSystemManager::flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount) +{ + if (pointCount > 0 && m_batchTexture != nullptr && m_pointGroup != nullptr) + { + m_pointGroup->Set_Texture(m_batchTexture); + + switch (m_batchShaderType) + { + case ParticleSystemInfo::ADDITIVE: + m_pointGroup->Set_Shader(ShaderClass::_PresetAdditiveSpriteShader); + break; + case ParticleSystemInfo::ALPHA: + m_pointGroup->Set_Shader(ShaderClass::_PresetAlphaSpriteShader); + break; + case ParticleSystemInfo::ALPHA_TEST: + m_pointGroup->Set_Shader(ShaderClass::_PresetATestSpriteShader); + break; + case ParticleSystemInfo::MULTIPLY: + m_pointGroup->Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); + break; + } + + m_pointGroup->Set_Flag(PointGroupClass::TRANSFORM, true); + m_pointGroup->Set_Point_Mode(PointGroupClass::QUADS); + m_pointGroup->Set_Arrays(m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount); + m_pointGroup->Set_Billboard(m_batchBillboard); + m_pointGroup->Set_Point_Frame(0); + m_pointGroup->Render(rinfo); + } + + if (m_batchTexture != nullptr) + { + m_batchTexture->Release_Ref(); + m_batchTexture = nullptr; + } + + pointCount = 0; +} From e564b55887275131a2294bf21338db88c2e288d6 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:54:15 +0100 Subject: [PATCH 3/4] refactor(particlesys): Use refCountPtr objects to hold ref counted textures and cleanup batch initialization --- .../W3DDevice/GameClient/W3DParticleSys.h | 3 +- .../W3DDevice/GameClient/W3DParticleSys.cpp | 57 +++++++------------ 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h index 36ff3636780..424814dd291 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h @@ -50,11 +50,12 @@ class W3DParticleSystemManager : public ParticleSystemManager virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; } private: + void initializeBatch(ParticleSystem* system, const RefCountPtr& texture); void flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount); enum { MAX_POINTS_PER_GROUP = 512 }; - TextureClass *m_batchTexture; ///< the texture used as the drawing surface for batched particle draws + RefCountPtr m_batchTexture; ///< the texture used as the drawing surface for batched particle draws PointGroupClass *m_pointGroup; ///< the point group that contains all of the particles StreakLineClass *m_streakLine; ///< the streak class that contains all of the streaks ShareBufferClass *m_posBuffer; ///< array of particle positions diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index d9e89fb53a3..a67a0b7f72a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -48,7 +48,6 @@ W3DParticleSystemManager::W3DParticleSystemManager() { m_batchBillboard = true; m_batchShaderType = ParticleSystemInfo::INVALID_SHADER; - m_batchTexture = nullptr; m_pointGroup = nullptr; m_streakLine = nullptr; @@ -81,11 +80,6 @@ W3DParticleSystemManager::~W3DParticleSystemManager() REF_PTR_RELEASE(m_streakLine); } - if (m_batchTexture) - { - REF_PTR_RELEASE(m_batchTexture); - } - REF_PTR_RELEASE(m_posBuffer); REF_PTR_RELEASE(m_RGBABuffer); REF_PTR_RELEASE(m_sizeBuffer); @@ -214,12 +208,14 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) } // TheSuperHackers @performance Ronin/Mauller 09/08/2026 Implement batched rendering for similar particles. - // Particles with the same blending will now be batched onto a single texture surface before being drawn. + // Particles with the same properties will now be batched onto a single texture surface before being drawn. // If a different particle type appears before the batch is filled, the previous batch will be drawn first. - TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + RefCountPtr texture; + texture.Assign_No_Add_Ref(W3DDisplay::m_assetManager->Get_Texture(sys->getParticleTypeName().str())); + const Bool canBatch = !sys->isUsingStreak() && sys->getVolumeParticleDepth() < MIN_VOLUME_PARTICLE_DEPTH; if (!canBatch || - texture != m_batchTexture || + texture.Peek() != m_batchTexture.Peek() || sys->getShaderType() != m_batchShaderType || sys->shouldBillboard() != m_batchBillboard) { @@ -229,10 +225,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) // setup a new particle batch texture if prior batch was flushed. if (canBatch && m_batchTexture == nullptr) { - m_batchTexture = texture; - m_batchTexture->Add_Ref(); - m_batchShaderType = sys->getShaderType(); - m_batchBillboard = sys->shouldBillboard(); + initializeBatch(sys, texture); } Int startCount = pointCount; @@ -287,17 +280,13 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) // This prevents particles being dropped. Bank the stats first as the flush resets count to 0. m_onScreenParticleCount += (pointCount - startCount); flushParticleBatch(rinfo, pointCount); - m_batchTexture = texture; - m_batchTexture->Add_Ref(); - m_batchShaderType = sys->getShaderType(); - m_batchBillboard = sys->shouldBillboard(); + initializeBatch(sys, texture); startCount = 0; } } if (pointCount == startCount) { - texture->Release_Ref(); continue; //this system has no particles to render } @@ -305,8 +294,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) { m_streakLine->Reset_Line(); - m_streakLine->Set_Texture( texture ); - texture->Release_Ref();//release reference since it's held by streakline + m_streakLine->Set_Texture( texture.Peek() ); switch( sys->getShaderType() ) { case ParticleSystemInfo::ADDITIVE: @@ -354,8 +342,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) { if ( sys->getVolumeParticleDepth() >= MIN_VOLUME_PARTICLE_DEPTH ) { - m_pointGroup->Set_Texture( texture ); - texture->Release_Ref();//release reference since it's held by pointGroup + m_pointGroup->Set_Texture( texture.Peek() ); m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space switch( sys->getShaderType() ) @@ -391,13 +378,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) { if ( m_batchTexture == nullptr ) { - m_batchTexture = texture; - m_batchShaderType = sys->getShaderType(); - m_batchBillboard = sys->shouldBillboard(); - } - else - { - texture->Release_Ref(); // same key as the pending batch so drop the duplicate ref + initializeBatch(sys, texture); } if ( pointCount >= MAX_POINTS_PER_GROUP ) @@ -406,10 +387,6 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) } } } - else - { - texture->Release_Ref(); - } } @@ -454,11 +431,18 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) } } +void W3DParticleSystemManager::initializeBatch(ParticleSystem* system, const RefCountPtr& texture) +{ + m_batchTexture = texture; + m_batchShaderType = system->getShaderType(); + m_batchBillboard = system->shouldBillboard(); +} + void W3DParticleSystemManager::flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount) { - if (pointCount > 0 && m_batchTexture != nullptr && m_pointGroup != nullptr) + if (pointCount > 0 && m_batchTexture != nullptr) { - m_pointGroup->Set_Texture(m_batchTexture); + m_pointGroup->Set_Texture(m_batchTexture.Peek()); switch (m_batchShaderType) { @@ -486,8 +470,7 @@ void W3DParticleSystemManager::flushParticleBatch(RenderInfoClass& rinfo, Unsign if (m_batchTexture != nullptr) { - m_batchTexture->Release_Ref(); - m_batchTexture = nullptr; + m_batchTexture.Clear(); } pointCount = 0; From a19f5f82088df99622d3169bae99461bb32f25b1 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Thu, 20 Aug 2026 22:01:25 +0100 Subject: [PATCH 4/4] refactor(particlesys): Remove unnecessary null checks on pointGroup and streakLine objects --- .../W3DDevice/GameClient/W3DParticleSys.cpp | 87 ++++++++----------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index a67a0b7f72a..2b647918d94 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -290,7 +290,8 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) continue; //this system has no particles to render } - if ( m_streakLine && sys->isUsingStreak() && (pointCount >= 2) ) + // Handle drawing streak type particles. + if ( sys->isUsingStreak() && (pointCount >= 2) ) { m_streakLine->Reset_Line(); @@ -331,62 +332,44 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //RENDER STREAK! m_streakLine->Render( rinfo ); - + m_onScreenParticleCount += (pointCount - startCount); + pointCount = startCount; } - else - { - WWASSERT( m_pointGroup ); + // Handle volumetric type particle systems. + if ( sys->getVolumeParticleDepth() >= MIN_VOLUME_PARTICLE_DEPTH ) + { + m_pointGroup->Set_Texture( texture.Peek() ); + m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - if ( m_pointGroup ) // this catches the particle and volumeparticle cases + switch( sys->getShaderType() ) { - if ( sys->getVolumeParticleDepth() >= MIN_VOLUME_PARTICLE_DEPTH ) - { - m_pointGroup->Set_Texture( texture.Peek() ); - m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - - switch( sys->getShaderType() ) - { - case ParticleSystemInfo::ADDITIVE: - m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); - break; - case ParticleSystemInfo::ALPHA: - m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); - break; - case ParticleSystemInfo::ALPHA_TEST: - m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); - break; - case ParticleSystemInfo::MULTIPLY: - m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); - break; - } - - /// @todo Use both QUADS and TRIS for particles - m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); - m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount ); - m_pointGroup->Set_Billboard(sys->shouldBillboard()); - - /// @todo Support animated texture particles - /// @todo lorenzen sez: unimplemented code wastes cpu cycles - m_pointGroup->Set_Point_Frame( 0 ); - - m_pointGroup->RenderVolumeParticle( rinfo, sys->getVolumeParticleDepth() ); - m_onScreenParticleCount += (pointCount - startCount); - pointCount = startCount; - } - else - { - if ( m_batchTexture == nullptr ) - { - initializeBatch(sys, texture); - } - - if ( pointCount >= MAX_POINTS_PER_GROUP ) - { - flushParticleBatch(rinfo, pointCount); - } - } + case ParticleSystemInfo::ADDITIVE: + m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); + break; + case ParticleSystemInfo::ALPHA: + m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); + break; + case ParticleSystemInfo::ALPHA_TEST: + m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); + break; + case ParticleSystemInfo::MULTIPLY: + m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); + break; } + + /// @todo Use both QUADS and TRIS for particles + m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); + m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount ); + m_pointGroup->Set_Billboard(sys->shouldBillboard()); + + /// @todo Support animated texture particles + /// @todo lorenzen sez: unimplemented code wastes cpu cycles + m_pointGroup->Set_Point_Frame( 0 ); + + m_pointGroup->RenderVolumeParticle( rinfo, sys->getVolumeParticleDepth() ); + m_onScreenParticleCount += (pointCount - startCount); + pointCount = startCount; }