Skip to content

perf(particlesys): Batch same type particles to improve particle rendering performance by 15 - 30% - #3155

Open
Mauller wants to merge 4 commits into
TheSuperHackers:mainfrom
Mauller:Mauller/perf-batch-particle-draws
Open

perf(particlesys): Batch same type particles to improve particle rendering performance by 15 - 30%#3155
Mauller wants to merge 4 commits into
TheSuperHackers:mainfrom
Mauller:Mauller/perf-batch-particle-draws

Conversation

@Mauller

@Mauller Mauller commented Aug 15, 2026

Copy link
Copy Markdown

This can be squash merged

This PR is separated into two commits to aid reviewing.
The initial commit is a small refactor to make the diff slightly cleaner on the second commit.
The second commit implements the particle batching created by Ronin and cleaned up by myself.

When testing we see a 15-20% performance improvement on average. But this may be higher in some scenarios.
EDIT: An early particle visibility test has added 2 - 5% more performance on top of the original.

The batching works by creating a common texture that the particle effects are drawn to before being sent to the GPU. This reduces the number of draw calls, thus improving rendering performance.

Only particles with the same material and shader type can be batched to the texture, so once particles of a different type are observed. The system will flush the prior batch and start a new one based on the new particle type.

As draw order is preserved, particles adhere to their original layering.


Some performance comparison images

Using the chemical spray as the source of particles, all tractors are using their AoE spray ability when these images were taken.

Before:
image

After:
image

Using the firewall and flamethrower effect of the flame tank, a circle of flame tanks are creating firewalls in the centre while extra flamers spray fire into the centre of the inferno.

Before:
image

After:
image

@Mauller Mauller self-assigned this Aug 15, 2026
@Mauller Mauller added Major Severity: Minor < Major < Critical < Blocker Performance Is a performance concern Gen Relates to Generals ZH Relates to Zero Hour labels Aug 15, 2026
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 15, 2026

Copy link
Copy Markdown

PR Summary by Qodo

perf(particlesys): Batch same-type particles to reduce draw calls

✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Add same-texture/shader/billboard batching to reduce particle draw calls while preserving order.
• Add early per-system visibility culling to skip texture lookups and rendering work.
• Refine particle system getters to return const references and be const-correct.
Diagram

