18 #include <QElapsedTimer>
20 #include <Qt3DRender/QObjectPicker>
21 #include <Qt3DRender/QPickTriangleEvent>
22 #include <Qt3DRender/QBuffer>
37 static float screenSpaceError(
float epsilon,
float distance,
float screenSize,
float fov )
59 float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
63 static float screenSpaceError( QgsChunkNode *node,
const QgsChunkedEntity::SceneState &state )
65 if ( node->error() <= 0 )
68 float dist = node->bbox().distanceFromPoint( state.cameraPos );
72 float sse = screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
76 QgsChunkedEntity::QgsChunkedEntity(
float tau, QgsChunkLoaderFactory *loaderFactory,
bool ownsFactory,
int primitiveBudget, Qt3DCore::QNode *parent )
79 , mChunkLoaderFactory( loaderFactory )
80 , mOwnsFactory( ownsFactory )
81 , mPrimitivesBudget( primitiveBudget )
83 mRootNode = loaderFactory->createRootNode();
84 mChunkLoaderQueue =
new QgsChunkList;
85 mReplacementQueue =
new QgsChunkList;
89 QgsChunkedEntity::~QgsChunkedEntity()
94 Q_ASSERT( mActiveJobs.isEmpty() );
97 while ( !mChunkLoaderQueue->isEmpty() )
99 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
100 QgsChunkNode *node = entry->chunk;
102 if ( node->state() == QgsChunkNode::QueuedForLoad )
103 node->cancelQueuedForLoad();
104 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
105 node->cancelQueuedForUpdate();
110 delete mChunkLoaderQueue;
112 while ( !mReplacementQueue->isEmpty() )
114 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
117 entry->chunk->unloadChunk();
120 delete mReplacementQueue;
125 delete mChunkLoaderFactory;
130 void QgsChunkedEntity::update(
const SceneState &state )
140 pruneLoaderQueue( state );
145 int oldJobsCount = pendingJobsCount();
147 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
148 mActiveNodes.clear();
150 mCurrentTime = QTime::currentTime();
152 update( mRootNode, state );
154 int enabled = 0, disabled = 0, unloaded = 0;
156 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
158 if ( activeBefore.contains( node ) )
160 activeBefore.remove( node );
164 if ( !node->entity() )
166 QgsDebugMsg(
"Active node has null entity - this should never happen!" );
169 node->entity()->setEnabled(
true );
175 for ( QgsChunkNode *node : activeBefore )
177 if ( !node->entity() )
179 QgsDebugMsg(
"Active node has null entity - this should never happen!" );
182 node->entity()->setEnabled(
false );
186 double usedGpuMemory = QgsChunkedEntity::calculateEntityGpuMemorySize(
this );
190 while ( usedGpuMemory > mGpuMemoryLimit )
192 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
193 usedGpuMemory -= QgsChunkedEntity::calculateEntityGpuMemorySize( entry->chunk->entity() );
194 entry->chunk->unloadChunk();
195 mActiveNodes.removeOne( entry->chunk );
201 QList<QgsAABB> bboxes;
202 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
204 mBboxesEntity->setBoxes( bboxes );
210 mNeedsUpdate =
false;
212 if ( pendingJobsCount() != oldJobsCount )
213 emit pendingJobsCountChanged();
215 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
218 .arg( mFrustumCulled )
219 .arg( mReplacementQueue->count() )
221 .arg( t.elapsed() ), 2 );
224 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
226 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
231 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
235 mBboxesEntity->deleteLater();
236 mBboxesEntity =
nullptr;
240 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
242 for ( QgsChunkNode *node : nodes )
244 if ( node->state() == QgsChunkNode::QueuedForUpdate )
246 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
247 node->cancelQueuedForUpdate();
249 else if ( node->state() == QgsChunkNode::Updating )
251 cancelActiveJob( node->updater() );
254 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
256 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
257 node->setQueuedForUpdate( entry, updateJobFactory );
258 mChunkLoaderQueue->insertLast( entry );
265 void QgsChunkedEntity::pruneLoaderQueue(
const SceneState &state )
267 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
272 QgsChunkListEntry *e = mChunkLoaderQueue->first();
275 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
278 toRemoveFromLoaderQueue.append( e->chunk );
284 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
286 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
287 if ( n->state() == QgsChunkNode::QueuedForLoad )
289 n->cancelQueuedForLoad();
293 n->cancelQueuedForUpdate();
294 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
299 if ( !toRemoveFromLoaderQueue.isEmpty() )
301 QgsDebugMsgLevel( QStringLiteral(
"Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
306 int QgsChunkedEntity::pendingJobsCount()
const
308 return mChunkLoaderQueue->count() + mActiveJobs.count();
311 struct ResidencyRequest
313 QgsChunkNode *node =
nullptr;
316 ResidencyRequest() =
default;
329 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
331 if ( request.level == otherRequest.level )
332 return request.dist > otherRequest.dist;
333 return request.level > otherRequest.level;
335 } ResidencyRequestSorter;
337 void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneState &state )
339 QSet<QgsChunkNode *> nodes;
340 QVector<ResidencyRequest> residencyRequests;
342 using slotItem = std::pair<QgsChunkNode *, float>;
343 auto cmp_funct = [](
const slotItem & p1,
const slotItem & p2 )
345 return p1.second <= p2.second;
347 int renderedCount = 0;
348 std::priority_queue<slotItem, std::vector<slotItem>, decltype( cmp_funct )> pq( cmp_funct );
349 pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
350 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
352 slotItem s = pq.top();
354 QgsChunkNode *node = s.first;
363 if ( node->childCount() == -1 )
364 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
368 double dist = node->bbox().center().distanceToPoint( state.cameraPos );
369 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
371 if ( !node->entity() )
376 bool becomesActive =
false;
379 if ( node->childCount() == 0 )
383 becomesActive =
true;
385 else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
388 becomesActive =
true;
397 if ( mAdditiveStrategy )
402 becomesActive =
true;
404 QgsChunkNode *
const *children = node->children();
405 for (
int i = 0; i < node->childCount(); ++i )
407 if ( children[i]->entity() || !children[i]->hasData() )
410 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
418 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
419 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
428 if ( node->allChildChunksResident( mCurrentTime ) )
430 QgsChunkNode *
const *children = node->children();
431 for (
int i = 0; i < node->childCount(); ++i )
432 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
436 becomesActive =
true;
438 QgsChunkNode *
const *children = node->children();
439 for (
int i = 0; i < node->childCount(); ++i )
441 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
442 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
450 mActiveNodes << node;
452 if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
454 nodes.remove( node->parent() );
455 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
457 renderedCount += mChunkLoaderFactory->primitivesCount( node );
458 nodes.insert( node );
463 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
464 for (
const auto &request : residencyRequests )
465 requestResidency( request.node );
468 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
470 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
472 Q_ASSERT( node->replacementQueueEntry() );
473 Q_ASSERT( node->entity() );
474 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
475 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
477 else if ( node->state() == QgsChunkNode::QueuedForLoad )
480 Q_ASSERT( node->loaderQueueEntry() );
481 Q_ASSERT( !node->loader() );
482 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
484 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
485 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
488 else if ( node->state() == QgsChunkNode::Loading )
492 else if ( node->state() == QgsChunkNode::Skeleton )
494 if ( !node->hasData() )
498 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
499 node->setQueuedForLoad( entry );
500 mChunkLoaderQueue->insertFirst( entry );
503 Q_ASSERT(
false &&
"impossible!" );
507 void QgsChunkedEntity::onActiveJobFinished()
509 int oldJobsCount = pendingJobsCount();
511 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
513 Q_ASSERT( mActiveJobs.contains( job ) );
515 QgsChunkNode *node = job->chunk();
517 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
519 Q_ASSERT( node->state() == QgsChunkNode::Loading );
520 Q_ASSERT( node->loader() == loader );
522 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
523 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
525 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
527 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
532 node->setLoaded( entity );
534 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
536 if ( mPickingEnabled )
538 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
539 node->entity()->addComponent( picker );
540 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
543 emit newEntityCreated( entity );
547 node->setHasData(
false );
548 node->cancelLoading();
556 Q_ASSERT( node->state() == QgsChunkNode::Updating );
557 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
562 mActiveJobs.removeOne( job );
568 if ( pendingJobsCount() != oldJobsCount )
569 emit pendingJobsCountChanged();
572 void QgsChunkedEntity::startJobs()
574 while ( mActiveJobs.count() < 4 )
576 if ( mChunkLoaderQueue->isEmpty() )
579 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
581 QgsChunkNode *node = entry->chunk;
584 QgsChunkQueueJob *job = startJob( node );
585 mActiveJobs.append( job );
589 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
591 if ( node->state() == QgsChunkNode::QueuedForLoad )
593 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
594 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
596 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
597 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
598 node->setLoading( loader );
601 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
603 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
606 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
607 return node->updater();
616 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
620 QgsChunkNode *node = job->chunk();
622 if ( qobject_cast<QgsChunkLoader *>( job ) )
625 node->cancelLoading();
627 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
628 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
633 node->cancelUpdating();
635 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
639 mActiveJobs.removeOne( job );
643 void QgsChunkedEntity::cancelActiveJobs()
645 while ( !mActiveJobs.isEmpty() )
647 cancelActiveJob( mActiveJobs.takeFirst() );
652 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
654 if ( mPickingEnabled == enabled )
657 mPickingEnabled = enabled;
661 QgsChunkListEntry *entry = mReplacementQueue->first();
664 QgsChunkNode *node = entry->chunk;
665 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
666 node->entity()->addComponent( picker );
667 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
674 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
675 picker->deleteLater();
679 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
681 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
682 if ( !triangleEvent )
685 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
689 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
695 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
700 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
705 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
713 emit pickedObject( event, fid );
717 double QgsChunkedEntity::calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity )
719 long long usedGpuMemory = 0;
720 for ( Qt3DRender::QBuffer *buffer : entity->findChildren<Qt3DRender::QBuffer *>() )
722 usedGpuMemory += buffer->data().size();
724 for ( Qt3DRender::QTexture2D *tex : entity->findChildren<Qt3DRender::QTexture2D *>() )
727 usedGpuMemory += tex->width() * tex->height() * 4;
729 return usedGpuMemory / 1024.0 / 1024.0;