Hi @dark_sylinc,
I hit a case where a ParticleSystem2 simulates perfectly — correct world position, valid AABB, visibility flags matching the camera, getNumSimdActiveParticles() counting up as expected — but no draw call is ever submitted for it. A RenderDoc capture shows every other draw in the scene pass, and simply nothing for the particles.
The cause is an implicit coupling between ParticleSystemManager2 and ObjectMemoryManager.
In SceneManager::cullFrustum(), the render queue loop is bounded by the object memory manager:
Code: Select all
const size_t numRenderQueues = memoryManager->getNumRenderQueues();
size_t firstRq = std::min<size_t>( request.firstRq, numRenderQueues );
size_t lastRq = std::min<size_t>( request.lastRq, numRenderQueues );and the particle submission happens inside that loop:
Code: Select all
if( mRenderQueue->getRenderQueueMode( currRqId ) == RenderQueue::PARTICLE_SYSTEM &&
request.addToRenderQueue )
{
mParticleSystemManager2->_addToRenderQueue( threadIdx, mNumWorkerThreads,
mRenderQueue, currRqId, visibilityMask,
!request.casterPass );
}
ObjectMemoryManager::getNumRenderQueues() returns the highest slot actually occupied by a real MovableObject, plus one:
cpp
while( itor != endt )
{
if( itor->getUsedMemory() )
retVal = static_cast<size_t>( itor - begin );
++itor;
}
return retVal + 1;So if a ParticleSystemDef sits on, say, render queue 214, but no ordinary object occupies a slot at or above 214, the loop stops earlier and _addToRenderQueue is never called for 214. The particle system is configured correctly in every respect and still renders nothing.
This is easy to miss because it depends on unrelated scene content. In my case the editor build rendered the particles fine while the standalone game did not — same scene file, same code path. The difference was that the editor has gizmo geometry on render queue 220, which stretched the iterated range far enough to cover 214 by pure coincidence. Remove the selection, or run the game where no gizmos exist, and the particles vanish.
That did cost nerves. I searched for 3 days chasing this issue.
It also explains why kParticleSystemDefaultRenderQueueId = 15 works reliably in the samples: it is low enough to always fall inside the range occupied by ordinary objects.
My current workaround is to attach an invisible dummy Item to a high render queue purely to keep getNumRenderQueues() large enough. That works, but it means paying for the extra empty queue iterations every frame, and it is not something a user would ever guess.
Would it make sense to drive the particle submission from request.firstRq / request.lastRq — the range the compositor pass actually asked for — instead of from the object memory manager's occupancy? The particle definitions live in ParticleSystemManager2, not in ObjectMemoryManager, so bounding them by the latter seems accidental rather than intended.
Best Regards
Lax
