28#include <QElapsedTimer>
33#include "moc_qgschunkedentity.cpp"
35using namespace Qt::StringLiterals;
39static float screenSpaceError(
const QgsAABB &nodeBbox,
float nodeError,
const QgsChunkedEntity::SceneContext &sceneContext )
53static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
55 for (
int i = 0; i < node->childCount(); ++i )
57 QgsChunkNode *child = node->children()[i];
58 if ( child->entity() && activeNodes.contains( child ) )
60 if ( hasAnyActiveChildren( child, activeNodes ) )
67QgsChunkedEntity::QgsChunkedEntity(
Qgs3DMapSettings *mapSettings,
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
68 : Qgs3DMapSceneEntity( mapSettings, parent )
70 , mChunkLoaderFactory( loaderFactory )
71 , mOwnsFactory( ownsFactory )
72 , mPrimitivesBudget( primitiveBudget )
74 mRootNode = loaderFactory->createRootNode();
75 mChunkLoaderQueue =
new QgsChunkList;
76 mReplacementQueue =
new QgsChunkList;
79 connect( loaderFactory, &QgsChunkLoaderFactory::childrenPrepared,
this, [
this] {
80 setNeedsUpdate(
true );
81 emit pendingJobsCountChanged();
86QgsChunkedEntity::~QgsChunkedEntity()
91 Q_ASSERT( mActiveJobs.isEmpty() );
94 while ( !mChunkLoaderQueue->isEmpty() )
96 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
97 QgsChunkNode *node = entry->chunk;
99 if ( node->state() == QgsChunkNode::QueuedForLoad )
100 node->cancelQueuedForLoad();
101 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
102 node->cancelQueuedForUpdate();
107 delete mChunkLoaderQueue;
109 while ( !mReplacementQueue->isEmpty() )
111 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
114 entry->chunk->unloadChunk();
117 delete mReplacementQueue;
122 delete mChunkLoaderFactory;
127void QgsChunkedEntity::handleSceneUpdate(
const SceneContext &sceneContext )
137 pruneLoaderQueue( sceneContext );
142 int oldJobsCount = pendingJobsCount();
144 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
145 mActiveNodes.clear();
147 mCurrentTime = QTime::currentTime();
149 update( mRootNode, sceneContext );
152 int enabled = 0, disabled = 0, unloaded = 0;
155 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
157 if ( activeBefore.contains( node ) )
159 activeBefore.remove( node );
163 if ( !node->entity() )
165 QgsDebugError(
"Active node has null entity - this should never happen!" );
168 node->entity()->setEnabled(
true );
171 const QList<QgsGeoTransform *> transforms = node->entity()->findChildren<QgsGeoTransform *>();
172 for ( QgsGeoTransform *transform : transforms )
174 transform->setOrigin( mMapSettings->origin() );
184 for ( QgsChunkNode *node : activeBefore )
186 if ( !node->entity() )
188 QgsDebugError(
"Active node has null entity - this should never happen!" );
191 node->entity()->setEnabled(
false );
200 unloaded = unloadNodes();
207 QList<QgsBox3D> bboxes;
208 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
209 bboxes << n->box3D();
210 mBboxesEntity->setBoxes( bboxes );
216 mNeedsUpdate =
false;
218 if ( pendingJobsCount() != oldJobsCount )
219 emit pendingJobsCountChanged();
222 u
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms"_s.arg( mActiveNodes.count() )
225 .arg( mFrustumCulled )
226 .arg( mChunkLoaderQueue->count() )
227 .arg( mReplacementQueue->count() )
235int QgsChunkedEntity::unloadNodes()
238 if ( usedGpuMemory <= mGpuMemoryLimit )
240 setHasReachedGpuMemoryLimit(
false );
244 QgsDebugMsgLevel( u
"Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)"_s.arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
250 QgsChunkListEntry *entry = mReplacementQueue->last();
251 while ( entry && usedGpuMemory > mGpuMemoryLimit )
257 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
259 QgsChunkListEntry *entryPrev = entry->prev;
260 mReplacementQueue->takeEntry( entry );
262 mActiveNodes.removeOne( entry->chunk );
263 entry->chunk->unloadChunk();
273 if ( usedGpuMemory > mGpuMemoryLimit )
275 setHasReachedGpuMemoryLimit(
true );
276 QgsDebugMsgLevel( u
"Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)"_s.arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
283QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
285 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
290 if ( activeEntityNodes.empty() )
291 activeEntityNodes << rootNode();
296 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
304 fnear = std::min( fnear, bboxfnear );
305 ffar = std::max( ffar, bboxffar );
310void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
312 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
317 mBboxesEntity =
new QgsChunkBoundsEntity( mRootNode->box3D().center(),
this );
321 mBboxesEntity->deleteLater();
322 mBboxesEntity =
nullptr;
326void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
328 for ( QgsChunkNode *node : nodes )
330 if ( node->state() == QgsChunkNode::QueuedForUpdate )
332 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
333 node->cancelQueuedForUpdate();
335 else if ( node->state() == QgsChunkNode::Updating )
337 cancelActiveJob( node->updater() );
339 else if ( node->state() == QgsChunkNode::Skeleton || node->state() == QgsChunkNode::QueuedForLoad )
344 else if ( node->state() == QgsChunkNode::Loading )
347 cancelActiveJob( node->loader() );
348 requestResidency( node );
352 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
354 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
355 node->setQueuedForUpdate( entry, updateJobFactory );
356 mChunkLoaderQueue->insertLast( entry );
363void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
365 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
370 QgsChunkListEntry *e = mChunkLoaderQueue->first();
373 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
377 toRemoveFromLoaderQueue.append( e->chunk );
383 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
385 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
386 if ( n->state() == QgsChunkNode::QueuedForLoad )
388 n->cancelQueuedForLoad();
392 n->cancelQueuedForUpdate();
393 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
398 if ( !toRemoveFromLoaderQueue.isEmpty() )
400 QgsDebugMsgLevel( u
"Pruned %1 chunks in loading queue"_s.arg( toRemoveFromLoaderQueue.count() ), 2 );
405int QgsChunkedEntity::pendingJobsCount()
const
407 return mChunkLoaderQueue->count() + mActiveJobs.count();
410struct ResidencyRequest
412 QgsChunkNode *node =
nullptr;
415 ResidencyRequest() =
default;
416 ResidencyRequest( QgsChunkNode *n,
float d,
int l )
425 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
427 if ( request.level == otherRequest.level )
428 return request.dist > otherRequest.dist;
429 return request.level > otherRequest.level;
431} ResidencyRequestSorter;
433void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
435 QSet<QgsChunkNode *> nodes;
436 QVector<ResidencyRequest> residencyRequests;
438 using slotItem = std::pair<QgsChunkNode *, float>;
439 auto cmp_funct = [](
const slotItem &p1,
const slotItem &p2 ) {
return p1.second <= p2.second; };
440 int renderedCount = 0;
441 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
443 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
444 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
446 slotItem s = pq.top();
448 QgsChunkNode *node = s.first;
458 if ( !node->hasChildrenPopulated() )
468 if ( mChunkLoaderFactory->canCreateChildren( node ) )
470 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
474 mChunkLoaderFactory->prepareChildren( node );
480 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
481 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
483 if ( !node->entity() && node->hasData() )
488 bool becomesActive =
false;
491 if ( node->childCount() == 0 )
495 becomesActive =
true;
497 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
500 becomesActive =
true;
514 becomesActive =
true;
516 QgsChunkNode *
const *children = node->children();
517 for (
int i = 0; i < node->childCount(); ++i )
520 if ( children[i]->entity() || !children[i]->hasData() )
523 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
531 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
532 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
541 if ( node->allChildChunksResident( mCurrentTime ) )
543 QgsChunkNode *
const *children = node->children();
544 for (
int i = 0; i < node->childCount(); ++i )
547 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
552 becomesActive =
true;
554 QgsChunkNode *
const *children = node->children();
555 for (
int i = 0; i < node->childCount(); ++i )
558 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
559 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
565 if ( becomesActive && node->entity() )
567 mActiveNodes << node;
571 nodes.remove( node->parent() );
572 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
574 renderedCount += mChunkLoaderFactory->primitivesCount( node );
575 nodes.insert( node );
580 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
581 for (
const auto &request : residencyRequests )
582 requestResidency( request.node );
585void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
587 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
589 Q_ASSERT( node->replacementQueueEntry() );
590 Q_ASSERT( node->entity() );
591 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
592 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
594 else if ( node->state() == QgsChunkNode::QueuedForLoad )
597 Q_ASSERT( node->loaderQueueEntry() );
598 Q_ASSERT( !node->loader() );
599 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
601 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
602 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
605 else if ( node->state() == QgsChunkNode::Loading )
609 else if ( node->state() == QgsChunkNode::Skeleton )
611 if ( !node->hasData() )
615 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
616 node->setQueuedForLoad( entry );
617 mChunkLoaderQueue->insertFirst( entry );
620 Q_ASSERT(
false &&
"impossible!" );
624void QgsChunkedEntity::onActiveJobFinished()
626 int oldJobsCount = pendingJobsCount();
628 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
630 Q_ASSERT( mActiveJobs.contains( job ) );
632 QgsChunkNode *node = job->chunk();
634 if ( node->state() == QgsChunkNode::Loading )
636 QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job );
638 Q_ASSERT( node->loader() == loader );
640 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
641 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load"_s, node->tileId().text() );
643 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
645 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
655 mActiveNodes << node;
658 node->setLoaded( entity );
660 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
662 emit newEntityCreated( entity );
666 node->setHasData(
false );
667 node->cancelLoading();
675 Q_ASSERT( node->state() == QgsChunkNode::Updating );
681 if ( QgsChunkLoader *nodeUpdater = qobject_cast<QgsChunkLoader *>( node->updater() ) )
683 Qt3DCore::QEntity *newEntity = nodeUpdater->createEntity(
this );
684 node->replaceEntity( newEntity );
685 emit newEntityCreated( newEntity );
688 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Update"_s, node->tileId().text() );
693 mActiveJobs.removeOne( job );
699 if ( pendingJobsCount() != oldJobsCount )
700 emit pendingJobsCountChanged();
703void QgsChunkedEntity::startJobs()
705 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
707 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
709 QgsChunkNode *node = entry->chunk;
712 QgsChunkQueueJob *job = startJob( node );
713 if ( !job->isFinished() )
714 mActiveJobs.append( job );
718QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
720 if ( node->state() == QgsChunkNode::QueuedForLoad )
722 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Load"_s, node->tileId().text() );
723 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
725 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
726 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
728 node->setLoading( loader );
731 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
733 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, u
"3D"_s, u
"Update"_s, node->tileId().text() );
736 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
737 node->updater()->start();
738 return node->updater();
747void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
751 QgsChunkNode *node = job->chunk();
752 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
754 if ( node->state() == QgsChunkNode::Loading )
757 node->cancelLoading();
759 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load "_s + node->tileId().text(), node->tileId().text() );
760 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Load"_s, node->tileId().text() );
762 else if ( node->state() == QgsChunkNode::Updating )
765 node->cancelUpdating();
767 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, u
"3D"_s, u
"Update"_s, node->tileId().text() );
775 mActiveJobs.removeOne( job );
779void QgsChunkedEntity::cancelActiveJobs()
781 while ( !mActiveJobs.isEmpty() )
783 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)