26#include <QElapsedTimer>
30#include "moc_qgschunkedentity.cpp"
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] {
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 );
167 const QList<QgsGeoTransform *> transforms = node->entity()->findChildren<QgsGeoTransform *>();
168 for ( QgsGeoTransform *transform : transforms )
170 transform->setOrigin( mMapSettings->origin() );
180 for ( QgsChunkNode *node : activeBefore )
182 if ( !node->entity() )
184 QgsDebugError(
"Active node has null entity - this should never happen!" );
187 node->entity()->setEnabled(
false );
196 unloaded = unloadNodes();
203 QList<QgsBox3D> bboxes;
204 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
205 bboxes << n->box3D();
206 mBboxesEntity->setBoxes( bboxes );
212 mNeedsUpdate =
false;
214 if ( pendingJobsCount() != oldJobsCount )
215 emit pendingJobsCountChanged();
217 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 );
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( mRootNode->box3D().center(),
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() );
325 else if ( node->state() == QgsChunkNode::Skeleton || node->state() == QgsChunkNode::QueuedForLoad )
330 else if ( node->state() == QgsChunkNode::Loading )
333 cancelActiveJob( node->loader() );
334 requestResidency( node );
338 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
340 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
341 node->setQueuedForUpdate( entry, updateJobFactory );
342 mChunkLoaderQueue->insertLast( entry );
349void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
351 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
356 QgsChunkListEntry *e = mChunkLoaderQueue->first();
359 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
363 toRemoveFromLoaderQueue.append( e->chunk );
369 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
371 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
372 if ( n->state() == QgsChunkNode::QueuedForLoad )
374 n->cancelQueuedForLoad();
378 n->cancelQueuedForUpdate();
379 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
384 if ( !toRemoveFromLoaderQueue.isEmpty() )
386 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
391int QgsChunkedEntity::pendingJobsCount()
const
393 return mChunkLoaderQueue->count() + mActiveJobs.count();
396struct ResidencyRequest
398 QgsChunkNode *node =
nullptr;
401 ResidencyRequest() =
default;
415 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
417 if ( request.level == otherRequest.level )
418 return request.dist > otherRequest.dist;
419 return request.level > otherRequest.level;
421} ResidencyRequestSorter;
423void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
425 QSet<QgsChunkNode *> nodes;
426 QVector<ResidencyRequest> residencyRequests;
428 using slotItem = std::pair<QgsChunkNode *, float>;
429 auto cmp_funct = [](
const slotItem &p1,
const slotItem &p2 ) {
430 return p1.second <= p2.second;
432 int renderedCount = 0;
433 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
435 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
436 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
438 slotItem s = pq.top();
440 QgsChunkNode *node = s.first;
450 if ( !node->hasChildrenPopulated() )
460 if ( mChunkLoaderFactory->canCreateChildren( node ) )
462 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
466 mChunkLoaderFactory->prepareChildren( node );
472 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
473 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
475 if ( !node->entity() && node->hasData() )
480 bool becomesActive =
false;
483 if ( node->childCount() == 0 )
487 becomesActive =
true;
489 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
492 becomesActive =
true;
506 becomesActive =
true;
508 QgsChunkNode *
const *children = node->children();
509 for (
int i = 0; i < node->childCount(); ++i )
512 if ( children[i]->entity() || !children[i]->hasData() )
515 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
523 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
524 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
533 if ( node->allChildChunksResident( mCurrentTime ) )
535 QgsChunkNode *
const *children = node->children();
536 for (
int i = 0; i < node->childCount(); ++i )
539 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
544 becomesActive =
true;
546 QgsChunkNode *
const *children = node->children();
547 for (
int i = 0; i < node->childCount(); ++i )
550 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
551 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
557 if ( becomesActive && node->entity() )
559 mActiveNodes << node;
563 nodes.remove( node->parent() );
564 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
566 renderedCount += mChunkLoaderFactory->primitivesCount( node );
567 nodes.insert( node );
572 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
573 for (
const auto &request : residencyRequests )
574 requestResidency( request.node );
577void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
579 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
581 Q_ASSERT( node->replacementQueueEntry() );
582 Q_ASSERT( node->entity() );
583 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
584 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
586 else if ( node->state() == QgsChunkNode::QueuedForLoad )
589 Q_ASSERT( node->loaderQueueEntry() );
590 Q_ASSERT( !node->loader() );
591 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
593 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
594 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
597 else if ( node->state() == QgsChunkNode::Loading )
601 else if ( node->state() == QgsChunkNode::Skeleton )
603 if ( !node->hasData() )
607 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
608 node->setQueuedForLoad( entry );
609 mChunkLoaderQueue->insertFirst( entry );
612 Q_ASSERT(
false &&
"impossible!" );
616void QgsChunkedEntity::onActiveJobFinished()
618 int oldJobsCount = pendingJobsCount();
620 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
622 Q_ASSERT( mActiveJobs.contains( job ) );
624 QgsChunkNode *node = job->chunk();
626 if ( node->state() == QgsChunkNode::Loading )
628 QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job );
630 Q_ASSERT( node->loader() == loader );
632 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
633 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
635 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
637 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
647 mActiveNodes << node;
650 node->setLoaded( entity );
652 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
654 emit newEntityCreated( entity );
658 node->setHasData(
false );
659 node->cancelLoading();
667 Q_ASSERT( node->state() == QgsChunkNode::Updating );
673 if ( QgsChunkLoader *nodeUpdater = qobject_cast<QgsChunkLoader *>( node->updater() ) )
675 Qt3DCore::QEntity *newEntity = nodeUpdater->createEntity(
this );
676 node->replaceEntity( newEntity );
677 emit newEntityCreated( newEntity );
680 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
685 mActiveJobs.removeOne( job );
691 if ( pendingJobsCount() != oldJobsCount )
692 emit pendingJobsCountChanged();
695void QgsChunkedEntity::startJobs()
697 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
699 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
701 QgsChunkNode *node = entry->chunk;
704 QgsChunkQueueJob *job = startJob( node );
705 if ( !job->isFinished() )
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 );
720 node->setLoading( loader );
723 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
725 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
728 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
729 node->updater()->start();
730 return node->updater();
739void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
743 QgsChunkNode *node = job->chunk();
744 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
746 if ( node->state() == QgsChunkNode::Loading )
749 node->cancelLoading();
751 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
752 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
754 else if ( node->state() == QgsChunkNode::Updating )
757 node->cancelUpdating();
759 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
767 mActiveJobs.removeOne( job );
771void QgsChunkedEntity::cancelActiveJobs()
773 while ( !mActiveJobs.isEmpty() )
775 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)