// // 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-2019 NVIDIA Corporation. All rights reserved. // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved. // Copyright (c) 2001-2004 NovodeX AG. All rights reserved. #include "common/PxProfileZone.h" #include "geometry/PxGeometryQuery.h" #include "NpBatchQuery.h" #include "NpReadCheck.h" #include "NpActor.h" #include "NpShapeManager.h" #include "NpScene.h" #include "PsAtomic.h" #include "PsFoundation.h" #include "PsUtilities.h" using namespace physx; using namespace Sq; using namespace Cm; #if !PX_P64_FAMILY PX_COMPILE_TIME_ASSERT(0==(sizeof(PxRaycastHit)& 0x0f)); PX_COMPILE_TIME_ASSERT(0==(sizeof(PxSweepHit)& 0x0f)); PX_COMPILE_TIME_ASSERT(0==(sizeof(PxOverlapHit)& 0x0f)); #endif #define CHECK_RUNNING(QueryMessage) \ if(Ps::atomicCompareExchange(&mBatchQueryIsRunning, -1, 0) == 1)\ {\ Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, QueryMessage); return;\ } NpBatchQuery::NpBatchQuery(NpScene& owner, const PxBatchQueryDesc& d) : mNpScene(&owner), mNbRaycasts(0), mNbOverlaps(0), mNbSweeps(0), mBatchQueryIsRunning(0), mDesc(d), mPrevOffset(PxU32(eTERMINAL)) { mHasMtdSweep = false; } NpBatchQuery::~NpBatchQuery() { } void NpBatchQuery::setUserMemory(const PxBatchQueryMemory& userMem) { if(Ps::atomicCompareExchange(&mBatchQueryIsRunning, 0, 0) != 0) { Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, "PxBatchQuery::setUserMemory: This batch is still executing, skipping setUserMemory"); return; } PxBatchQueryDesc& desc = getDesc(); desc.queryMemory = userMem; } const PxBatchQueryMemory& NpBatchQuery::getUserMemory() { return getDesc().queryMemory; } // ROS abbreviates Raycast/Overlap/Sweep struct QTypeROS { enum Enum { eRAYCAST = 0, eOVERLAP = 1, eSWEEP = 2 }; }; // AP: perhaps can be shared with some other code namespace physx { struct BatchStreamHeader { BatchStreamHeader( PxHitFlags aHitFlags, const PxQueryCache* aCache, const PxQueryFilterData& aFd, void* aUserData, PxU16 aMaxTouchHits, QTypeROS::Enum aHitTypeId) : hitFlags(aHitFlags), fd(aFd), userData(aUserData), cache(aCache), maxTouchHits(aMaxTouchHits), hitTypeId(char(aHitTypeId)) { nextQueryOffset = PxU32(NpBatchQuery::eTERMINAL); } BatchStreamHeader() {} // TODO: possibly maintain 3 separate offset lists in the same array for raycasts/overlaps/sweeps // offset of a 4-byte ptr to offset in the previously stored batch query in the stream, this is not a ptr because of reallocs PxU32 nextQueryOffset; PxHitFlags hitFlags; PxQueryFilterData fd; void* userData; const PxQueryCache* cache; PxU16 maxTouchHits; char hitTypeId; // Sq::QTypeROS }; } // namespace physx static void writeGeom(BatchQueryStream& stream, const PxGeometry& geom) { PxGeometryType::Enum geomType = geom.getType(); stream.write(PxU32(geomType)); switch (geomType) { case PxGeometryType::eCAPSULE: stream.write(static_cast(geom)); break; case PxGeometryType::eSPHERE: stream.write(static_cast(geom)); break; case PxGeometryType::eCONVEXMESH: stream.write(static_cast(geom)); break; case PxGeometryType::eBOX: stream.write(static_cast(geom)); break; case PxGeometryType::ePLANE: case PxGeometryType::eTRIANGLEMESH: case PxGeometryType::eHEIGHTFIELD: case PxGeometryType::eGEOMETRY_COUNT: case PxGeometryType::eINVALID: PX_ALWAYS_ASSERT_MESSAGE("Unsupported geometry type in writeGeom"); } } static PxGeometry* readGeom(BatchQueryStreamReader& reader) { PxU32 geomType = *reader.read(); PxGeometry* anyGeom = NULL; switch (geomType) { case PxGeometryType::eCAPSULE: anyGeom = reader.read(); break; case PxGeometryType::eSPHERE: anyGeom = reader.read(); break; case PxGeometryType::eCONVEXMESH: anyGeom = reader.read(); break; case PxGeometryType::eBOX: anyGeom = reader.read(); break; default: PX_ALWAYS_ASSERT_MESSAGE("Unsupported geometry type in readGeom"); } return anyGeom; } static void writeQueryInput(BatchQueryStream& stream, const MultiQueryInput& input) { stream.write(input); if (input.rayOrigin) stream.write(input.rayOrigin); if (input.unitDir) stream.write(input.unitDir); if (input.pose) stream.write(input.pose); if (input.geometry) writeGeom(stream, *input.geometry); } static MultiQueryInput* readQueryInput(BatchQueryStreamReader& stream) { MultiQueryInput* input = stream.read(); if (input->rayOrigin) input->rayOrigin = stream.read(); if (input->unitDir) input->unitDir = stream.read(); if (input->pose) input->pose = stream.read(); if (input->geometry) input->geometry = readGeom(stream); return input; } template void writeStatus(ResultType* aRes, const PxHitBuffer& hits, void* userData, bool overflow) { ResultType* res = aRes; res->userData = userData; res->block = hits.block; res->hasBlock = hits.hasBlock; res->nbTouches = hits.nbTouches; res->queryStatus = PxU8(overflow ? PxBatchQueryStatus::eOVERFLOW : PxBatchQueryStatus::eSUCCESS); res->touches = (overflow && res->nbTouches == 0) ? NULL : hits.touches; } void NpBatchQuery::resetResultBuffers() { for (PxU32 i = 0; i struct PxOverflowBuffer : PxHitBuffer { bool overflow; PxU32 saveNbTouches; HitType extraHit; HitType* saveTouches; bool processCalled; PxOverflowBuffer(HitType* hits, PxU32 count) : PxHitBuffer(hits, count), overflow(false), processCalled(false) { } virtual PxAgain processTouches(const HitType* /*hits*/, PxU32 /*count*/) { if (processCalled) return false; saveTouches = this->touches; saveNbTouches = this->nbTouches; processCalled = true; this->touches = &extraHit; this->maxNbTouches = 1; return true; } virtual void finalizeQuery() { if (processCalled) { overflow = (this->nbTouches > 0); this->nbTouches = saveNbTouches; this->touches = saveTouches; } } }; void NpBatchQuery::execute() { NP_READ_CHECK(mNpScene); if(mNbRaycasts) { PX_CHECK_AND_RETURN(mDesc.queryMemory.userRaycastResultBuffer!=NULL, "PxBatchQuery execute: userRaycastResultBuffer is NULL"); PX_CHECK_AND_RETURN(mDesc.queryMemory.raycastTouchBufferSize > 0 ? (mDesc.queryMemory.userRaycastTouchBuffer != NULL) : true, "PxBatchQuery execute: userRaycastTouchBuffer is NULL"); } if(mNbOverlaps) { PX_CHECK_AND_RETURN(mDesc.queryMemory.userOverlapResultBuffer!=NULL, "PxBatchQuery execute: userOverlapResultBuffer is NULL"); PX_CHECK_AND_RETURN(mDesc.queryMemory.overlapTouchBufferSize > 0 ? (mDesc.queryMemory.userOverlapTouchBuffer != NULL) : true, "PxBatchQuery execute: userOverlapTouchBuffer is NULL"); } if(mNbSweeps) { PX_CHECK_AND_RETURN(mDesc.queryMemory.userSweepResultBuffer!=NULL, "PxBatchQuery execute: userSweepResultBuffer is NULL"); PX_CHECK_AND_RETURN(mDesc.queryMemory.sweepTouchBufferSize > 0 ? (mDesc.queryMemory.userSweepTouchBuffer != NULL) : true, "PxBatchQuery execute: userSweepTouchBuffer is NULL"); } PX_SIMD_GUARD; PX_PROFILE_ZONE("BatchedSceneQuery.execute", mNpScene->getContextId()); PxI32 ret = Ps::atomicCompareExchange(&mBatchQueryIsRunning, 1, 0); if(ret == 1) { Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, "PxBatchQuery::execute: This batch is already executing"); return; } else if(ret == -1) { Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, "PxBatchQuery::execute: Another thread is still adding queries to this batch"); return; } resetResultBuffers(); // If PVD is connected and IS_PVD_SQ_ENABLED, record the offsets for queries in pvd buffers and run the queries on PPU bool isSqCollectorLocked = false; PX_UNUSED(isSqCollectorLocked); #if PX_SUPPORT_PVD PxU32 pvdRayQstartIdx = 0; PxU32 pvdOverlapQstartIdx = 0; PxU32 pvdSweepQstartIdx = 0; Vd::ScbScenePvdClient& pvdClient = mNpScene->mScene.getScenePvdClient(); const bool needUpdatePvd = pvdClient.checkPvdDebugFlag() && (pvdClient.getScenePvdFlagsFast() & PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES); if(needUpdatePvd) { mNpScene->getBatchedSqCollector().getLock().lock(); isSqCollectorLocked = true; pvdRayQstartIdx = mNpScene->getBatchedSqCollector().mAccumulatedRaycastQueries.size(); pvdOverlapQstartIdx = mNpScene->getBatchedSqCollector().mAccumulatedOverlapQueries.size(); pvdSweepQstartIdx = mNpScene->getBatchedSqCollector().mAccumulatedSweepQueries.size(); } #endif // setup local pointers to user provided output buffers PxRaycastHit* raycastHits = mDesc.queryMemory.userRaycastTouchBuffer; PX_UNUSED(raycastHits); PxRaycastQueryResult* raycastResults = mDesc.queryMemory.userRaycastResultBuffer; PX_UNUSED(raycastResults); PxU32 raycastHitsSize = mDesc.queryMemory.raycastTouchBufferSize; PX_UNUSED(raycastHitsSize); PxOverlapHit* overlapHits = mDesc.queryMemory.userOverlapTouchBuffer; PX_UNUSED(overlapHits); PxOverlapQueryResult* overlapResults = mDesc.queryMemory.userOverlapResultBuffer; PX_UNUSED(overlapResults); PxU32 overlapHitsSize = mDesc.queryMemory.overlapTouchBufferSize; PX_UNUSED(overlapHitsSize); PxSweepHit* sweepHits = mDesc.queryMemory.userSweepTouchBuffer; PX_UNUSED(sweepHits); PxSweepQueryResult* sweepResults = mDesc.queryMemory.userSweepResultBuffer; PX_UNUSED(sweepResults); PxU32 sweepHitsSize = mDesc.queryMemory.sweepTouchBufferSize; PX_UNUSED(sweepHitsSize); BatchQueryFilterData bfd(mDesc.filterShaderData, mDesc.filterShaderDataSize, mDesc.preFilterShader, mDesc.postFilterShader); // data declarations for double buffering the input stream PxU32 curQueryOffset = 0; // first query starts at 0 if (mPrevOffset == eTERMINAL) // except if zero queries were queued { finalizeExecute(); return; } PxU32 hitsSpaceLeft; PX_UNUSED(hitsSpaceLeft); // ====================== parse and execute the batch query memory stream ====================== PxU32 queryCount = 0; do { // parse a query from the input stream, create a stream reader at current double buffer BatchQueryStreamReader reader(mStream.begin()+curQueryOffset); BatchStreamHeader& h = *reader.read(); curQueryOffset = h.nextQueryOffset; Ps::prefetchLine(mStream.begin() + curQueryOffset); MultiQueryInput& input = *readQueryInput(reader); // ====================== switch over query type - QTypeROS::eRAYCAST, eOVERLAP, eSWEEP ===================== switch (h.hitTypeId) { // =============== Current query is a raycast ===================== case QTypeROS::eRAYCAST: { PxU32 nbRaycastHits = PxU32(raycastHits - mDesc.queryMemory.userRaycastTouchBuffer); PX_ASSERT(nbRaycastHits <= raycastHitsSize); hitsSpaceLeft = raycastHitsSize - nbRaycastHits; PxOverflowBuffer hits(raycastHits, PxMin(h.maxTouchHits, hitsSpaceLeft)); mNpScene->NpScene::multiQuery(input, hits, h.hitFlags, h.cache, h.fd, NULL, &bfd); hits.overflow |= (hitsSpaceLeft == 0 && h.maxTouchHits > 0); // report overflow if 0 space left and maxTouchHits>0 writeStatus(raycastResults++, hits, h.userData, hits.overflow); raycastHits += hits.nbTouches; } break; // ================ Current query is an overlap ==================== case QTypeROS::eOVERLAP: { PxU32 nbOverlapHits = PxU32(overlapHits - mDesc.queryMemory.userOverlapTouchBuffer); PX_ASSERT(nbOverlapHits <= overlapHitsSize); hitsSpaceLeft = overlapHitsSize - nbOverlapHits; PxOverflowBuffer hits(overlapHits, PxMin(h.maxTouchHits, hitsSpaceLeft)); mNpScene->NpScene::multiQuery(input, hits, h.hitFlags, h.cache, h.fd, NULL, &bfd); hits.overflow |= (hitsSpaceLeft == 0 && h.maxTouchHits > 0); // report overflow if 0 space left and maxTouchHits>0 writeStatus(overlapResults++, hits, h.userData, hits.overflow); overlapHits += hits.nbTouches; } break; // ================== Current query is a sweep ========================= case QTypeROS::eSWEEP: { PxU32 nbSweepHits = PxU32(sweepHits - mDesc.queryMemory.userSweepTouchBuffer); PX_ASSERT(nbSweepHits <= sweepHitsSize); hitsSpaceLeft = sweepHitsSize - nbSweepHits; PxOverflowBuffer hits(sweepHits, PxMin(h.maxTouchHits, hitsSpaceLeft)); mNpScene->NpScene::multiQuery(input, hits, h.hitFlags, h.cache, h.fd, NULL, &bfd); hits.overflow |= (hitsSpaceLeft == 0 && h.maxTouchHits > 0); // report overflow if 0 space left and maxTouchHits>0 writeStatus(sweepResults++, hits, h.userData, hits.overflow); sweepHits += hits.nbTouches; } break; default: PX_ALWAYS_ASSERT_MESSAGE("Unexpected batch query type (raycast/overlap/sweep)."); } if (h.nextQueryOffset == eTERMINAL) // end of stream // AP: previously also had a break on hitCount==-1 which is aborted due to out of space // abort stream parsing if we ran into an aborted query (hitCount==-1).. but it was easier to just continue // the perf implications for aborted queries are not a significant consideration and this allows to avoid // writing special case code for filling the query buffers after aborted query break; #undef MULTIQ queryCount++; } while (queryCount < 1000000); #if PX_SUPPORT_PVD if( isSqCollectorLocked && needUpdatePvd) { mNpScene->getBatchedSqCollector().collectAllBatchedHits( mDesc.queryMemory.userRaycastResultBuffer, mNbRaycasts, pvdRayQstartIdx, mDesc.queryMemory.userOverlapResultBuffer, mNbOverlaps, pvdOverlapQstartIdx, mDesc.queryMemory.userSweepResultBuffer, mNbSweeps, pvdSweepQstartIdx); } // Maybe connection is disconnected after the lock if( isSqCollectorLocked ) { mNpScene->getBatchedSqCollector().getLock().unlock(); isSqCollectorLocked = false; } #endif finalizeExecute(); } /////////////////////////////////////////////////////////////////////////////// void NpBatchQuery::writeBatchHeader(const BatchStreamHeader& h) { PxU32 streamPos = PxU32(mStream.getPos()); // save the stream pos before we write the header mStream.write(h); // link into a list as offset PxU32* prevPtr = (mPrevOffset == eTERMINAL) ? &mPrevOffset : reinterpret_cast(mStream.begin()+mPrevOffset); PxU32 headerPtr = streamPos+PX_OFFSET_OF(BatchStreamHeader, nextQueryOffset); *prevPtr = headerPtr; mPrevOffset = headerPtr; } /////////////////////////////////////////////////////////////////////////////// void NpBatchQuery::raycast( const PxVec3& origin, const PxVec3& unitDir, PxReal distance, PxU16 maxTouchHits, PxHitFlags hitFlags, const PxQueryFilterData& fd, void* userData, const PxQueryCache* cache) { PX_CHECK_AND_RETURN(distance>0, "PxBatchQuery::raycast: The maximum distance must be greater than zero!"); PX_CHECK_AND_RETURN(unitDir.isNormalized(), "PxBatchQuery::raycast: Direction must be normalized"); PX_CHECK_AND_RETURN(origin.isFinite(), "PxBatchQuery::raycast: origin is not valid"); if (mNbRaycasts >= mDesc.queryMemory.getMaxRaycastsPerExecute()) { PX_CHECK_AND_RETURN(mNbRaycasts < mDesc.queryMemory.getMaxRaycastsPerExecute(), "PxBatchQuery: number of raycast() calls exceeds PxBatchQueryMemory::raycastResultBufferSize, query discarded"); return; } CHECK_RUNNING("PxBatchQuery::raycast: This batch is still executing, skipping query."); mNbRaycasts++; writeBatchHeader(BatchStreamHeader(hitFlags, cache, fd, userData, maxTouchHits, QTypeROS::eRAYCAST)); writeQueryInput(mStream, MultiQueryInput(origin, unitDir, distance)); Ps::atomicExchange(&mBatchQueryIsRunning, 0); } /////////////////////////////////////////////////////////////////////////////// void NpBatchQuery::overlap( const PxGeometry& geometry, const PxTransform& pose, PxU16 maxTouchHits, const PxQueryFilterData& fd, void* userData, const PxQueryCache* cache) { PX_CHECK_AND_RETURN(pose.isValid(), "NpBatchQuery::overlapMultiple pose is not valid."); if (mNbOverlaps >= mDesc.queryMemory.getMaxOverlapsPerExecute()) { PX_CHECK_AND_RETURN(mNbOverlaps < mDesc.queryMemory.getMaxOverlapsPerExecute(), "PxBatchQuery: number of overlap() calls exceeds PxBatchQueryMemory::overlapResultBufferSize, query discarded"); return; } CHECK_RUNNING("PxBatchQuery::overlap: This batch is still executing, skipping query.") mNbOverlaps++; writeBatchHeader(BatchStreamHeader(PxHitFlags(), cache, fd, userData, maxTouchHits, QTypeROS::eOVERLAP)); writeQueryInput(mStream, MultiQueryInput(&geometry, &pose)); Ps::atomicExchange(&mBatchQueryIsRunning, 0); } void NpBatchQuery::sweep( const PxGeometry& geometry, const PxTransform& pose, const PxVec3& unitDir, const PxReal distance, PxU16 maxTouchHits, PxHitFlags hitFlags, const PxQueryFilterData& fd, void* userData, const PxQueryCache* cache, const PxReal inflation) { PX_CHECK_AND_RETURN(pose.isValid(), "Batch sweep input check: pose is not valid."); PX_CHECK_AND_RETURN(unitDir.isFinite(), "Batch sweep input check: unitDir is not valid."); PX_CHECK_AND_RETURN(unitDir.isNormalized(), "Batch sweep input check: direction must be normalized"); PX_CHECK_AND_RETURN(distance >= 0.0f, "Batch sweep input check: distance cannot be negative"); PX_CHECK_AND_RETURN(distance != 0.0f || !(hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP), "Batch sweep input check: zero-length sweep only valid without the PxHitFlag::eASSUME_NO_INITIAL_OVERLAP flag"); #if PX_CHECKED if(!PxGeometryQuery::isValid(geometry)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "Provided geometry is not valid"); return; } #endif // PX_CHECKED if (mNbSweeps >= mDesc.queryMemory.getMaxSweepsPerExecute()) { PX_CHECK_AND_RETURN(mNbSweeps < mDesc.queryMemory.getMaxSweepsPerExecute(), "PxBatchQuery: number of sweep() calls exceeds PxBatchQueryMemory::sweepResultBufferSize, query discarded"); return; } CHECK_RUNNING("PxBatchQuery::sweep: This batch is still executing, skipping query.") mNbSweeps++; writeBatchHeader(BatchStreamHeader(hitFlags, cache, fd, userData, maxTouchHits, QTypeROS::eSWEEP)); //set the MTD flag mHasMtdSweep |= !!(hitFlags & PxHitFlag::eMTD); if((hitFlags & PxHitFlag::ePRECISE_SWEEP) && (hitFlags & PxHitFlag::eMTD)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " Precise sweep doesn't support MTD. Perform MTD with default sweep"); hitFlags &= ~PxHitFlag::ePRECISE_SWEEP; } if((hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP) && (hitFlags & PxHitFlag::eMTD)) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " eMTD cannot be used in conjunction with eASSUME_NO_INITIAL_OVERLAP. eASSUME_NO_INITIAL_OVERLAP will be ignored"); hitFlags &= ~PxHitFlag::eASSUME_NO_INITIAL_OVERLAP; } PxReal realInflation = inflation; if((hitFlags & PxHitFlag::ePRECISE_SWEEP)&& inflation > 0.0f) { realInflation = 0.0f; Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, " Precise sweep doesn't support inflation, inflation will be overwritten to be zero"); } writeQueryInput(mStream, MultiQueryInput(&geometry, &pose, unitDir, distance, realInflation)); Ps::atomicExchange(&mBatchQueryIsRunning, 0); } void NpBatchQuery::release() { if(Ps::atomicCompareExchange(&mBatchQueryIsRunning, 0, 0) != 0) { Ps::getFoundation().error(PxErrorCode::eINVALID_OPERATION, __FILE__, __LINE__, "PxBatchQuery::release: This batch is still executing, skipping release"); return; } mNpScene->releaseBatchQuery(this); } PxBatchQueryPreFilterShader NpBatchQuery::getPreFilterShader() const { return mDesc.preFilterShader; } PxBatchQueryPostFilterShader NpBatchQuery::getPostFilterShader() const { return mDesc.postFilterShader; } const void* NpBatchQuery::getFilterShaderData() const { return mDesc.filterShaderData; } PxU32 NpBatchQuery::getFilterShaderDataSize() const { return mDesc.filterShaderDataSize; }