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<QgsAABB> bboxes;
205 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
206 bboxes <<
Qgs3DUtils::mapToWorldExtent( n->box3D(), mMapSettings->origin() );
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(
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() );
327 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
329 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
330 node->setQueuedForUpdate( entry, updateJobFactory );
331 mChunkLoaderQueue->insertLast( entry );
338void QgsChunkedEntity::pruneLoaderQueue(
const SceneContext &sceneContext )
340 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
345 QgsChunkListEntry *e = mChunkLoaderQueue->first();
348 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
352 toRemoveFromLoaderQueue.append( e->chunk );
358 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
360 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
361 if ( n->state() == QgsChunkNode::QueuedForLoad )
363 n->cancelQueuedForLoad();
367 n->cancelQueuedForUpdate();
368 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
373 if ( !toRemoveFromLoaderQueue.isEmpty() )
375 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
380int QgsChunkedEntity::pendingJobsCount()
const
382 return mChunkLoaderQueue->count() + mActiveJobs.count();
385struct ResidencyRequest
387 QgsChunkNode *node =
nullptr;
390 ResidencyRequest() =
default;
404 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
406 if ( request.level == otherRequest.level )
407 return request.dist > otherRequest.dist;
408 return request.level > otherRequest.level;
410} ResidencyRequestSorter;
412void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneContext &sceneContext )
414 QSet<QgsChunkNode *> nodes;
415 QVector<ResidencyRequest> residencyRequests;
417 using slotItem = std::pair<QgsChunkNode *, float>;
418 auto cmp_funct = [](
const slotItem &p1,
const slotItem &p2 ) {
419 return p1.second <= p2.second;
421 int renderedCount = 0;
422 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
424 pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) );
425 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
427 slotItem s = pq.top();
429 QgsChunkNode *node = s.first;
439 if ( !node->hasChildrenPopulated() )
449 if ( mChunkLoaderFactory->canCreateChildren( node ) )
451 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
455 mChunkLoaderFactory->prepareChildren( node );
461 double dist = bbox.
center().distanceToPoint( sceneContext.cameraPos );
462 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
464 if ( !node->entity() && node->hasData() )
469 bool becomesActive =
false;
472 if ( node->childCount() == 0 )
476 becomesActive =
true;
478 else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() )
481 becomesActive =
true;
495 becomesActive =
true;
497 QgsChunkNode *
const *children = node->children();
498 for (
int i = 0; i < node->childCount(); ++i )
501 if ( children[i]->entity() || !children[i]->hasData() )
504 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
512 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
513 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
522 if ( node->allChildChunksResident( mCurrentTime ) )
524 QgsChunkNode *
const *children = node->children();
525 for (
int i = 0; i < node->childCount(); ++i )
528 pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) );
533 becomesActive =
true;
535 QgsChunkNode *
const *children = node->children();
536 for (
int i = 0; i < node->childCount(); ++i )
539 double dist = childBbox.
center().distanceToPoint( sceneContext.cameraPos );
540 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
546 if ( becomesActive && node->entity() )
548 mActiveNodes << node;
552 nodes.remove( node->parent() );
553 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
555 renderedCount += mChunkLoaderFactory->primitivesCount( node );
556 nodes.insert( node );
561 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
562 for (
const auto &request : residencyRequests )
563 requestResidency( request.node );
566void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
568 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
570 Q_ASSERT( node->replacementQueueEntry() );
571 Q_ASSERT( node->entity() );
572 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
573 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
575 else if ( node->state() == QgsChunkNode::QueuedForLoad )
578 Q_ASSERT( node->loaderQueueEntry() );
579 Q_ASSERT( !node->loader() );
580 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
582 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
583 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
586 else if ( node->state() == QgsChunkNode::Loading )
590 else if ( node->state() == QgsChunkNode::Skeleton )
592 if ( !node->hasData() )
596 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
597 node->setQueuedForLoad( entry );
598 mChunkLoaderQueue->insertFirst( entry );
601 Q_ASSERT(
false &&
"impossible!" );
605void QgsChunkedEntity::onActiveJobFinished()
607 int oldJobsCount = pendingJobsCount();
609 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
611 Q_ASSERT( mActiveJobs.contains( job ) );
613 QgsChunkNode *node = job->chunk();
615 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
617 Q_ASSERT( node->state() == QgsChunkNode::Loading );
618 Q_ASSERT( node->loader() == loader );
620 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
621 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
623 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
625 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
635 mActiveNodes << node;
638 node->setLoaded( entity );
640 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
642 emit newEntityCreated( entity );
646 node->setHasData(
false );
647 node->cancelLoading();
655 Q_ASSERT( node->state() == QgsChunkNode::Updating );
656 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
661 mActiveJobs.removeOne( job );
667 if ( pendingJobsCount() != oldJobsCount )
668 emit pendingJobsCountChanged();
671void QgsChunkedEntity::startJobs()
673 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
675 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
677 QgsChunkNode *node = entry->chunk;
680 QgsChunkQueueJob *job = startJob( node );
681 mActiveJobs.append( job );
685QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
687 if ( node->state() == QgsChunkNode::QueuedForLoad )
689 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
690 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
692 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
693 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
694 node->setLoading( loader );
697 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
699 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
702 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
703 return node->updater();
712void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
716 QgsChunkNode *node = job->chunk();
717 disconnect( job, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
719 if ( qobject_cast<QgsChunkLoader *>( job ) )
722 node->cancelLoading();
724 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
725 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
730 node->cancelUpdating();
732 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
736 mActiveJobs.removeOne( job );
740void QgsChunkedEntity::cancelActiveJobs()
742 while ( !mActiveJobs.isEmpty() )
744 cancelActiveJob( mActiveJobs.takeFirst() );
753 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.