26#include <QElapsedTimer>
31#include "moc_qgschunkedentity.cpp"
33using namespace Qt::StringLiterals;
38static float screenSpaceError(
const QgsAABB &nodeBbox,
float nodeError,
const QgsChunkedEntity::SceneContext &sceneContext )
52static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
54 for (
int i = 0; i < node->childCount(); ++i )
56 QgsChunkNode *child = node->children()[i];
57 if ( child->entity() && activeNodes.contains( child ) )
59 if ( hasAnyActiveChildren( child, activeNodes ) )
66QgsChunkedEntity::QgsChunkedEntity(
Qgs3DMapSettings *mapSettings,
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
67 : Qgs3DMapSceneEntity( mapSettings, parent )
69 , mChunkLoaderFactory( loaderFactory )
70 , mOwnsFactory( ownsFactory )
71 , mPrimitivesBudget( primitiveBudget )
73 mRootNode = loaderFactory->createRootNode();
74 mChunkLoaderQueue =
new QgsChunkList;
75 mReplacementQueue =
new QgsChunkList;
78 connect( loaderFactory, &QgsChunkLoaderFactory::childrenPrepared,
this, [
this] {
79 setNeedsUpdate(
true );
80 emit pendingJobsCountChanged();
85QgsChunkedEntity::~QgsChunkedEntity()
90 Q_ASSERT( mActiveJobs.isEmpty() );
93 while ( !mChunkLoaderQueue->isEmpty() )
95 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
96 QgsChunkNode *node = entry->chunk;
98 if ( node->state() == QgsChunkNode::QueuedForLoad )
99 node->cancelQueuedForLoad();
100 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
101 node->cancelQueuedForUpdate();
106 delete mChunkLoaderQueue;
108 while ( !mReplacementQueue->isEmpty() )
110 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
113 entry->chunk->unloadChunk();
116 delete mReplacementQueue;
121 delete mChunkLoaderFactory;
126void QgsChunkedEntity::handleSceneUpdate(
const SceneContext &sceneContext )
136 pruneLoaderQueue( sceneContext );
141 int oldJobsCount = pendingJobsCount();
143 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
144 mActiveNodes.clear();
146 mCurrentTime = QTime::currentTime();
148 update( mRootNode, sceneContext );
151 int enabled = 0, disabled = 0, unloaded = 0;
154 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
156 if ( activeBefore.contains( node ) )
158 activeBefore.remove( node );
162 if ( !node->entity() )
164 QgsDebugError(
"Active node has null entity - this should never happen!" );
167 node->entity()->setEnabled(
true );
170 const QList<QgsGeoTransform *> transforms = node->entity()->findChildren<QgsGeoTransform *>();
171 for ( QgsGeoTransform *transform : transforms )
173 transform->setOrigin( mMapSettings->origin() );
183 for ( QgsChunkNode *node : activeBefore )
185 if ( !node->entity() )
187 QgsDebugError(
"Active node has null entity - this should never happen!" );
190 node->entity()->setEnabled(
false );
199 unloaded = unloadNodes();
206 QList<QgsBox3D> bboxes;
207 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
208 bboxes << n->box3D();
209 mBboxesEntity->setBoxes( bboxes );
215 mNeedsUpdate =
false;
217 if ( pendingJobsCount() != oldJobsCount )
218 emit pendingJobsCountChanged();
221 u
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms"_s.arg( mActiveNodes.count() )
224 .arg( mFrustumCulled )
225 .arg( mChunkLoaderQueue->count() )
226 .arg( mReplacementQueue->count() )
234int QgsChunkedEntity::unloadNodes()
237 if ( usedGpuMemory <= mGpuMemoryLimit )
239 setHasReachedGpuMemoryLimit(
false );
243 QgsDebugMsgLevel( u
"Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)"_s.arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
249 QgsChunkListEntry *entry = mReplacementQueue->last();
250 while ( entry && usedGpuMemory > mGpuMemoryLimit )
256 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
258 QgsChunkListEntry *entryPrev = entry->prev;
259 mReplacementQueue->takeEntry( entry );
261 mActiveNodes.removeOne( entry->chunk );
262 entry->chunk->unloadChunk();
272 if ( usedGpuMemory > mGpuMemoryLimit )
274 setHasReachedGpuMemoryLimit(
true );
275 QgsDebugMsgLevel( u
"Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)"_s.arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
282QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
284 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
289 if ( activeEntityNodes.empty() )
290 activeEntityNodes << rootNode();
295 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
303 fnear = std::min( fnear, bboxfnear );
304 ffar = std::max( ffar, bboxffar );
309void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
311 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
316 mBboxesEntity =
new QgsChunkBoundsEntity( mRootNode->box3D().center(),
this );
320 mBboxesEntity->deleteLater();
321 mBboxesEntity =
nullptr;
325void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
327 for ( QgsChunkNode *node : nodes )
329 if ( node->state() == QgsChunkNode::QueuedForUpdate )
331 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
332 node->cancelQueuedForUpdate();
334 else if ( node->state() == QgsChunkNode::Updating )
336 cancelActiveJob( node->updater() );
338 else if ( node->state() == QgsChunkNode::Skeleton || node->state() == QgsChunkNode::QueuedForLoad )
343 else if ( node->state() == QgsChunkNode::Loading )
346 cancelActiveJob( node->loader() );
347 requestResidency( node );
351 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
353 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
354 node->setQueuedForUpdate( entry, updateJobFactory );
355 mChunkLoaderQueue->insertLast( entry );
362void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
364 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
369 QgsChunkListEntry *e = mChunkLoaderQueue->first();
372 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
376 toRemoveFromLoaderQueue.append( e->chunk );
382 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
384 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
385 if ( n->state() == QgsChunkNode::QueuedForLoad )
387 n->cancelQueuedForLoad();
391 n->cancelQueuedForUpdate();
392 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
397 if ( !toRemoveFromLoaderQueue.isEmpty() )
399 QgsDebugMsgLevel( u
"Pruned %1 chunks in loading queue"_s.arg( toRemoveFromLoaderQueue.count() ), 2 );
404int QgsChunkedEntity::pendingJobsCount()
const
406 return mChunkLoaderQueue->count() + mActiveJobs.count();
409struct ResidencyRequest
411 QgsChunkNode *node =
nullptr;
414 ResidencyRequest() =
default;
415 ResidencyRequest( QgsChunkNode *n,
float d,
int l )
424 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
426 if ( request.level == otherRequest.level )
427 return request.dist > otherRequest.dist;
428 return request.level > otherRequest.level;
430} ResidencyRequestSorter;
432void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
434 QSet<QgsChunkNode *> nodes;
435 QVector<ResidencyRequest> residencyRequests;
437 using slotItem = std::pair<QgsChunkNode *, float>;
438 auto cmp_funct = [](
const slotItem &p1,
const slotItem &p2 ) {
return p1.second <= p2.second; };
439 int renderedCount = 0;
440 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
442 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
443 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
445 slotItem s = pq.top();
447 QgsChunkNode *node = s.first;
457 if ( !node->hasChildrenPopulated() )
467 if ( mChunkLoaderFactory->canCreateChildren( node ) )
469 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
473 mChunkLoaderFactory->prepareChildren( node );
479 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
480 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
482 if ( !node->entity() && node->hasData() )
487 bool becomesActive =
false;
490 if ( node->childCount() == 0 )
494 becomesActive =
true;
496 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
499 becomesActive =
true;
513 becomesActive =
true;
515 QgsChunkNode *
const *children = node->children();
516 for (
int i = 0; i < node->childCount(); ++i )
519 if ( children[i]->entity() || !children[i]->hasData() )
522 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
530 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
531 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
540 if ( node->allChildChunksResident( mCurrentTime ) )
542 QgsChunkNode *
const *children = node->children();
543 for (
int i = 0; i < node->childCount(); ++i )
546 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
551 becomesActive =
true;
553 QgsChunkNode *
const *children = node->children();
554 for (
int i = 0; i < node->childCount(); ++i )
557 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
558 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
564 if ( becomesActive && node->entity() )
566 mActiveNodes << node;
570 nodes.remove( node->parent() );
571 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
573 renderedCount += mChunkLoaderFactory->primitivesCount( node );
574 nodes.insert( node );
579 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
580 for (
const auto &request : residencyRequests )
581 requestResidency( request.node );
584void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
586 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
588 Q_ASSERT( node->replacementQueueEntry() );
589 Q_ASSERT( node->entity() );
590 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
591 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
593 else if ( node->state() == QgsChunkNode::QueuedForLoad )
596 Q_ASSERT( node->loaderQueueEntry() );
597 Q_ASSERT( !node->loader() );
598 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
600 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
601 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
604 else if ( node->state() == QgsChunkNode::Loading )
608 else if ( node->state() == QgsChunkNode::Skeleton )
610 if ( !node->hasData() )
614 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
615 node->setQueuedForLoad( entry );
616 mChunkLoaderQueue->insertFirst( entry );
619 Q_ASSERT(
false &&
"impossible!" );
623void QgsChunkedEntity::onActiveJobFinished()
625 int oldJobsCount = pendingJobsCount();
627 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
629 Q_ASSERT( mActiveJobs.contains( job ) );
631 QgsChunkNode *node = job->chunk();
633 if ( node->state() == QgsChunkNode::Loading )
635 QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job );
637 Q_ASSERT( node->loader() == loader );
639 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
640 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load"_s, node->tileId().text() );
642 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
644 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
654 mActiveNodes << node;
657 node->setLoaded( entity );
659 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
661 emit newEntityCreated( entity );
665 node->setHasData(
false );
666 node->cancelLoading();
674 Q_ASSERT( node->state() == QgsChunkNode::Updating );
680 if ( QgsChunkLoader *nodeUpdater = qobject_cast<QgsChunkLoader *>( node->updater() ) )
682 Qt3DCore::QEntity *newEntity = nodeUpdater->createEntity(
this );
683 node->replaceEntity( newEntity );
684 emit newEntityCreated( newEntity );
687 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Update"_s, node->tileId().text() );
692 mActiveJobs.removeOne( job );
698 if ( pendingJobsCount() != oldJobsCount )
699 emit pendingJobsCountChanged();
702void QgsChunkedEntity::startJobs()
704 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
706 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
708 QgsChunkNode *node = entry->chunk;
711 QgsChunkQueueJob *job = startJob( node );
712 if ( !job->isFinished() )
713 mActiveJobs.append( job );
717QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
719 if ( node->state() == QgsChunkNode::QueuedForLoad )
721 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Load"_s, node->tileId().text() );
722 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
724 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
725 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
727 node->setLoading( loader );
730 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
732 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Update"_s, node->tileId().text() );
735 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
736 node->updater()->start();
737 return node->updater();
746void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
750 QgsChunkNode *node = job->chunk();
751 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
753 if ( node->state() == QgsChunkNode::Loading )
756 node->cancelLoading();
758 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
759 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load"_s, node->tileId().text() );
761 else if ( node->state() == QgsChunkNode::Updating )
764 node->cancelUpdating();
766 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Update"_s, node->tileId().text() );
774 mActiveJobs.removeOne( job );
778void QgsChunkedEntity::cancelActiveJobs()
780 while ( !mActiveJobs.isEmpty() )
782 cancelActiveJob( mActiveJobs.takeFirst() );
@ 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 ...
Axis-aligned bounding box - in world coords.
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).
A representation of a ray in 3D.
Responsible for defining parameters of the ray casting operations in 3D map canvases.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)