Revision control
+ // Redistribution and use in source and binary forms, with or without
+ // modification, are permitted provided that the following conditions
+ // are met:
+ // * Redistributions of source code must retain the above copyright
+ // notice, this list of conditions and the following disclaimer.
+ // * Redistributions in binary form must reproduce the above copyright
+ // notice, this list of conditions and the following disclaimer in the
+ // documentation and/or other materials provided with the distribution.
+ // * Neither the name of NVIDIA CORPORATION nor the names of its
+ // contributors may be used to endorse or promote products derived
+ // from this software without specific prior written permission.
+ //
+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
+ // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ //
+ // Copyright (c) 2008-2026 NVIDIA Corporation. All rights reserved.
+ // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
+ // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
+
+ #ifndef BP_AABBMANAGER_BASE_H
+ #define BP_AABBMANAGER_BASE_H
+
+ #include "foundation/PxAllocator.h"
+ #include "CmPinnableArray.h"
+ #include "CmPinnableBitMap.h"
+ #include "foundation/PxSList.h"
+ #include "foundation/PxBitUtils.h"
+ #include "foundation/PxSimpleTypes.h"
+ #include "BpVolumeData.h"
+ #include "BpBroadPhaseUpdate.h"
+ #include "GuBounds.h"
+ #include "PxFiltering.h"
+ #include "PxAggregate.h"
+
+ namespace physx
+ {
+ class PxcScratchAllocator;
+ class PxRenderOutput;
+ class PxBaseTask;
+
+ namespace Cm
+ {
+ class FlushPool;
+ }
+
+ namespace Bp
+ {
+ typedef PxU32 BoundsIndex;
+ //typedef PxU32 ActorHandle;
+
+ /**
+ \brief Changes to the configuration of overlap pairs are reported as void* pairs.
+ \note Each void* in the pair corresponds to the void* passed to AABBManager::createVolume.
+ \see AABBManager::createVolume, AABBManager::getCreatedOverlaps, AABBManager::getDestroyedOverlaps
+ */
+ struct AABBOverlap
+ {
+ PX_FORCE_INLINE AABBOverlap() {}
+ PX_FORCE_INLINE AABBOverlap(void* userData0, void* userData1/*, ActorHandle pairHandle*/) : mUserData0(userData0), mUserData1(userData1)/*, mPairHandle(pairHandle)*/
+ {
+ // PT: TODO: why is this forbidden?
+ PX_ASSERT(userData0 != userData1);
+ }
+
+ // PT: these will eventually be the userData pointers passed to addBounds(), i.e. Sc::ElementSim pointers in PhysX. This may not be
+ // necessary at all, since in the current design the bounds indices themselves come from BP clients (they're not handles managed by the BP).
+ // So there's a 1:1 mapping between bounds-indices (which are effectively edlement IDs in PhysX) and the userData pointers (Sc::ElementSim).
+ // Thus we could just return bounds indices directly to users - at least in the context of PhysX, maybe the standalone BP is different.
+ void* mUserData0;
+ void* mUserData1;
+
+ // PT: TODO: not sure what happened there but mPairUserData is not used inside the BP itself so we need to revisit this.
+ /* union
+ {
+ ActorHandle mPairHandle; //For created pairs, this is the index into the pair in the pair manager
+ void* mUserData; //For deleted pairs, this is the user data written by the application to the pair
+ };*/
+ void* mPairUserData; //For deleted pairs, this is the user data written by the application to the pair
+ };
+
+ struct BpCacheData : public PxSListEntry
+ {
+ PxArray<AABBOverlap> mCreatedPairs[2];
+ PxArray<AABBOverlap> mDeletedPairs[2];
+
+ void reset()
+ {
+ mCreatedPairs[0].resizeUninitialized(0);
+ mCreatedPairs[1].resizeUninitialized(0);
+ mDeletedPairs[0].resizeUninitialized(0);
+ mDeletedPairs[1].resizeUninitialized(0);
+ }
+ };
+
+ class BoundsArray : public PxUserAllocated
+ {
+ PX_NOCOPY(BoundsArray)
+ public:
+ BoundsArray(Cm::VirtualAllocatorCallback& allocator,
+ PxI32 group = 0,
+ Cm::PinnableAllocatorFallback::Enum fallback = Cm::PinnableAllocatorFallback::eENABLED)
+ :
+ mBounds(allocator, group, fallback),
+ mHasAnythingChanged(true), //needs to be set explicitly for PxgBounds first copy
+ mAllocFailed(false)
+ {
+ }
+
+ virtual ~BoundsArray(){}
+
+ PX_FORCE_INLINE bool initEntry(PxU32 index)
+ {
+ index++; // PT: always pretend we need one more entry, to make sure reading the last used entry will be SIMD-safe.
+ const PxU32 oldCapacity = mBounds.capacity();
+ if (index >= oldCapacity)
+ {
+ const PxU32 newCapacity = PxNextPowerOfTwo(index);
+ return resize(newCapacity);
+ }
+ return true;
+ }
+
+ virtual void updateBounds(const PxTransform& transform, const PxGeometry& geom, PxU32 index, PxU32 /*indexFrom*/)
+ {
+ Gu::computeBounds(mBounds[index], geom, transform, 0.0f, 1.0f);
+ mHasAnythingChanged = true;
+ }
+
+ virtual void setBounds(const PxBounds3& bounds, PxU32 index)
+ {
+ // PX_CHECK_AND_RETURN(bounds.isValid() && !bounds.isEmpty(), "BoundsArray::setBounds - illegal bounds\n");
+ mBounds[index] = bounds;
+ mHasAnythingChanged = true;
+ }
+
+ PX_FORCE_INLINE const PxBounds3* begin() const { return mBounds.begin(); }
+ PX_FORCE_INLINE PxBounds3* begin() { return mBounds.begin(); }
+ PX_FORCE_INLINE const PxBounds3* getBounds() const { return mBounds.begin(); }
+ PX_FORCE_INLINE PxBounds3* getBounds() { return mBounds.begin(); }
+ PX_FORCE_INLINE const PxBounds3& getBounds(PxU32 index) const { return mBounds[index]; }
+ PX_FORCE_INLINE PxU32 size() const { return mBounds.size(); }
+ PX_FORCE_INLINE bool hasChanged() const { return mHasAnythingChanged; }
+ PX_FORCE_INLINE void resetChangedState() { mHasAnythingChanged = false; }
+ PX_FORCE_INLINE void setChangedState() { mHasAnythingChanged = true; }
+ PX_FORCE_INLINE bool hadAllocationFailure() const { return mAllocFailed; }
+
+ void shiftOrigin(const PxVec3& shift)
+ {
+ // we shift some potential NaNs here because we don't know what's active, but should be harmless
+ const PxU32 nbBounds = mBounds.size();
+ for(PxU32 i=0; i<nbBounds; i++)
+ {
+ mBounds[i].minimum -= shift;
+ mBounds[i].maximum -= shift;
+ }
+ mHasAnythingChanged = true;
+ }
+ protected:
+
+ virtual bool resize(PxU32 size)
+ {
+ bool allocOk = mBounds.reserve(size);
+ //The base class resize is always supposed to succeed, because mBounds allocator is either
+ //configured for pageable allocation, or pinned allocation with pageable fallback
+ //(enableFallback passed in ctor is true).
+ PX_UNUSED(allocOk);
+ PX_ASSERT(allocOk);
+ mBounds.forceSize_Unsafe(size);
+ return true;
+ }
+
+ Cm::PinnableArray<PxBounds3> mBounds;
+ bool mHasAnythingChanged;
+ bool mAllocFailed;
+ };
+
+ /**
+ \brief A structure responsible for:
+ * storing an aabb representation for each active shape in the related scene
+ * managing the creation/removal of aabb representations when their related shapes are created/removed
+ * updating all aabbs that require an update due to modification of shape geometry or transform
+ * updating the aabb of all aggregates from the union of the aabbs of all shapes that make up each aggregate
+ * computing and reporting the incremental changes to the set of overlapping aabb pairs
+ */
+ class AABBManagerBase : public PxUserAllocated
+ {
+ PX_NOCOPY(AABBManagerBase)
+ public:
+ AABBManagerBase(BroadPhase& bp, BoundsArray& boundsArray, Cm::PinnableArray<PxReal>& contactDistance,
+ PxU32 maxNbAggregates, PxU32 maxNbShapes, Cm::VirtualAllocatorCallback& allocator, PxU64 contextID,
+ PxPairFilteringMode::Enum kineKineFilteringMode, PxPairFilteringMode::Enum staticKineFilteringMode);
+
+ virtual ~AABBManagerBase() {}
+
+ virtual void destroy() = 0;
+
+ virtual AggregateHandle createAggregate(BoundsIndex index, Bp::FilterGroup::Enum group, void* userData, PxU32 maxNumShapes, PxAggregateFilterHint filterHint, PxU32 envID) = 0;
+ virtual bool destroyAggregate(BoundsIndex& index, Bp::FilterGroup::Enum& group, AggregateHandle aggregateHandle) = 0;
+
+ virtual bool addBounds(BoundsIndex index, PxReal contactDistance, Bp::FilterGroup::Enum group, void* userdata, AggregateHandle aggregateHandle, ElementType::Enum volumeType, PxU32 envID) = 0;
+ virtual bool removeBounds(BoundsIndex index) = 0;
+
+ void reserveSpaceForBounds(BoundsIndex index);
+
+ PX_FORCE_INLINE PxIntBool isMarkedForRemove(BoundsIndex index) const { return mRemovedHandleMap.boundedTest(index); }
+ // PX_FORCE_INLINE PxIntBool isMarkedForAdd(BoundsIndex index) const { return mAddedHandleMap.boundedTest(index); }
+ PX_FORCE_INLINE BroadPhase* getBroadPhase() const { return &mBroadPhase; }
+ PX_FORCE_INLINE BoundsArray& getBoundsArray() { return mBoundsArray; }
+ PX_FORCE_INLINE PxU32 getNbActiveAggregates() const { return mNbAggregates; }
+ PX_FORCE_INLINE const float* getContactDistances() const { return mContactDistance.begin(); }
+ PX_FORCE_INLINE Cm::PinnableBitMap& getChangedAABBMgActorHandleMap() { return mChangedHandleMap; }
+ PX_FORCE_INLINE void* getUserData(const BoundsIndex index) const { return (index<mVolumeData.size()) ? mVolumeData[index].getUserData() : NULL; }
+
+ void setContactDistance(BoundsIndex handle, PxReal offset)
+ {
+ // PT: this works even for aggregated shapes, since the corresponding bit will also be set in the 'updated' map.
+ mContactDistance.begin()[handle] = offset;
+ setPersistentStateChanged();
+ mChangedHandleMap.growAndSet(handle);
+ }
+
+ void setBPGroup(BoundsIndex index, Bp::FilterGroup::Enum group)
+ {
+ PX_ASSERT((index + 1) < mVolumeData.size());
+ PX_ASSERT(group != Bp::FilterGroup::eINVALID); // PT: we use group == Bp::FilterGroup::eINVALID to mark removed/invalid entries
+ mGroups[index] = group;
+ }
+
+ virtual void updateBPFirstPass(PxU32 numCpuTasks, Cm::FlushPool& flushPool, bool hasContactDistanceUpdated, PxBaseTask* continuation) = 0;
+ virtual void updateBPSecondPass(PxcScratchAllocator* scratchAllocator, PxBaseTask* continuation) = 0;
+
+ virtual void postBroadPhase(PxBaseTask*, Cm::FlushPool& flushPool) = 0;
+ virtual void reallocateChangedAABBMgActorHandleMap(const PxU32 size) = 0;
+
+ AABBOverlap* getCreatedOverlaps(ElementType::Enum type, PxU32& count)
+ {
+ PX_ASSERT(type < ElementType::eCOUNT);
+ count = mCreatedOverlaps[type].size();
+ return mCreatedOverlaps[type].begin();
+ }
+
+ AABBOverlap* getDestroyedOverlaps(ElementType::Enum type, PxU32& count)
+ {
+ PX_ASSERT(type < ElementType::eCOUNT);
+ count = mDestroyedOverlaps[type].size();
+ return mDestroyedOverlaps[type].begin();
+ }
+
+ void freeBuffers();
+
+ struct OutOfBoundsData
+ {
+ PxU32 mNbOutOfBoundsObjects;
+ PxU32 mNbOutOfBoundsAggregates;
+ void** mOutOfBoundsObjects;
+ void** mOutOfBoundsAggregates;
+ };
+ virtual bool getOutOfBoundsObjects(OutOfBoundsData&) { return false; }
+ virtual void clearOutOfBoundsObjects() {}
+
+ void shiftOrigin(const PxVec3& shift);
+
+ virtual void visualize(PxRenderOutput& out) = 0;
+
+ virtual void releaseDeferredAggregateIds() = 0;
+ virtual void setGPUStateChanged() {}
+ virtual void setPersistentStateChanged() {}
+
+ #if PX_ENABLE_SIM_STATS
+ PxU32 getGpuDynamicsLostFoundPairsStats() { return mGpuDynamicsLostFoundPairsStats; }
+ PxU32 getGpuDynamicsTotalAggregatePairsStats() { return mGpuDynamicsTotalAggregatePairsStats; }
+ PxU32 getGpuDynamicsLostFoundAggregatePairsStats() { return mGpuDynamicsLostFoundAggregatePairsStats; }
+ #else
+ PX_CATCH_UNDEFINED_ENABLE_SIM_STATS
+ #endif
+
+
+ protected:
+ void reserveShapeSpace(PxU32 nbShapes);
+
+ // PT: we have bitmaps here probably to quickly handle added/removed objects during same frame.
+ // PT: TODO: consider replacing with plain arrays (easier to parse, already existing below, etc)
+ Cm::PinnableBitMap mAddedHandleMap; // PT: indexed by BoundsIndex
+ Cm::PinnableBitMap mRemovedHandleMap; // PT: indexed by BoundsIndex
+ Cm::PinnableBitMap mChangedHandleMap;
+
+ //Returns true if the bounds was pending insert, false otherwise
+ PX_FORCE_INLINE bool removeBPEntry(BoundsIndex index) // PT: only for objects passed to the BP
+ {
+ if (mAddedHandleMap.test(index)) // PT: if object had been added this frame...
+ {
+ mAddedHandleMap.reset(index); // PT: ...then simply revert the previous operation locally (it hasn't been passed to the BP yet).
+ return true;
+ }
+ else
+ mRemovedHandleMap.set(index); // PT: else we need to remove it from the BP
+ return false;
+ }
+
+ PX_FORCE_INLINE void addBPEntry(BoundsIndex index)
+ {
+ if (mRemovedHandleMap.test(index))
+ mRemovedHandleMap.reset(index);
+ else
+ mAddedHandleMap.set(index);
+ }
+
+ //ML: we create mGroups and mContactDistance in the AABBManager constructor. PxArray will take Cm::VirtualAllocatorCallback as a parameter. Therefore, if GPU BP is using,
+ //we will passed a pinned host memory allocator, otherwise, we will just pass a normal allocator.
+ Cm::PinnableArray<Bp::FilterGroup::Enum> mGroups; // NOTE: we stick Bp::FilterGroup::eINVALID in this slot to indicate that the entry is invalid (removed or never inserted.)
+ Cm::PinnableArray<PxU32> mEnvIDs; // PT: should ideally be in the GPU class
+ Cm::PinnableArray<PxReal>& mContactDistance;
+ Cm::PinnableArray<VolumeData> mVolumeData;
+ BpFilter mFilters;
+
+ PX_FORCE_INLINE void initEntry(BoundsIndex index, PxReal contactDistance, Bp::FilterGroup::Enum group, void* userData)
+ {
+ if ((index + 1) >= mVolumeData.size())
+ reserveShapeSpace(index + 1);
+
+ // PT: TODO: why is this needed at all? Why aren't size() and capacity() enough?
+ mUsedSize = PxMax(index + 1, mUsedSize);
+
+ PX_ASSERT(group != Bp::FilterGroup::eINVALID); // PT: we use group == Bp::FilterGroup::eINVALID to mark removed/invalid entries
+ mGroups[index] = group;
+ mContactDistance.begin()[index] = contactDistance;
+ mVolumeData[index].setUserData(userData);
+ }
+
+ PX_FORCE_INLINE void initEntry(BoundsIndex index, PxReal contactDistance, Bp::FilterGroup::Enum group, void* userData, ElementType::Enum volumeType)
+ {
+ initEntry(index, contactDistance, group, userData);
+ mVolumeData[index].setVolumeType(volumeType); // PT: must be done after setUserData
+ }
+
+ PX_FORCE_INLINE void resetEntry(BoundsIndex index)
+ {
+ mGroups[index] = Bp::FilterGroup::eINVALID;
+ mContactDistance.begin()[index] = 0.0f;
+ mVolumeData[index].reset();
+
+ if(index<mEnvIDs.size())
+ mEnvIDs[index] = PX_INVALID_U32;
+ }
+
+ // PT: TODO: remove confusion between BoundsIndex and ShapeHandle here!
+ Cm::PinnableArray<ShapeHandle> mAddedHandles;
+ Cm::PinnableArray<ShapeHandle> mUpdatedHandles; // PT: TODO: only on CPU
+ Cm::PinnableArray<ShapeHandle> mRemovedHandles;
+
+ BroadPhase& mBroadPhase;
+ BoundsArray& mBoundsArray;
+
+ PxArray<AABBOverlap> mCreatedOverlaps[ElementType::eCOUNT];
+ PxArray<AABBOverlap> mDestroyedOverlaps[ElementType::eCOUNT];
+
+ PxU32 mUsedSize; // highest used value + 1
+ PxU32 mNbAggregates;
+
+ #if PX_ENABLE_SIM_STATS
+ PxU32 mGpuDynamicsLostFoundPairsStats;
+ PxU32 mGpuDynamicsTotalAggregatePairsStats;
+ PxU32 mGpuDynamicsLostFoundAggregatePairsStats;
+ #else
+ PX_CATCH_UNDEFINED_ENABLE_SIM_STATS
+ #endif
+
+ #if BP_USE_AGGREGATE_GROUP_TAIL
+ // PT: TODO: even in the 3.4 trunk this stuff is a clumsy mess: groups are "BpHandle" suddenly passed
+ // to BroadPhaseUpdateData as "ShapeHandle".
+ //Free aggregate group ids.
+ PxU32 mAggregateGroupTide;
+ PxArray<Bp::FilterGroup::Enum> mFreeAggregateGroups; // PT: TODO: remove this useless array
+ #endif
+ PxU64 mContextID;
+ bool mOriginShifted;
+
+ #if BP_USE_AGGREGATE_GROUP_TAIL
+ PX_FORCE_INLINE void releaseAggregateGroup(const Bp::FilterGroup::Enum group)
+ {
+ PX_ASSERT(group != Bp::FilterGroup::eINVALID);
+ mFreeAggregateGroups.pushBack(group);
+ }
+
+ PX_FORCE_INLINE Bp::FilterGroup::Enum getAggregateGroup()
+ {
+ PxU32 id;
+ if (mFreeAggregateGroups.size())
+ id = mFreeAggregateGroups.popBack();
+ else
+ {
+ id = mAggregateGroupTide--;
+ id <<= BP_FILTERING_TYPE_SHIFT_BIT;
+ id |= FilterType::AGGREGATE;
+ }
+ const Bp::FilterGroup::Enum group = Bp::FilterGroup::Enum(id);
+ PX_ASSERT(group != Bp::FilterGroup::eINVALID);
+ return group;
+ }
+ #endif
+ };
+
+ } //namespace Bp
+ } //namespace physx
+
+ #endif //BP_AABBMANAGER_BASE_H