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;