Revision control

++ // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
++ // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
++ // SPDX-FileCopyrightText: Copyright (c) 2008-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
++ // SPDX-License-Identifier: Apache-2.0
++
++ #include "ExtSqManager.h"
++ #define SQ_DEBUG_VIZ_STATIC_COLOR PxU32(PxDebugColor::eARGB_BLUE)
++ #define SQ_DEBUG_VIZ_DYNAMIC_COLOR PxU32(PxDebugColor::eARGB_RED)
++ #define SQ_DEBUG_VIZ_STATIC_COLOR2 PxU32(PxDebugColor::eARGB_DARKBLUE)
++ #define SQ_DEBUG_VIZ_DYNAMIC_COLOR2 PxU32(PxDebugColor::eARGB_DARKRED)
++ #define SQ_DEBUG_VIZ_COMPOUND_COLOR PxU32(PxDebugColor::eARGB_MAGENTA)
++ #include "GuBounds.h"
++
++ using namespace physx;
++ using namespace Sq;
++ using namespace Gu;
++
++ ///////////////////////////////////////////////////////////////////////////////
++
++ #include "SqFactory.h"
++ #include "common/PxProfileZone.h"
++ #include "common/PxRenderBuffer.h"
++ #include "GuBVH.h"
++ #include "foundation/PxAlloca.h"
++
++ // PT: this is a customized version of physx::Sq::PrunerManager that supports more than 2 hardcoded pruners.
++ // It might not be possible to support the whole PxSceneQuerySystem API with an arbitrary number of pruners.
++
++ ExtPrunerManager::ExtPrunerManager(PxU64 contextID, float inflation, const Adapter& adapter, bool usesTreeOfPruners) :
++ mAdapter (adapter),
++ mTreeOfPruners (NULL),
++ mContextID (contextID),
++ mStaticTimestamp (0),
++ mRebuildRateHint (100),
++ mInflation (inflation),
++ mPrunerNeedsUpdating (false),
++ mTimestampNeedsUpdating (false),
++ mUsesTreeOfPruners (usesTreeOfPruners)
++ {
++ mCompoundPrunerExt.mPruner = createCompoundPruner(contextID);
++ }
++
++ ExtPrunerManager::~ExtPrunerManager()
++ {
++ PX_DELETE(mTreeOfPruners);
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ PX_DELETE(pe);
++ }
++ }
++
++ PxU32 ExtPrunerManager::addPruner(Pruner* pruner, PxU32 preallocated)
++ {
++ const PxU32 index = mPrunerExt.size();
++ PrunerExt* pe = PX_NEW(PrunerExt);
++ pe->init(pruner);
++ if(preallocated)
++ pe->preallocate(preallocated);
++ mPrunerExt.pushBack(pe);
++ return index;
++ }
++
++ void ExtPrunerManager::preallocate(PxU32 prunerIndex, PxU32 nbShapes)
++ {
++ const bool preallocateCompoundPruner = prunerIndex==0xffffffff;
++ if(preallocateCompoundPruner)
++ {
++ mCompoundPrunerExt.preallocate(nbShapes);
++ }
++ else
++ {
++ if(prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ mPrunerExt[prunerIndex]->preallocate(nbShapes);
++ }
++ }
++
++ void ExtPrunerManager::flushMemory()
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ pe->flushMemory();
++ }
++
++ mCompoundPrunerExt.flushMemory();
++ }
++
++ PrunerHandle ExtPrunerManager::addPrunerShape(const PrunerPayload& payload, PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, const PxBounds3& bounds, const PxTransform& transform, bool hasPruningStructure)
++ {
++ if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return INVALID_PRUNERHANDLE;
++ }
++
++ mPrunerNeedsUpdating = true;
++
++ if(!dynamic)
++ invalidateStaticTimestamp();
++
++ PrunerHandle handle;
++ if(compoundId == INVALID_COMPOUND_ID)
++ {
++ PX_ASSERT(prunerIndex<mPrunerExt.size());
++ PX_ASSERT(mPrunerExt[prunerIndex]->pruner());
++ mPrunerExt[prunerIndex]->pruner()->addObjects(&handle, &bounds, &payload, &transform, 1, hasPruningStructure);
++ //mPrunerExt[prunerIndex].growDirtyList(handle);
++ }
++ else
++ {
++ PX_ASSERT(mCompoundPrunerExt.pruner());
++ mCompoundPrunerExt.pruner()->addObject(compoundId, handle, bounds, payload, transform);
++ }
++
++ return handle;
++ }
++
++ void ExtPrunerManager::removePrunerShape(PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, PrunerHandle shapeHandle, PrunerPayloadRemovalCallback* removalCallback)
++ {
++ if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ mPrunerNeedsUpdating = true;
++
++ if(!dynamic)
++ invalidateStaticTimestamp();
++
++ if(compoundId == INVALID_COMPOUND_ID)
++ {
++ PX_ASSERT(prunerIndex<mPrunerExt.size());
++ PX_ASSERT(mPrunerExt[prunerIndex]->pruner());
++
++ mPrunerExt[prunerIndex]->removeFromDirtyList(shapeHandle);
++ mPrunerExt[prunerIndex]->pruner()->removeObjects(&shapeHandle, 1, removalCallback);
++ }
++ else
++ {
++ mCompoundPrunerExt.removeFromDirtyList(compoundId, shapeHandle);
++ mCompoundPrunerExt.pruner()->removeObject(compoundId, shapeHandle, removalCallback);
++ }
++ }
++
++ void ExtPrunerManager::markForUpdate(PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, PrunerHandle shapeHandle, const PxTransform& transform)
++ {
++ if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ mPrunerNeedsUpdating = true;
++
++ if(!dynamic)
++ invalidateStaticTimestamp();
++
++ if(compoundId == INVALID_COMPOUND_ID)
++ {
++ PX_ASSERT(prunerIndex<mPrunerExt.size());
++ PX_ASSERT(mPrunerExt[prunerIndex]->pruner());
++
++ // PT: TODO: at this point do we still need a dirty list? we could just update the bounds directly?
++ mPrunerExt[prunerIndex]->addToDirtyList(shapeHandle, dynamic, transform);
++ }
++ else
++ mCompoundPrunerExt.addToDirtyList(compoundId, shapeHandle, transform);
++ }
++
++ void ExtPrunerManager::setDynamicTreeRebuildRateHint(PxU32 rebuildRateHint)
++ {
++ mRebuildRateHint = rebuildRateHint;
++
++ // PT: we are still using the same rebuild hint for all pruners here, which may or may
++ // not make sense. We could also have a different build rate for each pruner.
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ Pruner* pruner = pe->pruner();
++ if(pruner && pruner->isDynamic())
++ static_cast<DynamicPruner*>(pruner)->setRebuildRateHint(rebuildRateHint);
++ }
++ }
++
++ void ExtPrunerManager::afterSync(bool buildStep, bool commit)
++ {
++ PX_PROFILE_ZONE("Sim.sceneQueryBuildStep", mContextID);
++
++ if(!buildStep && !commit)
++ {
++ mPrunerNeedsUpdating = true;
++ return;
++ }
++
++ // flush user modified objects
++ flushShapes();
++
++ // PT: TODO: with N pruners this part would be worth multi-threading
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ Pruner* pruner = pe->pruner();
++ if(pruner)
++ {
++ if(pruner->isDynamic())
++ static_cast<DynamicPruner*>(pruner)->buildStep(true);
++
++ if(commit)
++ pruner->commit();
++ }
++ }
++
++ if(commit)
++ {
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++ }
++
++ mPrunerNeedsUpdating = !commit;
++ }
++
++ void ExtPrunerManager::flushShapes()
++ {
++ PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
++
++ // must already have acquired writer lock here
++
++ const float inflation = 1.0f + mInflation;
++
++ bool mustInvalidateStaticTimestamp = false;
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ if(pe->processDirtyList(i, mAdapter, inflation))
++ mustInvalidateStaticTimestamp = true;
++ }
++
++ if(mustInvalidateStaticTimestamp)
++ invalidateStaticTimestamp();
++
++ mCompoundPrunerExt.flushShapes(mAdapter, inflation);
++ }
++
++ void ExtPrunerManager::flushUpdates()
++ {
++ PX_PROFILE_ZONE("SceneQuery.flushUpdates", mContextID);
++
++ if(mPrunerNeedsUpdating)
++ {
++ // no need to take lock if manual sq update is enabled
++ // as flushUpdates will only be called from NpScene::flushQueryUpdates()
++ mSQLock.lock();
++
++ if(mPrunerNeedsUpdating)
++ {
++ flushShapes();
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ if(pe->pruner())
++ pe->pruner()->commit();
++ }
++
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++
++ PxMemoryBarrier();
++ mPrunerNeedsUpdating = false;
++ }
++ mSQLock.unlock();
++ }
++ }
++
++ void ExtPrunerManager::forceRebuildDynamicTree(PxU32 prunerIndex)
++ {
++ // PT: beware here when called from the PxScene, the prunerIndex may not match
++
++ PX_PROFILE_ZONE("SceneQuery.forceDynamicTreeRebuild", mContextID);
++
++ if(prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ PxMutex::ScopedLock lock(mSQLock);
++
++ PrunerExt* pe = mPrunerExt[prunerIndex];
++ Pruner* pruner = pe->pruner();
++
++ if(pruner && pruner->isDynamic())
++ {
++ static_cast<DynamicPruner*>(pruner)->purge();
++ static_cast<DynamicPruner*>(pruner)->commit();
++ }
++ }
++
++ void* ExtPrunerManager::prepareSceneQueriesUpdate(PxU32 prunerIndex)
++ {
++ // PT: beware here when called from the PxScene, the prunerIndex may not match
++
++ if(prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return NULL;
++ }
++
++ PX_ASSERT(mPrunerExt[prunerIndex]->pruner());
++
++ bool retVal = false;
++ Pruner* pruner = mPrunerExt[prunerIndex]->pruner();
++ if(pruner && pruner->isDynamic())
++ retVal = static_cast<DynamicPruner*>(pruner)->prepareBuild();
++
++ return retVal ? pruner : NULL;
++ }
++
++ void ExtPrunerManager::sceneQueryBuildStep(void* handle)
++ {
++ PX_PROFILE_ZONE("SceneQuery.sceneQueryBuildStep", mContextID);
++
++ Pruner* pruner = reinterpret_cast<Pruner*>(handle);
++
++ if(pruner && pruner->isDynamic())
++ {
++ const bool buildFinished = static_cast<DynamicPruner*>(pruner)->buildStep(false);
++ if(buildFinished)
++ mPrunerNeedsUpdating = true;
++ }
++ }
++
++ void ExtPrunerManager::visualize(PxU32 prunerIndex, PxRenderOutput& out) const
++ {
++ // PT: beware here when called from the PxScene, the prunerIndex may not match
++ // This is quite awkward and should be improved.
++
++ // PT: problem here is that this function will be called by the regular PhysX scene when plugged to its PxSceneDesc,
++ // and the calling code only understands the regular PhysX pruners.
++
++ const bool visualizeCompoundPruner = prunerIndex==0xffffffff;
++ if(visualizeCompoundPruner)
++ {
++ const CompoundPruner* cp = mCompoundPrunerExt.pruner();
++ if(cp)
++ cp->visualizeEx(out, SQ_DEBUG_VIZ_COMPOUND_COLOR, true, true);
++ }
++ else
++ {
++ if(prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ PrunerExt* pe = mPrunerExt[prunerIndex];
++ Pruner* pruner = pe->pruner();
++ if(pruner)
++ {
++ // PT: TODO: this doesn't really work: the static color was for static shapes but they
++ // could still be stored in a dynamic pruner. So this code doesn't use a color scheme
++ // consistent with what we use in the default code.
++ if(pruner->isDynamic())
++ {
++ //if(visDynamic)
++ pruner->visualize(out, SQ_DEBUG_VIZ_DYNAMIC_COLOR, SQ_DEBUG_VIZ_DYNAMIC_COLOR2);
++ }
++ else
++ {
++ //if(visStatic)
++ pruner->visualize(out, SQ_DEBUG_VIZ_STATIC_COLOR, SQ_DEBUG_VIZ_STATIC_COLOR2);
++ }
++ }
++ }
++ }
++
++ void ExtPrunerManager::shiftOrigin(const PxVec3& shift)
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ Pruner* pruner = pe->pruner();
++ if(pruner)
++ pruner->shiftOrigin(shift);
++ }
++
++ mCompoundPrunerExt.pruner()->shiftOrigin(shift);
++ }
++
++ void ExtPrunerManager::addCompoundShape(const PxBVH& pxbvh, PrunerCompoundId compoundId, const PxTransform& compoundTransform, PrunerHandle* prunerHandle, const PrunerPayload* payloads, const PxTransform* transforms, bool isDynamic)
++ {
++ const Gu::BVH& bvh = static_cast<const Gu::BVH&>(pxbvh);
++
++ PX_ASSERT(mCompoundPrunerExt.mPruner);
++ mCompoundPrunerExt.mPruner->addCompound(prunerHandle, bvh, compoundId, compoundTransform, isDynamic, payloads, transforms);
++ if(!isDynamic)
++ invalidateStaticTimestamp();
++ }
++
++ void ExtPrunerManager::updateCompoundActor(PrunerCompoundId compoundId, const PxTransform& compoundTransform)
++ {
++ PX_ASSERT(mCompoundPrunerExt.mPruner);
++ const bool isDynamic = mCompoundPrunerExt.mPruner->updateCompound(compoundId, compoundTransform);
++ if(!isDynamic)
++ invalidateStaticTimestamp();
++ }
++
++ void ExtPrunerManager::removeCompoundActor(PrunerCompoundId compoundId, PrunerPayloadRemovalCallback* removalCallback)
++ {
++ PX_ASSERT(mCompoundPrunerExt.mPruner);
++ const bool isDynamic = mCompoundPrunerExt.mPruner->removeCompound(compoundId, removalCallback);
++ if(!isDynamic)
++ invalidateStaticTimestamp();
++ }
++
++ void ExtPrunerManager::sync(PxU32 prunerIndex, const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* bounds, const PxTransform32* transforms, PxU32 count, const PxBitMap& ignoredIndices)
++ {
++ if(!count)
++ return;
++
++ if(prunerIndex>=mPrunerExt.size())
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return;
++ }
++
++ Pruner* pruner = getPruner(prunerIndex);
++ if(!pruner)
++ return;
++
++ PxU32 startIndex = 0;
++ PxU32 numIndices = count;
++
++ // if shape sim map is not empty, parse the indices and skip update for the dirty one
++ if(ignoredIndices.count())
++ {
++ // PT: I think this codepath was used with SCB / buffered changes, but it's not needed anymore
++ numIndices = 0;
++
++ for(PxU32 i=0; i<count; i++)
++ {
++ // if(ignoredIndices.test(boundsIndices[i]))
++ if(ignoredIndices.boundedTest(boundsIndices[i]))
++ {
++ pruner->updateObjects(handles + startIndex, numIndices, mInflation, boundsIndices + startIndex, bounds, transforms);
++ numIndices = 0;
++ startIndex = i + 1;
++ }
++ else
++ numIndices++;
++ }
++ // PT: we fallback to the next line on purpose - no "else"
++ }
++
++ pruner->updateObjects(handles + startIndex, numIndices, mInflation, boundsIndices + startIndex, bounds, transforms);
++ }
++
++ PxU32 ExtPrunerManager::startCustomBuildstep()
++ {
++ PX_PROFILE_ZONE("SceneQuery.startCustomBuildstep", mContextID);
++
++ // flush user modified objects
++ //flushShapes();
++ {
++ PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
++ // PT: only flush the compound pruner synchronously
++ // must already have acquired writer lock here
++ const float inflation = 1.0f + mInflation;
++ mCompoundPrunerExt.flushShapes(mAdapter, inflation);
++ }
++
++ mTimestampNeedsUpdating = false;
++
++ return mPrunerExt.size();
++ }
++
++ void ExtPrunerManager::customBuildstep(PxU32 index)
++ {
++ PX_PROFILE_ZONE("SceneQuery.customBuildstep", mContextID);
++
++ PX_ASSERT(index<mPrunerExt.size());
++
++ PrunerExt* pe = mPrunerExt[index];
++ Pruner* pruner = pe->pruner();
++
++ //void ExtPrunerManager::flushShapes()
++ {
++ PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
++ // must already have acquired writer lock here
++ const float inflation = 1.0f + mInflation;
++ if(pe->processDirtyList(index, mAdapter, inflation))
++ mTimestampNeedsUpdating = true;
++ }
++
++ if(pruner)
++ {
++ if(pruner->isDynamic())
++ static_cast<DynamicPruner*>(pruner)->buildStep(true); // PT: "true" because that parameter was made for PxSceneQuerySystem::sceneQueryBuildStep(), not us
++
++ pruner->commit();
++ }
++ }
++
++ void ExtPrunerManager::finishCustomBuildstep()
++ {
++ PX_PROFILE_ZONE("SceneQuery.finishCustomBuildstep", mContextID);
++
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++
++ mPrunerNeedsUpdating = false;
++ if(mTimestampNeedsUpdating)
++ invalidateStaticTimestamp();
++ }
++
++ void ExtPrunerManager::createTreeOfPruners()
++ {
++ PX_PROFILE_ZONE("SceneQuery.createTreeOfPruners", mContextID);
++
++ PX_DELETE(mTreeOfPruners);
++
++ mTreeOfPruners = PX_NEW(BVH)(NULL);
++
++ const PxU32 nb = mPrunerExt.size();
++
++ PxBounds3* prunerBounds = reinterpret_cast<PxBounds3*>(PxAlloca(sizeof(PxBounds3)*(nb+1)));
++
++ PxU32 nbBounds = 0;
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ Pruner* pruner = pe->pruner();
++ if(pruner)
++ pruner->getGlobalBounds(prunerBounds[nbBounds++]);
++ }
++
++ mTreeOfPruners->init(nbBounds, NULL, prunerBounds, sizeof(PxBounds3), BVH_SPLATTER_POINTS, 1, 0.01f);
++ }