18#include <QElapsedTimer>
34static float screenSpaceError( QgsChunkNode *node,
const QgsChunkedEntity::SceneContext &sceneContext )
36 if ( node->error() <= 0 )
39 float dist = node->bbox().distanceFromPoint( sceneContext.cameraPos );
48static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
50 for (
int i = 0; i < node->childCount(); ++i )
52 QgsChunkNode *child = node->children()[i];
53 if ( child->entity() && activeNodes.contains( child ) )
55 if ( hasAnyActiveChildren( child, activeNodes ) )
62QgsChunkedEntity::QgsChunkedEntity(
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
63 : Qgs3DMapSceneEntity( parent )
65 , mChunkLoaderFactory( loaderFactory )
66 , mOwnsFactory( ownsFactory )
67 , mPrimitivesBudget( primitiveBudget )
69 mRootNode = loaderFactory->createRootNode();
70 mChunkLoaderQueue =
new QgsChunkList;
71 mReplacementQueue =
new QgsChunkList;
74 connect( loaderFactory, &QgsChunkLoaderFactory::childrenPrepared,
this, [
this]
76 setNeedsUpdate(
true );
77 emit pendingJobsCountChanged();
82QgsChunkedEntity::~QgsChunkedEntity()
87 Q_ASSERT( mActiveJobs.isEmpty() );
90 while ( !mChunkLoaderQueue->isEmpty() )
92 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
93 QgsChunkNode *node = entry->chunk;
95 if ( node->state() == QgsChunkNode::QueuedForLoad )
96 node->cancelQueuedForLoad();
97 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
98 node->cancelQueuedForUpdate();
103 delete mChunkLoaderQueue;
105 while ( !mReplacementQueue->isEmpty() )
107 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
110 entry->chunk->unloadChunk();
113 delete mReplacementQueue;
118 delete mChunkLoaderFactory;
123void QgsChunkedEntity::handleSceneUpdate(
const SceneContext &sceneContext )
133 pruneLoaderQueue( sceneContext );
138 int oldJobsCount = pendingJobsCount();
140 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
141 mActiveNodes.clear();
143 mCurrentTime = QTime::currentTime();
145 update( mRootNode, sceneContext );
148 int enabled = 0, disabled = 0, unloaded = 0;
151 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
153 if ( activeBefore.contains( node ) )
155 activeBefore.remove( node );
159 if ( !node->entity() )
161 QgsDebugError(
"Active node has null entity - this should never happen!" );
164 node->entity()->setEnabled(
true );
172 for ( QgsChunkNode *node : activeBefore )
174 if ( !node->entity() )
176 QgsDebugError(
"Active node has null entity - this should never happen!" );
179 node->entity()->setEnabled(
false );
188 unloaded = unloadNodes();
195 QList<QgsAABB> bboxes;
196 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
198 mBboxesEntity->setBoxes( bboxes );
204 mNeedsUpdate =
false;
206 if ( pendingJobsCount() != oldJobsCount )
207 emit pendingJobsCountChanged();
209 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
212 .arg( mFrustumCulled )
213 .arg( mChunkLoaderQueue->count() )
214 .arg( mReplacementQueue->count() )
216 .arg( t.elapsed() ), 2 );
220int QgsChunkedEntity::unloadNodes()
223 if ( usedGpuMemory <= mGpuMemoryLimit )
225 setHasReachedGpuMemoryLimit(
false );
229 QgsDebugMsgLevel( QStringLiteral(
"Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
235 QgsChunkListEntry *entry = mReplacementQueue->last();
236 while ( entry && usedGpuMemory > mGpuMemoryLimit )
242 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
244 QgsChunkListEntry *entryPrev = entry->prev;
245 mReplacementQueue->takeEntry( entry );
247 mActiveNodes.removeOne( entry->chunk );
248 entry->chunk->unloadChunk();
258 if ( usedGpuMemory > mGpuMemoryLimit )
260 setHasReachedGpuMemoryLimit(
true );
261 QgsDebugMsgLevel( QStringLiteral(
"Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
268QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
270 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
275 if ( activeEntityNodes.empty() )
276 activeEntityNodes << rootNode();
281 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
289 fnear = std::min( fnear, bboxfnear );
290 ffar = std::max( ffar, bboxffar );
295void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
297 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
302 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
306 mBboxesEntity->deleteLater();
307 mBboxesEntity =
nullptr;
311void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
313 for ( QgsChunkNode *node : nodes )
315 if ( node->state() == QgsChunkNode::QueuedForUpdate )
317 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
318 node->cancelQueuedForUpdate();
320 else if ( node->state() == QgsChunkNode::Updating )
322 cancelActiveJob( node->updater() );
325 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
327 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
328 node->setQueuedForUpdate( entry, updateJobFactory );
329 mChunkLoaderQueue->insertLast( entry );
336void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
338 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
343 QgsChunkListEntry *e = mChunkLoaderQueue->first();
346 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
349 toRemoveFromLoaderQueue.append( e->chunk );
355 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
357 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
358 if ( n->state() == QgsChunkNode::QueuedForLoad )
360 n->cancelQueuedForLoad();
364 n->cancelQueuedForUpdate();
365 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
370 if ( !toRemoveFromLoaderQueue.isEmpty() )
372 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
377int QgsChunkedEntity::pendingJobsCount()
const
379 return mChunkLoaderQueue->count() + mActiveJobs.count();
382struct ResidencyRequest
384 QgsChunkNode *node =
nullptr;
387 ResidencyRequest() =
default;
400 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
402 if ( request.level == otherRequest.level )
403 return request.dist > otherRequest.dist;
404 return request.level > otherRequest.level;
406} ResidencyRequestSorter;
408void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
410 QSet<QgsChunkNode *> nodes;
411 QVector<ResidencyRequest> residencyRequests;
413 using slotItem = std::pair<QgsChunkNode *, float>;
414 auto cmp_funct = [](
const slotItem & p1,
const slotItem & p2 )
416 return p1.second <= p2.second;
418 int renderedCount = 0;
419 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
420 pq.push( std::make_pair( root, screenSpaceError( root, sceneContext ) ) );
421 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
423 slotItem s = pq.top();
425 QgsChunkNode *node = s.first;
434 if ( !node->hasChildrenPopulated() )
444 if ( mChunkLoaderFactory->canCreateChildren( node ) )
446 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
450 mChunkLoaderFactory->prepareChildren( node );
456 double dist = node->bbox().center().distanceToPoint( sceneContext.cameraPos );
457 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
459 if ( !node->entity() )
464 bool becomesActive =
false;
467 if ( node->childCount() == 0 )
471 becomesActive =
true;
473 else if ( mTau > 0 && screenSpaceError( node, sceneContext ) <= mTau )
476 becomesActive =
true;
490 becomesActive =
true;
492 QgsChunkNode *
const *children = node->children();
493 for (
int i = 0; i < node->childCount(); ++i )
495 if ( children[i]->entity() || !children[i]->hasData() )
498 pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) );
506 double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos );
507 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
516 if ( node->allChildChunksResident( mCurrentTime ) )
518 QgsChunkNode *
const *children = node->children();
519 for (
int i = 0; i < node->childCount(); ++i )
520 pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) );
524 becomesActive =
true;
526 QgsChunkNode *
const *children = node->children();
527 for (
int i = 0; i < node->childCount(); ++i )
529 double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos );
530 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
538 mActiveNodes << node;
542 nodes.remove( node->parent() );
543 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
545 renderedCount += mChunkLoaderFactory->primitivesCount( node );
546 nodes.insert( node );
551 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
552 for (
const auto &request : residencyRequests )
553 requestResidency( request.node );
556void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
558 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
560 Q_ASSERT( node->replacementQueueEntry() );
561 Q_ASSERT( node->entity() );
562 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
563 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
565 else if ( node->state() == QgsChunkNode::QueuedForLoad )
568 Q_ASSERT( node->loaderQueueEntry() );
569 Q_ASSERT( !node->loader() );
570 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
572 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
573 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
576 else if ( node->state() == QgsChunkNode::Loading )
580 else if ( node->state() == QgsChunkNode::Skeleton )
582 if ( !node->hasData() )
586 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
587 node->setQueuedForLoad( entry );
588 mChunkLoaderQueue->insertFirst( entry );
591 Q_ASSERT(
false &&
"impossible!" );
595void QgsChunkedEntity::onActiveJobFinished()
597 int oldJobsCount = pendingJobsCount();
599 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
601 Q_ASSERT( mActiveJobs.contains( job ) );
603 QgsChunkNode *node = job->chunk();
605 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
607 Q_ASSERT( node->state() == QgsChunkNode::Loading );
608 Q_ASSERT( node->loader() == loader );
610 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
611 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
613 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
615 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
625 mActiveNodes << node;
628 node->setLoaded( entity );
630 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
632 emit newEntityCreated( entity );
636 node->setHasData(
false );
637 node->cancelLoading();
645 Q_ASSERT( node->state() == QgsChunkNode::Updating );
646 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
651 mActiveJobs.removeOne( job );
657 if ( pendingJobsCount() != oldJobsCount )
658 emit pendingJobsCountChanged();
661void QgsChunkedEntity::startJobs()
663 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
665 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
667 QgsChunkNode *node = entry->chunk;
670 QgsChunkQueueJob *job = startJob( node );
671 mActiveJobs.append( job );
675QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
677 if ( node->state() == QgsChunkNode::QueuedForLoad )
679 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
680 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
682 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
683 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
684 node->setLoading( loader );
687 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
689 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
692 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
693 return node->updater();
702void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
706 QgsChunkNode *node = job->chunk();
707 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
709 if ( qobject_cast<QgsChunkLoader *>( job ) )
712 node->cancelLoading();
714 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
715 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
720 node->cancelUpdating();
722 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
726 mActiveJobs.removeOne( job );
730void QgsChunkedEntity::cancelActiveJobs()
732 while ( !mActiveJobs.isEmpty() )
734 cancelActiveJob( mActiveJobs.takeFirst() );
743 return QVector<QgsRayCastingUtils::RayHit>();
@ Additive
When tile is refined its content should be used alongside its children simultaneously.
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 ...
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.