17#include "moc_qgschunkedentity.cpp"
19#include <QElapsedTimer>
36static float screenSpaceError(
const QgsAABB &nodeBbox,
float nodeError,
const QgsChunkedEntity::SceneContext &sceneContext )
50static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
52 for (
int i = 0; i < node->childCount(); ++i )
54 QgsChunkNode *child = node->children()[i];
55 if ( child->entity() && activeNodes.contains( child ) )
57 if ( hasAnyActiveChildren( child, activeNodes ) )
64QgsChunkedEntity::QgsChunkedEntity(
Qgs3DMapSettings *mapSettings,
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
65 : Qgs3DMapSceneEntity( mapSettings, parent )
67 , mChunkLoaderFactory( loaderFactory )
68 , mOwnsFactory( ownsFactory )
69 , mPrimitivesBudget( primitiveBudget )
71 mRootNode = loaderFactory->createRootNode();
72 mChunkLoaderQueue =
new QgsChunkList;
73 mReplacementQueue =
new QgsChunkList;
76 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 );
168 const QList<QgsGeoTransform *> transforms = node->entity()->findChildren<QgsGeoTransform *>();
169 for ( QgsGeoTransform *transform : transforms )
171 transform->setOrigin( mMapSettings->origin() );
181 for ( QgsChunkNode *node : activeBefore )
183 if ( !node->entity() )
185 QgsDebugError(
"Active node has null entity - this should never happen!" );
188 node->entity()->setEnabled(
false );
197 unloaded = unloadNodes();
204 QList<QgsBox3D> bboxes;
205 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
206 bboxes << n->box3D();
207 mBboxesEntity->setBoxes( bboxes );
213 mNeedsUpdate =
false;
215 if ( pendingJobsCount() != oldJobsCount )
216 emit pendingJobsCountChanged();
218 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() ).arg( enabled ).arg( disabled ).arg( mFrustumCulled ).arg( mChunkLoaderQueue->count() ).arg( mReplacementQueue->count() ).arg( unloaded ).arg( t.elapsed() ), 2 );
222int QgsChunkedEntity::unloadNodes()
225 if ( usedGpuMemory <= mGpuMemoryLimit )
227 setHasReachedGpuMemoryLimit(
false );
231 QgsDebugMsgLevel( QStringLiteral(
"Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
237 QgsChunkListEntry *entry = mReplacementQueue->last();
238 while ( entry && usedGpuMemory > mGpuMemoryLimit )
244 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
246 QgsChunkListEntry *entryPrev = entry->prev;
247 mReplacementQueue->takeEntry( entry );
249 mActiveNodes.removeOne( entry->chunk );
250 entry->chunk->unloadChunk();
260 if ( usedGpuMemory > mGpuMemoryLimit )
262 setHasReachedGpuMemoryLimit(
true );
263 QgsDebugMsgLevel( QStringLiteral(
"Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
270QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
272 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
277 if ( activeEntityNodes.empty() )
278 activeEntityNodes << rootNode();
283 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
291 fnear = std::min( fnear, bboxfnear );
292 ffar = std::max( ffar, bboxffar );
297void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
299 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
304 mBboxesEntity =
new QgsChunkBoundsEntity( mRootNode->box3D().center(),
this );
308 mBboxesEntity->deleteLater();
309 mBboxesEntity =
nullptr;
313void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
315 for ( QgsChunkNode *node : nodes )
317 if ( node->state() == QgsChunkNode::QueuedForUpdate )
319 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
320 node->cancelQueuedForUpdate();
322 else if ( node->state() == QgsChunkNode::Updating )
324 cancelActiveJob( node->updater() );
326 else if ( node->state() == QgsChunkNode::Skeleton || node->state() == QgsChunkNode::QueuedForLoad )
331 else if ( node->state() == QgsChunkNode::Loading )
334 cancelActiveJob( node->loader() );
335 requestResidency( node );
339 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
341 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
342 node->setQueuedForUpdate( entry, updateJobFactory );
343 mChunkLoaderQueue->insertLast( entry );
350void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
352 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
357 QgsChunkListEntry *e = mChunkLoaderQueue->first();
360 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
364 toRemoveFromLoaderQueue.append( e->chunk );
370 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
372 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
373 if ( n->state() == QgsChunkNode::QueuedForLoad )
375 n->cancelQueuedForLoad();
379 n->cancelQueuedForUpdate();
380 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
385 if ( !toRemoveFromLoaderQueue.isEmpty() )
387 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
392int QgsChunkedEntity::pendingJobsCount()
const
394 return mChunkLoaderQueue->count() + mActiveJobs.count();
397struct ResidencyRequest
399 QgsChunkNode *node =
nullptr;
402 ResidencyRequest() =
default;
416 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
418 if ( request.level == otherRequest.level )
419 return request.dist > otherRequest.dist;
420 return request.level > otherRequest.level;
422} ResidencyRequestSorter;
424void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
426 QSet<QgsChunkNode *> nodes;
427 QVector<ResidencyRequest> residencyRequests;
429 using slotItem = std::pair<QgsChunkNode *, float>;
430 auto cmp_funct = [](
const slotItem &p1,
const slotItem &p2 ) {
431 return p1.second <= p2.second;
433 int renderedCount = 0;
434 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
436 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
437 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
439 slotItem s = pq.top();
441 QgsChunkNode *node = s.first;
451 if ( !node->hasChildrenPopulated() )
461 if ( mChunkLoaderFactory->canCreateChildren( node ) )
463 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
467 mChunkLoaderFactory->prepareChildren( node );
473 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
474 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
476 if ( !node->entity() && node->hasData() )
481 bool becomesActive =
false;
484 if ( node->childCount() == 0 )
488 becomesActive =
true;
490 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
493 becomesActive =
true;
507 becomesActive =
true;
509 QgsChunkNode *
const *children = node->children();
510 for (
int i = 0; i < node->childCount(); ++i )
513 if ( children[i]->entity() || !children[i]->hasData() )
516 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
524 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
525 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
534 if ( node->allChildChunksResident( mCurrentTime ) )
536 QgsChunkNode *
const *children = node->children();
537 for (
int i = 0; i < node->childCount(); ++i )
540 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
545 becomesActive =
true;
547 QgsChunkNode *
const *children = node->children();
548 for (
int i = 0; i < node->childCount(); ++i )
551 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
552 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
558 if ( becomesActive && node->entity() )
560 mActiveNodes << node;
564 nodes.remove( node->parent() );
565 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
567 renderedCount += mChunkLoaderFactory->primitivesCount( node );
568 nodes.insert( node );
573 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
574 for (
const auto &request : residencyRequests )
575 requestResidency( request.node );
578void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
580 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
582 Q_ASSERT( node->replacementQueueEntry() );
583 Q_ASSERT( node->entity() );
584 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
585 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
587 else if ( node->state() == QgsChunkNode::QueuedForLoad )
590 Q_ASSERT( node->loaderQueueEntry() );
591 Q_ASSERT( !node->loader() );
592 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
594 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
595 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
598 else if ( node->state() == QgsChunkNode::Loading )
602 else if ( node->state() == QgsChunkNode::Skeleton )
604 if ( !node->hasData() )
608 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
609 node->setQueuedForLoad( entry );
610 mChunkLoaderQueue->insertFirst( entry );
613 Q_ASSERT(
false &&
"impossible!" );
617void QgsChunkedEntity::onActiveJobFinished()
619 int oldJobsCount = pendingJobsCount();
621 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
623 Q_ASSERT( mActiveJobs.contains( job ) );
625 QgsChunkNode *node = job->chunk();
627 if ( node->state() == QgsChunkNode::Loading )
629 QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job );
631 Q_ASSERT( node->loader() == loader );
633 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
634 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
636 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
638 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
648 mActiveNodes << node;
651 node->setLoaded( entity );
653 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
655 emit newEntityCreated( entity );
659 node->setHasData(
false );
660 node->cancelLoading();
668 Q_ASSERT( node->state() == QgsChunkNode::Updating );
674 if ( QgsChunkLoader *nodeUpdater = qobject_cast<QgsChunkLoader *>( node->updater() ) )
676 Qt3DCore::QEntity *newEntity = nodeUpdater->createEntity(
this );
677 node->replaceEntity( newEntity );
678 emit newEntityCreated( newEntity );
681 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
686 mActiveJobs.removeOne( job );
692 if ( pendingJobsCount() != oldJobsCount )
693 emit pendingJobsCountChanged();
696void QgsChunkedEntity::startJobs()
698 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
700 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
702 QgsChunkNode *node = entry->chunk;
705 QgsChunkQueueJob *job = startJob( node );
706 mActiveJobs.append( job );
710QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
712 if ( node->state() == QgsChunkNode::QueuedForLoad )
714 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
715 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
717 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
718 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
719 node->setLoading( loader );
722 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
724 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
727 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
728 return node->updater();
737void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
741 QgsChunkNode *node = job->chunk();
742 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
744 if ( node->state() == QgsChunkNode::Loading )
747 node->cancelLoading();
749 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
750 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
752 else if ( node->state() == QgsChunkNode::Updating )
755 node->cancelUpdating();
757 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
765 mActiveJobs.removeOne( job );
769void QgsChunkedEntity::cancelActiveJobs()
771 while ( !mActiveJobs.isEmpty() )
773 cancelActiveJob( mActiveJobs.takeFirst() );
782 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.