graph TD
  A["W3DParticleSystemManager::doParticles"] --> B["Cull & mark particles"] --> C{Any visible?}
  C -->|No| A
  C -->|Yes| D{Batch key matches?}
  D -->|No| F["Flush current batch"] --> D
  D -->|Yes| E["Append to buffers"] --> G["Direct render (streak/volume) or defer"] --> A
  A -. "end frame" .-> H["Flush pending batch"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. GPU instancing / dynamic vertex buffer per material
  • ➕ Potentially larger win than texture-based batching by minimizing CPU-side state changes
  • ➕ Keeps a clearer separation between batching and per-system logic
  • ➖ Higher implementation complexity in this engine path (shader/vertex format changes)
  • ➖ May require broader renderer changes and more testing across particle types
2. Pre-bucket systems by render key (texture/shader/billboard)
  • ➕ Maximizes batching opportunities beyond adjacent systems
  • ➕ Simplifies flush frequency
  • ➖ Likely breaks strict draw order/layering unless additional depth-sorting logic is added
  • ➖ Can introduce visual regressions for effects relying on deterministic ordering
3. Cache texture handle on ParticleSystem / template
  • ➕ Avoid per-frame asset manager lookups and reduces batch-key churn
  • ➕ Lower risk incremental optimization that composes with current batching
  • ➖ Requires careful lifetime/ref-count management and invalidation rules
  • ➖ Smaller win than batching alone if lookups are already cheap

Recommendation: The PR’s approach is a good tradeoff: it improves performance while preserving existing draw order by only batching adjacent compatible systems and flushing on key changes/full buffers. Consider a follow-up to cache resolved particle textures (or a stable render key) on the system/template to further reduce asset lookups and reduce unnecessary flush triggers.

Files changed (3) +185 / -89

Enhancement (2) +183 / -87
W3DParticleSys.hAdd batching helpers and batch state to W3D particle manager +7/-0

Add batching helpers and batch state to W3D particle manager

• Introduces initializeBatch/flushParticleBatch helpers and stores batch key state (texture, shader type, billboard flag). This enables batched sprite rendering across compatible particle systems.

Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h

W3DParticleSys.cppImplement early particle visibility culling and batched sprite flushing +176/-87

Implement early particle visibility culling and batched sprite flushing

• Adds an early per-system visibility pass that marks particles culled to avoid unnecessary work. Implements batching for compatible sprite particles (same texture/shader/billboard), flushing on key changes, buffer-full mid-system, and end-of-frame; streak and volumetric systems continue to render via their dedicated paths.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp

Refactor (1) +2 / -2
ParticleSys.hMake particle template/system name getters const-ref and const +2/-2

Make particle template/system name getters const-ref and const

• Updates ParticleSystemTemplate::getName and ParticleSystem::getParticleTypeName to return const references and be const-qualified. This reduces string copies and improves const-correctness for rendering and lookup call sites.

Core/GameEngine/Include/GameClient/ParticleSys.h

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. canBatch always true 🐞 Bug ≡ Correctness
Description
doParticles() computes canBatch with `!isUsingStreak() ||
getVolumeParticleDepth()==DEFAULT_VOLUME_PARTICLE_DEPTH`, but DEFAULT_VOLUME_PARTICLE_DEPTH is 0 and
getVolumeParticleDepth() returns 0 for all non-volume systems (including STREAK), making canBatch
effectively always true. This allows streak/volumetric systems to share the global batched buffer
and then render using pointCount from index 0, which can include particles from previous systems,
producing incorrect rendering and potential duplicate draws.
Code

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[R224-230]

+		const Bool canBatch = !sys->isUsingStreak() || sys->getVolumeParticleDepth() == DEFAULT_VOLUME_PARTICLE_DEPTH;
+		if (!canBatch ||
+			texture.Peek() != m_batchTexture.Peek() ||
+			sys->getShaderType() != m_batchShaderType ||
+			sys->shouldBillboard() != m_batchBillboard)
+		{
+			flushParticleBatch(rinfo, pointCount);
Evidence
DEFAULT_VOLUME_PARTICLE_DEPTH is defined as 0, while ParticleSystem::getVolumeParticleDepth()
returns 0 for all non-volume particle types; this makes the OR-based canBatch expression true even
for STREAK systems. The STREAK and volumetric render paths then render using pointCount and raw
arrays from index 0, which will include any prior batched particle data when a batch is pending.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[218-239]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[301-345]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[347-378]
Core/GameEngine/Include/GameClient/ParticleSys.h[60-62]
Core/GameEngine/Include/GameClient/ParticleSys.h[605-610]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`canBatch` is computed in a way that makes it effectively always `true` (because DEFAULT_VOLUME_PARTICLE_DEPTH is 0 and `getVolumeParticleDepth()` returns 0 for non-volume systems). This allows non-batchable systems (notably STREAK and volumetric particle draws) to be processed as if they were batchable, contaminating the shared particle buffers and leading to incorrect rendering.
### Issue Context
Batching relies on `pointCount` being a global “batch” count, but STREAK and volumetric rendering paths call render functions using `pointCount` and buffer pointers starting at index 0. If a pending batch exists, these paths will read/render prior systems’ particle data.
### Fix Focus Areas
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[218-239]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[301-378]
- Core/GameEngine/Include/GameClient/ParticleSys.h[60-62]
- Core/GameEngine/Include/GameClient/ParticleSys.h[605-610]
### Expected change
Update the eligibility logic so batching is only enabled for the intended particle types (e.g., sprite/non-streak and non-volumetric). For example:
- Use `&&` instead of `||` and ensure the volumetric depth check actually excludes volumetric systems.
- Ensure streak/volumetric paths force a flush before populating their local buffers (or otherwise guarantee they start from index 0 without pending batched content).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. RefCountPtr double release 🐞 Bug ☼ Reliability
Description
The destructor calls REF_PTR_RELEASE(m_batchTexture) even though m_batchTexture is a
RefCountPtr, causing Release_Ref() to be invoked twice (once via x->Release_Ref() and once via
x = nullptr which releases the current referent). This can delete the texture early and then
release a freed object (use-after-free) during shutdown/teardown.
Code

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[R84-87]

+	if (m_batchTexture)
+	{
+		REF_PTR_RELEASE(m_batchTexture);
+	}
Evidence
The PR calls REF_PTR_RELEASE on a RefCountPtr member in the destructor. REF_PTR_RELEASE performs
both an explicit Release_Ref() and then assigns nullptr; RefCountPtr's nullptr assignment operator
also calls Release_Ref(), resulting in two releases of the same referent.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[73-88]
Core/Libraries/Source/WWVegas/WWLib/refcount.h[63-65]
Core/Libraries/Source/WWVegas/WWLib/ref_ptr.h[291-300]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`REF_PTR_RELEASE` is a macro intended for raw ref-counted pointers. Applying it to `RefCountPtr<T>` can double-release the referent because the macro calls `x->Release_Ref()` and then `x = nullptr`, and `RefCountPtr::operator=(nullptr)` also releases the current referent.
### Issue Context
`W3DParticleSystemManager::m_batchTexture` is declared as `RefCountPtr<TextureClass>`. It already releases in its destructor and also provides `Clear()` for explicit release.
### Fix Focus Areas
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[73-88]
- Core/Libraries/Source/WWVegas/WWLib/refcount.h[63-65]
- Core/Libraries/Source/WWVegas/WWLib/ref_ptr.h[291-300]
### Expected change
Replace `REF_PTR_RELEASE(m_batchTexture)` with `m_batchTexture.Clear()` (or remove the explicit release entirely and rely on `RefCountPtr` destruction). Do not use `REF_PTR_RELEASE` on `RefCountPtr` variables.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Batch texture ref leak ✓ Resolved 🐞 Bug ☼ Reliability
Description
W3DParticleSystemManager retains an extra reference to m_batchTexture via Add_Ref() during batching,
but the destructor never releases it, so destroying the manager with a pending batch can leak a
TextureClass ref and prevent proper cleanup. Normal end-of-frame flushing usually releases it, but
lifecycle paths that skip the final flush (shutdown/teardown) can still leak.
Code

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[R210-213]

+		if (canBatch && m_batchTexture == nullptr)
+		{
+			m_batchTexture = texture;
+			m_batchTexture->Add_Ref();
Evidence
The batching code explicitly increments the texture refcount for m_batchTexture, and
flushParticleBatch() decrements it, but the class destructor does not release m_batchTexture at
all, violating the refcount ownership rules for stored pointers.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[195-216]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[256-282]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[441-478]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[75-90]
Core/Libraries/Source/WWVegas/WWLib/refcount.h[67-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`W3DParticleSystemManager` now owns a ref-counted `m_batchTexture` (via `Add_Ref()`), but its destructor doesn’t release that ownership. If the manager is destroyed while a batch is pending, the texture ref can leak.
### Issue Context
- `Get_Texture()` returns an add-ref’d `TextureClass*`, and any retained pointer must be released per `refcount.h` rules.
- `flushParticleBatch()` releases `m_batchTexture`, but destructor cleanup should not rely on a render-path being invoked.
### Fix Focus Areas
- Add destructor cleanup for the new owned member (`m_batchTexture`) and reset batch state.
- file/path[start_line-end_line]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[47-90]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[209-216]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[441-478]
### Suggested change
In `~W3DParticleSystemManager()`, add e.g.:
- `REF_PTR_RELEASE(m_batchTexture);`
- `m_pointCount = 0;`
(Optionally do this before/after deleting `m_pointGroup`; either is fine since both are ref-counted owners.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Unconditional texture lookup ✓ Resolved 🐞 Bug ➹ Performance
Description
doParticles now calls Get_Texture() before it knows whether any particles survive culling
(m_pointCount==startCount), causing unnecessary texture-hash lookup and refcount inc/dec work for
systems that render nothing. This adds avoidable per-system overhead on the empty/fully-culled path.
Code

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[R195-199]

+		// TheSuperHackers @perf 09/08/2026 Ronin/Mauller 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 = !( m_streakLine && sys->isUsingStreak() ) && ( sys->getVolumeParticleDepth() <= 1 );
Evidence
The code fetches the texture (which includes an Add_Ref) before particle culling, and then
immediately releases it in the no-particles path, proving the extra work occurs even when nothing is
rendered.

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[195-206]
Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[285-289]
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp[157-181]
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp[221-229]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Get_Texture()` is invoked for every non-smudge particle system before culling determines whether the system contributes any renderable particles. For fully-culled/empty systems this work is wasted.
### Issue Context
`W3DAssetManager::Get_Texture()` performs a hash lookup and `Add_Ref()` before returning, so calling it for systems that end up not rendering does extra work and refcount churn.
### Fix Focus Areas
- Lazily acquire the texture only after the first particle passes culling (before writing into the batch buffers), so fully-culled systems never call `Get_Texture()`.
- Ensure batching flush/setup still happens before appending any particles into the shared buffers.
- file/path[start_line-end_line]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[195-218]
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[231-290]
- Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp[157-229]
### Suggested approach
- Initialize `TextureClass* texture = nullptr;` and postpone `Get_Texture()` until you encounter the first particle that passes the cull checks.
- At that moment, run the current batch-compatibility checks and potentially `flushParticleBatch(rinfo)` **before** writing that first particle into the buffers.
- If no particles pass culling, skip texture acquisition entirely.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
@stephanmeesters

Copy link
Copy Markdown

I think W3DParticleSys.h was supposed to have been moved to core in #3014 but wasn't...

Comment thread Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h Outdated
@Mauller

Mauller commented Aug 16, 2026

Copy link
Copy Markdown
Author

I think W3DParticleSys.h was supposed to have been moved to core in #3014 but wasn't...

Outside the scope of this change, but i noticed that too.

@Mauller

Mauller commented Aug 16, 2026

Copy link
Copy Markdown
Author

Made another modification to perform an earlier visibility check, it can help prevent non visible particle systems from causing a batch flush. It also saves a bit of overhead later on.

It can give a bit of extra perf due to this, around 2 - 5% more.
image

@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from 62747ca to 315c30c Compare August 16, 2026 09:26
@Mauller Mauller changed the title perf(particlesys): Batch same type particles to improve particle rendering performance by 15-20% perf(particlesys): Batch same type particles to improve particle rendering performance by 20 - 30% Aug 16, 2026
@Mauller Mauller changed the title perf(particlesys): Batch same type particles to improve particle rendering performance by 20 - 30% perf(particlesys): Batch same type particles to improve particle rendering performance by 15 - 30% Aug 16, 2026
@Mauller

Mauller commented Aug 16, 2026

Copy link
Copy Markdown
Author

Ah stupid VC6 loop handling, will just fixing now

@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from 315c30c to 0df2c07 Compare August 16, 2026 09:47
@Mauller

Mauller commented Aug 16, 2026

Copy link
Copy Markdown
Author

Fixed VC6 build and issues mentioned by the bot

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space

switch( sys->getShaderType() )
if ( sys->getVolumeParticleDepth() > 1 )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make things simpler/more consistent if volume particles were batched as well?

@Mauller Mauller Aug 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

volume particles work in a different way as they have multiple surfaces.
The batching only really works with billboarded / flat particles

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are they really that different though, they appear to use the same input arrays. The only difference is that they call a different render function and render more surfaces

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would need to find something that uses volume particles, all my current tests don't show any activity down that path

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found that the microwave tank uses them.

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from 0df2c07 to 0e8506d Compare August 16, 2026 14:06

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First review pass. Reference counting needs simplification.

if (sys->isUsingDrawables())
continue;

// TheSuperHackers @perf 16/08/2026 Mauller Test if particle system has any visible particles that can be drawn

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

date after author

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed and changes @Perf to @performance

const Coord3D* pos = vp->getPosition();
Real psize = vp->getSize();

//Test if particle is at the screen or terrain edges.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test exists more than once in this file. Can consolidate and simplify.

@Mauller Mauller Aug 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried by having the visibility test section put particles that are visible into a list, but the performance was lower than just testing again and running through all particles later on.

There is likely an element of memory locality to it which putting pointers to the particle system on a list loses.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by setting the culled variable on the particles, they always had the variable but it has not been used till now.


enum { MAX_POINTS_PER_GROUP = 512 };

TextureClass *m_batchTexture; ///< the texture used as the drawing surface for batched particle draws

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefCountPtr<TextureClass>

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
// TheSuperHackers @perf 09/08/2026 Ronin/Mauller 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() );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefCountPtr

Bool m_batchBillboard;
Bool m_batchParticleSystems;
ParticleSystemInfo::ParticleShaderType m_batchShaderType;
Int m_pointCount;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe can be unsigned

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made it unsigned and put it back within doParticles() outside of the particle system list loop

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

m_batchBillboard = true;
m_batchParticleSystems = true;
m_batchShaderType = ParticleSystemInfo::INVALID_SHADER;
m_pointCount = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_pointCount does not need to be a class member. Is used in one function. Can be passed as argument to the flush function. m_pointCount as class member also poses risk from early returns.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah when looking at it again i forgot that we flush the last batch anyway and don't batch between calls to doParticles()

going to make it a function member again.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function now does these culling tests 3 times. Can we optimize this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else i could try is adding a flag to the particle which the first visibility test sets to say if the particle is visible on screen.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed by setting and using the culled variable on the particles.

@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from 0e8506d to 4b5ec70 Compare August 16, 2026 17:45
@Mauller

Mauller commented Aug 16, 2026

Copy link
Copy Markdown
Author

Partially addressed review comments, things still appear to give a good 15 - 30% more performance with the tweaks.

bobtista added a commit to bobtista/GeneralsGameCode that referenced this pull request Aug 17, 2026
…visibility skip until the next upstream rebase
@Mauller

Mauller commented Aug 18, 2026

Copy link
Copy Markdown
Author

I am going to change this back to a draft for the moment as i have seen some other things to cleanup.

@Mauller
Mauller marked this pull request as draft August 18, 2026 07:03
@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch 3 times, most recently from 5fdf6ae to a764d03 Compare August 18, 2026 20:28
@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from b773d9e to 7655d62 Compare August 21, 2026 16:47
@Mauller
Mauller marked this pull request as ready for review August 22, 2026 07:04
@Mauller

Mauller commented Aug 22, 2026

Copy link
Copy Markdown
Author

Brought this back into review, is in a better place now.

Commits are split to aid reviewing since there are significant layout changes due to removing multiple levels of nesting.

Comment thread Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp Outdated
Comment on lines +84 to +87
if (m_batchTexture)
{
REF_PTR_RELEASE(m_batchTexture);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Refcountptr double release 🐞 Bug ☼ Reliability

The destructor calls REF_PTR_RELEASE(m_batchTexture) even though m_batchTexture is a
RefCountPtr<TextureClass>, causing Release_Ref() to be invoked twice (once via
x->Release_Ref() and once via x = nullptr which releases the current referent). This can delete
the texture early and then release a freed object (use-after-free) during shutdown/teardown.
Agent Prompt
### Issue description
`REF_PTR_RELEASE` is a macro intended for raw ref-counted pointers. Applying it to `RefCountPtr<T>` can double-release the referent because the macro calls `x->Release_Ref()` and then `x = nullptr`, and `RefCountPtr::operator=(nullptr)` also releases the current referent.

### Issue Context
`W3DParticleSystemManager::m_batchTexture` is declared as `RefCountPtr<TextureClass>`. It already releases in its destructor and also provides `Clear()` for explicit release.

### Fix Focus Areas
- Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp[73-88]
- Core/Libraries/Source/WWVegas/WWLib/refcount.h[63-65]
- Core/Libraries/Source/WWVegas/WWLib/ref_ptr.h[291-300]

### Expected change
Replace `REF_PTR_RELEASE(m_batchTexture)` with `m_batchTexture.Clear()` (or remove the explicit release entirely and rely on `RefCountPtr` destruction). Do not use `REF_PTR_RELEASE` on `RefCountPtr` variables.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed and updated

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7655d62

@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from 7655d62 to ab2d941 Compare August 22, 2026 07:10
@Mauller
Mauller force-pushed the Mauller/perf-batch-particle-draws branch from ab2d941 to 84a8d40 Compare August 22, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Performance Is a performance concern ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants