18#include <QElapsedTimer>
20#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
21#include <Qt3DRender/QBuffer>
24#include <Qt3DCore/QBuffer>
42static float screenSpaceError( QgsChunkNode *node,
const QgsChunkedEntity::SceneState &state )
44 if ( node->error() <= 0 )
47 float dist = node->bbox().distanceFromPoint( state.cameraPos );
55QgsChunkedEntity::QgsChunkedEntity(
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
56 : Qgs3DMapSceneEntity( parent )
58 , mChunkLoaderFactory( loaderFactory )
59 , mOwnsFactory( ownsFactory )
60 , mPrimitivesBudget( primitiveBudget )
62 mRootNode = loaderFactory->createRootNode();
63 mChunkLoaderQueue =
new QgsChunkList;
64 mReplacementQueue =
new QgsChunkList;
68QgsChunkedEntity::~QgsChunkedEntity()
73 Q_ASSERT( mActiveJobs.isEmpty() );
76 while ( !mChunkLoaderQueue->isEmpty() )
78 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
79 QgsChunkNode *node = entry->chunk;
81 if ( node->state() == QgsChunkNode::QueuedForLoad )
82 node->cancelQueuedForLoad();
83 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
84 node->cancelQueuedForUpdate();
89 delete mChunkLoaderQueue;
91 while ( !mReplacementQueue->isEmpty() )
93 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
96 entry->chunk->unloadChunk();
99 delete mReplacementQueue;
104 delete mChunkLoaderFactory;
108void QgsChunkedEntity::handleSceneUpdate(
const SceneState &state )
118 pruneLoaderQueue( state );
123 int oldJobsCount = pendingJobsCount();
125 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
126 mActiveNodes.clear();
128 mCurrentTime = QTime::currentTime();
130 update( mRootNode, state );
133 int enabled = 0, disabled = 0, unloaded = 0;
136 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
138 if ( activeBefore.contains( node ) )
140 activeBefore.remove( node );
144 if ( !node->entity() )
146 QgsDebugError(
"Active node has null entity - this should never happen!" );
149 node->entity()->setEnabled(
true );
157 for ( QgsChunkNode *node : activeBefore )
159 if ( !node->entity() )
161 QgsDebugError(
"Active node has null entity - this should never happen!" );
164 node->entity()->setEnabled(
false );
170 double usedGpuMemory = QgsChunkedEntity::calculateEntityGpuMemorySize(
this );
174 while ( usedGpuMemory > mGpuMemoryLimit )
176 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
177 usedGpuMemory -= QgsChunkedEntity::calculateEntityGpuMemorySize( entry->chunk->entity() );
178 entry->chunk->unloadChunk();
179 mActiveNodes.removeOne( entry->chunk );
187 QList<QgsAABB> bboxes;
188 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
190 mBboxesEntity->setBoxes( bboxes );
196 mNeedsUpdate =
false;
198 if ( pendingJobsCount() != oldJobsCount )
199 emit pendingJobsCountChanged();
201 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
204 .arg( mFrustumCulled )
205 .arg( mReplacementQueue->count() )
207 .arg( t.elapsed() ), 2 );
210QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange(
const QMatrix4x4 &viewMatrix )
const
212 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
217 if ( activeEntityNodes.empty() )
218 activeEntityNodes << rootNode();
223 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
228 for (
int i = 0; i < 8; ++i )
230 const QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.
xMin : bbox.
xMax,
231 ( ( i >> 1 ) & 1 ) ? bbox.
yMin : bbox.
yMax,
232 ( ( i >> 2 ) & 1 ) ? bbox.
zMin : bbox.
zMax, 1 );
234 const QVector4D pc = viewMatrix * p;
236 const float dst = -pc.z();
237 fnear = std::min( fnear, dst );
238 ffar = std::max( ffar, dst );
244void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
246 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
251 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
255 mBboxesEntity->deleteLater();
256 mBboxesEntity =
nullptr;
260void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
262 for ( QgsChunkNode *node : nodes )
264 if ( node->state() == QgsChunkNode::QueuedForUpdate )
266 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
267 node->cancelQueuedForUpdate();
269 else if ( node->state() == QgsChunkNode::Updating )
271 cancelActiveJob( node->updater() );
274 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
276 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
277 node->setQueuedForUpdate( entry, updateJobFactory );
278 mChunkLoaderQueue->insertLast( entry );
285void QgsChunkedEntity::pruneLoaderQueue(
const SceneState &state )
287 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
292 QgsChunkListEntry *e = mChunkLoaderQueue->first();
295 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
298 toRemoveFromLoaderQueue.append( e->chunk );
304 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
306 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
307 if ( n->state() == QgsChunkNode::QueuedForLoad )
309 n->cancelQueuedForLoad();
313 n->cancelQueuedForUpdate();
314 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
319 if ( !toRemoveFromLoaderQueue.isEmpty() )
321 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
326int QgsChunkedEntity::pendingJobsCount()
const
328 return mChunkLoaderQueue->count() + mActiveJobs.count();
331struct ResidencyRequest
333 QgsChunkNode *node =
nullptr;
336 ResidencyRequest() =
default;
349 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
351 if ( request.level == otherRequest.level )
352 return request.dist > otherRequest.dist;
353 return request.level > otherRequest.level;
355} ResidencyRequestSorter;
357void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneState &state )
359 QSet<QgsChunkNode *> nodes;
360 QVector<ResidencyRequest> residencyRequests;
362 using slotItem = std::pair<QgsChunkNode *, float>;
363 auto cmp_funct = [](
const slotItem & p1,
const slotItem & p2 )
365 return p1.second <= p2.second;
367 int renderedCount = 0;
368 std::priority_queue<slotItem, std::vector<slotItem>,
decltype( cmp_funct )> pq( cmp_funct );
369 pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
370 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
372 slotItem s = pq.top();
374 QgsChunkNode *node = s.first;
383 if ( node->childCount() == -1 )
384 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
388 double dist = node->bbox().center().distanceToPoint( state.cameraPos );
389 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
391 if ( !node->entity() )
396 bool becomesActive =
false;
399 if ( node->childCount() == 0 )
403 becomesActive =
true;
405 else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
408 becomesActive =
true;
417 if ( mAdditiveStrategy )
422 becomesActive =
true;
424 QgsChunkNode *
const *children = node->children();
425 for (
int i = 0; i < node->childCount(); ++i )
427 if ( children[i]->entity() || !children[i]->hasData() )
430 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
438 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
439 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
448 if ( node->allChildChunksResident( mCurrentTime ) )
450 QgsChunkNode *
const *children = node->children();
451 for (
int i = 0; i < node->childCount(); ++i )
452 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
456 becomesActive =
true;
458 QgsChunkNode *
const *children = node->children();
459 for (
int i = 0; i < node->childCount(); ++i )
461 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
462 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
470 mActiveNodes << node;
472 if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
474 nodes.remove( node->parent() );
475 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
477 renderedCount += mChunkLoaderFactory->primitivesCount( node );
478 nodes.insert( node );
483 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
484 for (
const auto &request : residencyRequests )
485 requestResidency( request.node );
488void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
490 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
492 Q_ASSERT( node->replacementQueueEntry() );
493 Q_ASSERT( node->entity() );
494 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
495 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
497 else if ( node->state() == QgsChunkNode::QueuedForLoad )
500 Q_ASSERT( node->loaderQueueEntry() );
501 Q_ASSERT( !node->loader() );
502 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
504 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
505 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
508 else if ( node->state() == QgsChunkNode::Loading )
512 else if ( node->state() == QgsChunkNode::Skeleton )
514 if ( !node->hasData() )
518 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
519 node->setQueuedForLoad( entry );
520 mChunkLoaderQueue->insertFirst( entry );
523 Q_ASSERT(
false &&
"impossible!" );
527void QgsChunkedEntity::onActiveJobFinished()
529 int oldJobsCount = pendingJobsCount();
531 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
533 Q_ASSERT( mActiveJobs.contains( job ) );
535 QgsChunkNode *node = job->chunk();
537 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
539 Q_ASSERT( node->state() == QgsChunkNode::Loading );
540 Q_ASSERT( node->loader() == loader );
542 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
543 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
545 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
547 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
557 mActiveNodes << node;
560 node->setLoaded( entity );
562 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
564 emit newEntityCreated( entity );
568 node->setHasData(
false );
569 node->cancelLoading();
577 Q_ASSERT( node->state() == QgsChunkNode::Updating );
578 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
583 mActiveJobs.removeOne( job );
589 if ( pendingJobsCount() != oldJobsCount )
590 emit pendingJobsCountChanged();
593void QgsChunkedEntity::startJobs()
595 while ( mActiveJobs.count() < 4 )
597 if ( mChunkLoaderQueue->isEmpty() )
600 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
602 QgsChunkNode *node = entry->chunk;
605 QgsChunkQueueJob *job = startJob( node );
606 mActiveJobs.append( job );
610QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
612 if ( node->state() == QgsChunkNode::QueuedForLoad )
614 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
615 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
617 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
618 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
619 node->setLoading( loader );
622 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
624 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
627 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
628 return node->updater();
637void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
641 QgsChunkNode *node = job->chunk();
643 if ( qobject_cast<QgsChunkLoader *>( job ) )
646 node->cancelLoading();
648 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
649 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
654 node->cancelUpdating();
656 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
660 mActiveJobs.removeOne( job );
664void QgsChunkedEntity::cancelActiveJobs()
666 while ( !mActiveJobs.isEmpty() )
668 cancelActiveJob( mActiveJobs.takeFirst() );
672double QgsChunkedEntity::calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity )
674 long long usedGpuMemory = 0;
677 usedGpuMemory += buffer->data().size();
679 for ( Qt3DRender::QTexture2D *tex : entity->findChildren<Qt3DRender::QTexture2D *>() )
682 usedGpuMemory += tex->width() * tex->height() * 4;
684 return usedGpuMemory / 1024.0 / 1024.0;
687QVector<QgsRayCastingUtils::RayHit> QgsChunkedEntity::rayIntersection(
const QgsRayCastingUtils::Ray3D &ray,
const QgsRayCastingUtils::RayCastContext &context )
const
691 return QVector<QgsRayCastingUtils::RayHit>();
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, float screenSize, float fov)
This routine approximately calculates how an error (epsilon) of an object in world coordinates at giv...
A template based class for storing ranges (lower to upper values).
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QBuffer Qt3DQBuffer
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)