17#include "moc_qgschunkedentity.cpp"
19#include <QElapsedTimer>
35static float screenSpaceError(
const QgsAABB &nodeBbox,
float nodeError,
const QgsChunkedEntity::SceneContext &sceneContext )
49static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
51 for (
int i = 0; i < node->childCount(); ++i )
53 QgsChunkNode *child = node->children()[i];
54 if ( child->entity() && activeNodes.contains( child ) )
56 if ( hasAnyActiveChildren( child, activeNodes ) )
63QgsChunkedEntity::QgsChunkedEntity(
Qgs3DMapSettings *mapSettings,
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
64 : Qgs3DMapSceneEntity( mapSettings, parent )
66 , mChunkLoaderFactory( loaderFactory )
67 , mOwnsFactory( ownsFactory )
68 , mPrimitivesBudget( primitiveBudget )
70 mRootNode = loaderFactory->createRootNode();
71 mChunkLoaderQueue =
new QgsChunkList;
72 mReplacementQueue =
new QgsChunkList;
75 connect( loaderFactory, &QgsChunkLoaderFactory::childrenPrepared,
this, [
this]
77 setNeedsUpdate(
true );
78 emit pendingJobsCountChanged();
83QgsChunkedEntity::~QgsChunkedEntity()
88 Q_ASSERT( mActiveJobs.isEmpty() );
91 while ( !mChunkLoaderQueue->isEmpty() )
93 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
94 QgsChunkNode *node = entry->chunk;
96 if ( node->state() == QgsChunkNode::QueuedForLoad )
97 node->cancelQueuedForLoad();
98 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
99 node->cancelQueuedForUpdate();
104 delete mChunkLoaderQueue;
106 while ( !mReplacementQueue->isEmpty() )
108 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
111 entry->chunk->unloadChunk();
114 delete mReplacementQueue;
119 delete mChunkLoaderFactory;
124void QgsChunkedEntity::handleSceneUpdate(
const SceneContext &sceneContext )
134 pruneLoaderQueue( sceneContext );
139 int oldJobsCount = pendingJobsCount();
141 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
142 mActiveNodes.clear();
144 mCurrentTime = QTime::currentTime();
146 update( mRootNode, sceneContext );
149 int enabled = 0, disabled = 0, unloaded = 0;
152 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
154 if ( activeBefore.contains( node ) )
156 activeBefore.remove( node );
160 if ( !node->entity() )
162 QgsDebugError(
"Active node has null entity - this should never happen!" );
165 node->entity()->setEnabled(
true );
173 for ( QgsChunkNode *node : activeBefore )
175 if ( !node->entity() )
177 QgsDebugError(
"Active node has null entity - this should never happen!" );
180 node->entity()->setEnabled(
false );
189 unloaded = unloadNodes();
196 QList<QgsAABB> bboxes;
197 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
198 bboxes <<
Qgs3DUtils::mapToWorldExtent( n->box3D(), mMapSettings->origin() );
199 mBboxesEntity->setBoxes( bboxes );
205 mNeedsUpdate =
false;
207 if ( pendingJobsCount() != oldJobsCount )
208 emit pendingJobsCountChanged();
210 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
213 .arg( mFrustumCulled )
214 .arg( mChunkLoaderQueue->count() )
215 .arg( mReplacementQueue->count() )
217 .arg( t.elapsed() ), 2 );
221int QgsChunkedEntity::unloadNodes()
224 if ( usedGpuMemory <= mGpuMemoryLimit )
226 setHasReachedGpuMemoryLimit(
false );
230 QgsDebugMsgLevel( QStringLiteral(
"Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
236 QgsChunkListEntry *entry = mReplacementQueue->last();
237 while ( entry && usedGpuMemory > mGpuMemoryLimit )
243 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
245 QgsChunkListEntry *entryPrev = entry->prev;
246 mReplacementQueue->takeEntry( entry );
248 mActiveNodes.removeOne( entry->chunk );
249 entry->chunk->unloadChunk();
259 if ( usedGpuMemory > mGpuMemoryLimit )
261 setHasReachedGpuMemoryLimit(
true );
262 QgsDebugMsgLevel( QStringLiteral(
"Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
269QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
271 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
276 if ( activeEntityNodes.empty() )
277 activeEntityNodes << rootNode();
282 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
290 fnear = std::min( fnear, bboxfnear );
291 ffar = std::max( ffar, bboxffar );
296void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
298 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
303 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
307 mBboxesEntity->deleteLater();
308 mBboxesEntity =
nullptr;
312void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
314 for ( QgsChunkNode *node : nodes )
316 if ( node->state() == QgsChunkNode::QueuedForUpdate )
318 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
319 node->cancelQueuedForUpdate();
321 else if ( node->state() == QgsChunkNode::Updating )
323 cancelActiveJob( node->updater() );
326 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
328 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
329 node->setQueuedForUpdate( entry, updateJobFactory );
330 mChunkLoaderQueue->insertLast( entry );
337void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
339 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
344 QgsChunkListEntry *e = mChunkLoaderQueue->first();
347 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
351 toRemoveFromLoaderQueue.append( e->chunk );
357 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
359 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
360 if ( n->state() == QgsChunkNode::QueuedForLoad )
362 n->cancelQueuedForLoad();
366 n->cancelQueuedForUpdate();
367 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
372 if ( !toRemoveFromLoaderQueue.isEmpty() )
374 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
379int QgsChunkedEntity::pendingJobsCount()
const
381 return mChunkLoaderQueue->count() + mActiveJobs.count();
384struct ResidencyRequest
386 QgsChunkNode *node =
nullptr;
389 ResidencyRequest() =
default;
402 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
404 if ( request.level == otherRequest.level )
405 return request.dist > otherRequest.dist;
406 return request.level > otherRequest.level;
408} ResidencyRequestSorter;
410void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
412 QSet<QgsChunkNode *> nodes;
413 QVector<ResidencyRequest> residencyRequests;
415 using slotItem = std::pair<QgsChunkNode *, float>;
416 auto cmp_funct = [](
const slotItem & p1,
const slotItem & p2 )
418 return p1.second <= p2.second;
420 int renderedCount = 0;
421 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
423 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
424 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
426 slotItem s = pq.top();
428 QgsChunkNode *node = s.first;
438 if ( !node->hasChildrenPopulated() )
448 if ( mChunkLoaderFactory->canCreateChildren( node ) )
450 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
454 mChunkLoaderFactory->prepareChildren( node );
460 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
461 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
463 if ( !node->entity() && node->hasData() )
468 bool becomesActive =
false;
471 if ( node->childCount() == 0 )
475 becomesActive =
true;
477 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
480 becomesActive =
true;
494 becomesActive =
true;
496 QgsChunkNode *
const *children = node->children();
497 for (
int i = 0; i < node->childCount(); ++i )
500 if ( children[i]->entity() || !children[i]->hasData() )
503 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
511 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
512 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
521 if ( node->allChildChunksResident( mCurrentTime ) )
523 QgsChunkNode *
const *children = node->children();
524 for (
int i = 0; i < node->childCount(); ++i )
527 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
532 becomesActive =
true;
534 QgsChunkNode *
const *children = node->children();
535 for (
int i = 0; i < node->childCount(); ++i )
538 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
539 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
545 if ( becomesActive && node->entity() )
547 mActiveNodes << node;
551 nodes.remove( node->parent() );
552 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
554 renderedCount += mChunkLoaderFactory->primitivesCount( node );
555 nodes.insert( node );
560 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
561 for (
const auto &request : residencyRequests )
562 requestResidency( request.node );
565void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
567 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
569 Q_ASSERT( node->replacementQueueEntry() );
570 Q_ASSERT( node->entity() );
571 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
572 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
574 else if ( node->state() == QgsChunkNode::QueuedForLoad )
577 Q_ASSERT( node->loaderQueueEntry() );
578 Q_ASSERT( !node->loader() );
579 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
581 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
582 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
585 else if ( node->state() == QgsChunkNode::Loading )
589 else if ( node->state() == QgsChunkNode::Skeleton )
591 if ( !node->hasData() )
595 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
596 node->setQueuedForLoad( entry );
597 mChunkLoaderQueue->insertFirst( entry );
600 Q_ASSERT(
false &&
"impossible!" );
604void QgsChunkedEntity::onActiveJobFinished()
606 int oldJobsCount = pendingJobsCount();
608 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
610 Q_ASSERT( mActiveJobs.contains( job ) );
612 QgsChunkNode *node = job->chunk();
614 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
616 Q_ASSERT( node->state() == QgsChunkNode::Loading );
617 Q_ASSERT( node->loader() == loader );
619 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
620 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
622 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
624 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
634 mActiveNodes << node;
637 node->setLoaded( entity );
639 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
641 emit newEntityCreated( entity );
645 node->setHasData(
false );
646 node->cancelLoading();
654 Q_ASSERT( node->state() == QgsChunkNode::Updating );
655 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
660 mActiveJobs.removeOne( job );
666 if ( pendingJobsCount() != oldJobsCount )
667 emit pendingJobsCountChanged();
670void QgsChunkedEntity::startJobs()
672 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
674 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
676 QgsChunkNode *node = entry->chunk;
679 QgsChunkQueueJob *job = startJob( node );
680 mActiveJobs.append( job );
684QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
686 if ( node->state() == QgsChunkNode::QueuedForLoad )
688 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
689 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
691 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
692 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
693 node->setLoading( loader );
696 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
698 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
701 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
702 return node->updater();
711void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
715 QgsChunkNode *node = job->chunk();
716 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
718 if ( qobject_cast<QgsChunkLoader *>( job ) )
721 node->cancelLoading();
723 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
724 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
729 node->cancelUpdating();
731 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
735 mActiveJobs.removeOne( job );
739void QgsChunkedEntity::cancelActiveJobs()
741 while ( !mActiveJobs.isEmpty() )
743 cancelActiveJob( mActiveJobs.takeFirst() );
752 return QVector<QgsRayCastingUtils::RayHit>();
@ Additive
When tile is refined its content should be used alongside its children simultaneously.
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
static double calculateEntityGpuMemorySize(Qt3DCore::QEntity *entity)
Calculates approximate usage of GPU memory by an entity.
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume.
static float screenSpaceError(float epsilon, float distance, int screenSize, float fov)
This routine approximately calculates how an error (epsilon) of an object in world coordinates at giv...
static void computeBoundingBoxNearFarPlanes(const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar)
This routine computes nearPlane farPlane from the closest and farthest corners point of bounding box ...
QVector3D center() const
Returns coordinates of the center of the box.
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
A template based class for storing ranges (lower to upper values).
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
Helper struct to store ray casting parameters.