Files
XCEngine/engine/third_party/physx/source/scenequery/src/SqManager.cpp

578 lines
17 KiB
C++
Raw Normal View History

// 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-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
// PT: SQ-API LEVEL 2 (Level 1 = SqPruner.h)
// PT: this file is part of a "high-level" set of files within Sq. The SqPruner API doesn't rely on them.
// PT: this should really be at Np level but moving it to Sq allows us to share it.
#include "SqManager.h"
#include "GuSqInternal.h"
#include "GuBounds.h"
using namespace physx;
using namespace Sq;
using namespace Gu;
PrunerExt::PrunerExt() : mPruner(NULL), mDirtyList("SQmDirtyList"), mDirtyStatic(false)
{
}
PrunerExt::~PrunerExt()
{
PX_DELETE(mPruner);
}
void PrunerExt::init(Pruner* pruner)
{
mPruner = pruner;
}
void PrunerExt::preallocate(PxU32 nbShapes)
{
// if(nbShapes > mDirtyMap.size())
// mDirtyMap.resize(nbShapes);
if(mPruner)
mPruner->preallocate(nbShapes);
}
void PrunerExt::flushMemory()
{
if(!mDirtyList.size())
mDirtyList.reset();
// PT: TODO: flush bitmap here
// PT: TODO: flush pruner here?
}
void PrunerExt::addToDirtyList(PrunerHandle handle, bool dynamic, const PxTransform& transform)
{
if(mPruner)
mPruner->setTransform(handle, transform);
PxBitMap& dirtyMap = mDirtyMap;
{
if(dirtyMap.size() <= handle)
{
PxU32 size = PxMax<PxU32>(dirtyMap.size()*2, 1024);
const PxU32 minSize = handle+1;
if(minSize>size)
size = minSize*2;
dirtyMap.resize(size);
PX_ASSERT(handle<dirtyMap.size());
PX_ASSERT(!dirtyMap.test(handle));
}
}
if(!dirtyMap.test(handle))
{
dirtyMap.set(handle);
mDirtyList.pushBack(handle);
}
if(!dynamic)
mDirtyStatic = true;
}
void PrunerExt::removeFromDirtyList(PrunerHandle handle)
{
PxBitMap& dirtyMap = mDirtyMap;
// if(dirtyMap.test(handle))
if(dirtyMap.boundedTest(handle))
{
dirtyMap.reset(handle);
mDirtyList.findAndReplaceWithLast(handle);
}
// PT: if we remove the object that made us set mDirtyStatic to true, tough luck,
// we don't bother fixing that bool here. It's going to potentially cause an
// unnecessary update of the character controller's caches, which is not a big deal.
}
bool PrunerExt::processDirtyList(PxU32 index, const Adapter& adapter, float inflation)
{
const PxU32 numDirtyList = mDirtyList.size();
if(!numDirtyList)
return false;
const PrunerHandle* const prunerHandles = mDirtyList.begin();
for(PxU32 i=0; i<numDirtyList; i++)
{
const PrunerHandle handle = prunerHandles[i];
mDirtyMap.reset(handle);
// PT: we compute the new bounds and store them directly in the pruner structure to avoid copies. We delay the updateObjects() call
// to take advantage of batching.
PX_UNUSED(index);
PrunerPayloadData ppd;
const PrunerPayload& pp = mPruner->getPayloadData(handle, &ppd);
computeBounds(*ppd.mBounds, adapter.getGeometry(pp), *ppd.mTransform, 0.0f, inflation);
}
// PT: batch update happens after the loop instead of once per loop iteration
mPruner->updateObjects(prunerHandles, numDirtyList);
mDirtyList.clear();
const bool ret = mDirtyStatic;
mDirtyStatic = false;
return ret;
}
// PT: TODO: re-inline this
/*void PrunerExt::growDirtyList(PrunerHandle handle)
{
// pruners must either provide indices in order or reuse existing indices, so this 'if' is enough to ensure we have space for the new handle
// PT: TODO: fix this. There is just no need for any of it. The pruning pool itself could support the feature for free, similar to what we do
// in MBP. There would be no need for the bitmap or the dirty list array. However doing this through the virtual interface would be clumsy,
// adding the cost of virtual calls for very cheap & simple operations. It would be a lot easier to drop it and go back to what we had before.
PxBitMap& dirtyMap = mDirtyMap;
if(dirtyMap.size() <= handle)
dirtyMap.resize(PxMax<PxU32>(dirtyMap.size() * 2, 1024));
PX_ASSERT(handle<dirtyMap.size());
dirtyMap.reset(handle);
}*/
///////////////////////////////////////////////////////////////////////////////
CompoundPrunerExt::CompoundPrunerExt() :
mPruner (NULL)
{
}
CompoundPrunerExt::~CompoundPrunerExt()
{
PX_DELETE(mPruner);
}
void CompoundPrunerExt::preallocate(PxU32 nbShapes)
{
// if(nbShapes > mDirtyList.size())
// mDirtyList.reserve(nbShapes);
if(mPruner)
mPruner->preallocate(nbShapes);
}
void CompoundPrunerExt::flushMemory()
{
if(!mDirtyList.size())
mDirtyList.clear();
}
void CompoundPrunerExt::flushShapes(const Adapter& adapter, float inflation)
{
const PxU32 numDirtyList = mDirtyList.size();
if(!numDirtyList)
return;
const CompoundPair* const compoundPairs = mDirtyList.getEntries();
for(PxU32 i=0; i<numDirtyList; i++)
{
const PrunerHandle handle = compoundPairs[i].second;
const PrunerCompoundId compoundId = compoundPairs[i].first;
// PT: we compute the new bounds and store them directly in the pruner structure to avoid copies. We delay the updateObjects() call
// to take advantage of batching.
PrunerPayloadData ppd;
const PrunerPayload& pp = mPruner->getPayloadData(handle, compoundId, &ppd);
computeBounds(*ppd.mBounds, adapter.getGeometry(pp), *ppd.mTransform, 0.0f, inflation);
// A.B. not very effective, we might do better here
mPruner->updateObjectAfterManualBoundsUpdates(compoundId, handle);
}
mDirtyList.clear();
}
void CompoundPrunerExt::addToDirtyList(PrunerCompoundId compoundId, PrunerHandle handle, const PxTransform& transform)
{
if(mPruner)
mPruner->setTransform(handle, compoundId, transform);
mDirtyList.insert(CompoundPair(compoundId, handle));
}
void CompoundPrunerExt::removeFromDirtyList(PrunerCompoundId compoundId, PrunerHandle handle)
{
mDirtyList.erase(CompoundPair(compoundId, handle));
}
///////////////////////////////////////////////////////////////////////////////
#include "SqFactory.h"
#include "common/PxProfileZone.h"
#include "common/PxRenderBuffer.h"
#include "GuBVH.h"
#include "foundation/PxAlloca.h"
#include "PxSceneDesc.h" // PT: for PxSceneLimits TODO: remove
namespace
{
enum PxScenePrunerIndex
{
PX_SCENE_PRUNER_STATIC = 0,
PX_SCENE_PRUNER_DYNAMIC = 1,
PX_SCENE_COMPOUND_PRUNER = 0xffffffff,
};
}
PrunerManager::PrunerManager( PxU64 contextID, Pruner* staticPruner, Pruner* dynamicPruner,
PxU32 dynamicTreeRebuildRateHint, float inflation,
const PxSceneLimits& limits, const Adapter& adapter) :
mAdapter (adapter),
mContextID (contextID),
mStaticTimestamp (0),
mInflation (inflation)
{
mPrunerExt[PruningIndex::eSTATIC].init(staticPruner);
mPrunerExt[PruningIndex::eDYNAMIC].init(dynamicPruner);
setDynamicTreeRebuildRateHint(dynamicTreeRebuildRateHint);
mCompoundPrunerExt.mPruner = createCompoundPruner(contextID);
preallocate(PruningIndex::eSTATIC, limits.maxNbStaticShapes);
preallocate(PruningIndex::eDYNAMIC, limits.maxNbDynamicShapes);
preallocate(PxU32(PX_SCENE_COMPOUND_PRUNER), 32);
mPrunerNeedsUpdating = false;
}
PrunerManager::~PrunerManager()
{
}
void PrunerManager::preallocate(PxU32 prunerIndex, PxU32 nbShapes)
{
if(prunerIndex==PruningIndex::eSTATIC)
mPrunerExt[PruningIndex::eSTATIC].preallocate(nbShapes);
else if(prunerIndex==PruningIndex::eDYNAMIC)
mPrunerExt[PruningIndex::eDYNAMIC].preallocate(nbShapes);
else if(prunerIndex==PX_SCENE_COMPOUND_PRUNER)
mCompoundPrunerExt.preallocate(nbShapes);
}
void PrunerManager::flushMemory()
{
for(PxU32 i=0;i<PruningIndex::eCOUNT;i++)
mPrunerExt[i].flushMemory();
mCompoundPrunerExt.flushMemory();
}
PrunerData PrunerManager::addPrunerShape(const PrunerPayload& payload, bool dynamic, PrunerCompoundId compoundId, const PxBounds3& bounds, const PxTransform& transform, bool hasPruningStructure)
{
mPrunerNeedsUpdating = true;
const PxU32 index = PxU32(dynamic);
if(!index)
invalidateStaticTimestamp();
PrunerHandle handle;
if(compoundId == INVALID_COMPOUND_ID)
{
PX_ASSERT(mPrunerExt[index].pruner());
mPrunerExt[index].pruner()->addObjects(&handle, &bounds, &payload, &transform, 1, hasPruningStructure);
//mPrunerExt[index].growDirtyList(handle);
}
else
{
PX_ASSERT(mCompoundPrunerExt.pruner());
mCompoundPrunerExt.pruner()->addObject(compoundId, handle, bounds, payload, transform);
}
return createPrunerData(index, handle);
}
void PrunerManager::removePrunerShape(PrunerCompoundId compoundId, PrunerData data, PrunerPayloadRemovalCallback* removalCallback)
{
mPrunerNeedsUpdating = true;
const PxU32 index = getPrunerIndex(data);
const PrunerHandle handle = getPrunerHandle(data);
if(!index)
invalidateStaticTimestamp();
if(compoundId == INVALID_COMPOUND_ID)
{
PX_ASSERT(mPrunerExt[index].pruner());
mPrunerExt[index].removeFromDirtyList(handle);
mPrunerExt[index].pruner()->removeObjects(&handle, 1, removalCallback);
}
else
{
mCompoundPrunerExt.removeFromDirtyList(compoundId, handle);
mCompoundPrunerExt.pruner()->removeObject(compoundId, handle, removalCallback);
}
}
void PrunerManager::markForUpdate(PrunerCompoundId compoundId, PrunerData data, const PxTransform& transform)
{
mPrunerNeedsUpdating = true;
const PxU32 index = getPrunerIndex(data);
const PrunerHandle handle = getPrunerHandle(data);
if(!index)
invalidateStaticTimestamp();
if(compoundId == INVALID_COMPOUND_ID)
// PT: TODO: at this point do we still need a dirty list? we could just update the bounds directly?
mPrunerExt[index].addToDirtyList(handle, index!=0, transform);
else
mCompoundPrunerExt.addToDirtyList(compoundId, handle, transform);
}
void PrunerManager::setDynamicTreeRebuildRateHint(PxU32 rebuildRateHint)
{
mRebuildRateHint = rebuildRateHint;
for(PxU32 i=0;i<PruningIndex::eCOUNT;i++)
{
Pruner* pruner = mPrunerExt[i].pruner();
if(pruner && pruner->isDynamic())
static_cast<DynamicPruner*>(pruner)->setRebuildRateHint(rebuildRateHint);
}
}
void PrunerManager::afterSync(bool buildStep, bool commit)
{
PX_PROFILE_ZONE("Sim.sceneQueryBuildStep", mContextID);
if(!buildStep && !commit)
{
mPrunerNeedsUpdating = true;
return;
}
// flush user modified objects
flushShapes();
for(PxU32 i=0; i<PruningIndex::eCOUNT; i++)
{
Pruner* pruner = mPrunerExt[i].pruner();
if(pruner)
{
if(pruner->isDynamic())
static_cast<DynamicPruner*>(pruner)->buildStep(true);
if(commit)
pruner->commit();
}
}
mPrunerNeedsUpdating = !commit;
}
void PrunerManager::flushShapes()
{
PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
// must already have acquired writer lock here
const float inflation = 1.0f + mInflation;
bool mustInvalidateStaticTimestamp = false;
for(PxU32 i=0; i<PruningIndex::eCOUNT; i++)
{
if(mPrunerExt[i].processDirtyList(i, mAdapter, inflation))
mustInvalidateStaticTimestamp = true;
}
if(mustInvalidateStaticTimestamp)
invalidateStaticTimestamp();
mCompoundPrunerExt.flushShapes(mAdapter, inflation);
}
void PrunerManager::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();
for(PxU32 i=0; i<PruningIndex::eCOUNT; i++)
if(mPrunerExt[i].pruner())
mPrunerExt[i].pruner()->commit();
PxMemoryBarrier();
mPrunerNeedsUpdating = false;
}
mSQLock.unlock();
}
}
void PrunerManager::forceRebuildDynamicTree(PxU32 prunerIndex)
{
PX_PROFILE_ZONE("SceneQuery.forceDynamicTreeRebuild", mContextID);
PxMutex::ScopedLock lock(mSQLock);
Pruner* pruner = mPrunerExt[prunerIndex].pruner();
if(pruner && pruner->isDynamic())
{
static_cast<DynamicPruner*>(pruner)->purge();
static_cast<DynamicPruner*>(pruner)->commit();
}
}
void* PrunerManager::prepareSceneQueriesUpdate(PruningIndex::Enum index)
{
bool retVal = false;
Pruner* pruner = mPrunerExt[index].pruner();
if(pruner && pruner->isDynamic())
retVal = static_cast<DynamicPruner*>(pruner)->prepareBuild();
return retVal ? pruner : NULL;
}
void PrunerManager::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;
}
}
// PT: TODO: revisit this. Perhaps it should be the user's responsibility to call the pruner's
// visualize functions directly, when & how he wants.
void PrunerManager::visualize(PxU32 prunerIndex, PxRenderOutput& out) const
{
if(prunerIndex==PX_SCENE_PRUNER_STATIC)
{
if(getPruner(PruningIndex::eSTATIC))
getPruner(PruningIndex::eSTATIC)->visualize(out, SQ_DEBUG_VIZ_STATIC_COLOR, SQ_DEBUG_VIZ_STATIC_COLOR2);
}
else if(prunerIndex==PX_SCENE_PRUNER_DYNAMIC)
{
if(getPruner(PruningIndex::eDYNAMIC))
getPruner(PruningIndex::eDYNAMIC)->visualize(out, SQ_DEBUG_VIZ_DYNAMIC_COLOR, SQ_DEBUG_VIZ_DYNAMIC_COLOR2);
}
else if(prunerIndex==PX_SCENE_COMPOUND_PRUNER)
{
const CompoundPruner* cp = mCompoundPrunerExt.pruner();
if(cp)
cp->visualizeEx(out, SQ_DEBUG_VIZ_COMPOUND_COLOR, true, true);
}
}
void PrunerManager::shiftOrigin(const PxVec3& shift)
{
for(PxU32 i=0; i<PruningIndex::eCOUNT; i++)
mPrunerExt[i].pruner()->shiftOrigin(shift);
mCompoundPrunerExt.pruner()->shiftOrigin(shift);
}
void PrunerManager::addCompoundShape(const PxBVH& pxbvh, PrunerCompoundId compoundId, const PxTransform& compoundTransform, PrunerData* prunerData, const PrunerPayload* payloads, const PxTransform* transforms, bool isDynamic)
{
const BVH& bvh = static_cast<const BVH&>(pxbvh);
const PxU32 nbShapes = bvh.Gu::BVH::getNbBounds();
PX_ALLOCA(res, PrunerHandle, nbShapes);
PX_ASSERT(mCompoundPrunerExt.mPruner);
mCompoundPrunerExt.mPruner->addCompound(res, bvh, compoundId, compoundTransform, isDynamic, payloads, transforms);
const PxU32 index = PxU32(isDynamic);
if(!index)
invalidateStaticTimestamp();
for(PxU32 i = 0; i < nbShapes; i++)
prunerData[i] = createPrunerData(index, res[i]);
}
void PrunerManager::updateCompoundActor(PrunerCompoundId compoundId, const PxTransform& compoundTransform)
{
PX_ASSERT(mCompoundPrunerExt.mPruner);
const bool isDynamic = mCompoundPrunerExt.mPruner->updateCompound(compoundId, compoundTransform);
if(!isDynamic)
invalidateStaticTimestamp();
}
void PrunerManager::removeCompoundActor(PrunerCompoundId compoundId, PrunerPayloadRemovalCallback* removalCallback)
{
PX_ASSERT(mCompoundPrunerExt.mPruner);
const bool isDynamic = mCompoundPrunerExt.mPruner->removeCompound(compoundId, removalCallback);
if(!isDynamic)
invalidateStaticTimestamp();
}
void PrunerManager::sync(const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* bounds, const PxTransform32* transforms, PxU32 count, const PxBitMap& ignoredIndices)
{
if(!count)
return;
Pruner* dynamicPruner = getPruner(PruningIndex::eDYNAMIC);
if(!dynamicPruner)
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]))
{
dynamicPruner->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"
}
dynamicPruner->updateObjects(handles + startIndex, numIndices, mInflation, boundsIndices + startIndex, bounds, transforms);
}