QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgschunkedentity_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgschunkedentity_p.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgschunkedentity_p.h"
17
18#include <QElapsedTimer>
19#include <QVector4D>
20
21#include "qgs3dutils.h"
23#include "qgschunklist_p.h"
24#include "qgschunkloader_p.h"
25#include "qgschunknode_p.h"
26
27#include "qgseventtracing.h"
28
29#include <queue>
30
32
33
34static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneContext &sceneContext )
35{
36 if ( node->error() <= 0 ) //it happens for meshes
37 return 0;
38
39 float dist = node->bbox().distanceFromPoint( sceneContext.cameraPos );
40
41 // TODO: what to do when distance == 0 ?
42
43 float sse = Qgs3DUtils::screenSpaceError( node->error(), dist, sceneContext.screenSizePx, sceneContext.cameraFov );
44 return sse;
45}
46
47
48static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
49{
50 for ( int i = 0; i < node->childCount(); ++i )
51 {
52 QgsChunkNode *child = node->children()[i];
53 if ( child->entity() && activeNodes.contains( child ) )
54 return true;
55 if ( hasAnyActiveChildren( child, activeNodes ) )
56 return true;
57 }
58 return false;
59}
60
61
62QgsChunkedEntity::QgsChunkedEntity( float tau, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, int primitiveBudget, Qt3DCore::QNode *parent )
63 : Qgs3DMapSceneEntity( parent )
64 , mTau( tau )
65 , mChunkLoaderFactory( loaderFactory )
66 , mOwnsFactory( ownsFactory )
67 , mPrimitivesBudget( primitiveBudget )
68{
69 mRootNode = loaderFactory->createRootNode();
70 mChunkLoaderQueue = new QgsChunkList;
71 mReplacementQueue = new QgsChunkList;
72
73 // in case the chunk loader factory supports fetching of hierarchy in background (to avoid GUI freezes)
74 connect( loaderFactory, &QgsChunkLoaderFactory::childrenPrepared, this, [this]
75 {
76 setNeedsUpdate( true );
77 emit pendingJobsCountChanged();
78 } );
79}
80
81
82QgsChunkedEntity::~QgsChunkedEntity()
83{
84 // derived classes have to make sure that any pending active job has finished / been canceled
85 // before getting to this destructor - here it would be too late to cancel them
86 // (e.g. objects required for loading/updating have been deleted already)
87 Q_ASSERT( mActiveJobs.isEmpty() );
88
89 // clean up any pending load requests
90 while ( !mChunkLoaderQueue->isEmpty() )
91 {
92 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
93 QgsChunkNode *node = entry->chunk;
94
95 if ( node->state() == QgsChunkNode::QueuedForLoad )
96 node->cancelQueuedForLoad();
97 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
98 node->cancelQueuedForUpdate();
99 else
100 Q_ASSERT( false ); // impossible!
101 }
102
103 delete mChunkLoaderQueue;
104
105 while ( !mReplacementQueue->isEmpty() )
106 {
107 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
108
109 // remove loaded data from node
110 entry->chunk->unloadChunk(); // also deletes the entry
111 }
112
113 delete mReplacementQueue;
114 delete mRootNode;
115
116 if ( mOwnsFactory )
117 {
118 delete mChunkLoaderFactory;
119 }
120}
121
122
123void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext )
124{
125 if ( !mIsValid )
126 return;
127
128 // Let's start the update by removing from loader queue chunks that
129 // would get frustum culled if loaded (outside of the current view
130 // of the camera). Removing them keeps the loading queue shorter,
131 // and we avoid loading chunks that we only wanted for a short period
132 // of time when camera was moving.
133 pruneLoaderQueue( sceneContext );
134
135 QElapsedTimer t;
136 t.start();
137
138 int oldJobsCount = pendingJobsCount();
139
140 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
141 mActiveNodes.clear();
142 mFrustumCulled = 0;
143 mCurrentTime = QTime::currentTime();
144
145 update( mRootNode, sceneContext );
146
147#ifdef QGISDEBUG
148 int enabled = 0, disabled = 0, unloaded = 0;
149#endif
150
151 for ( QgsChunkNode *node : std::as_const( mActiveNodes ) )
152 {
153 if ( activeBefore.contains( node ) )
154 {
155 activeBefore.remove( node );
156 }
157 else
158 {
159 if ( !node->entity() )
160 {
161 QgsDebugError( "Active node has null entity - this should never happen!" );
162 continue;
163 }
164 node->entity()->setEnabled( true );
165#ifdef QGISDEBUG
166 ++enabled;
167#endif
168 }
169 }
170
171 // disable those that were active but will not be anymore
172 for ( QgsChunkNode *node : activeBefore )
173 {
174 if ( !node->entity() )
175 {
176 QgsDebugError( "Active node has null entity - this should never happen!" );
177 continue;
178 }
179 node->entity()->setEnabled( false );
180#ifdef QGISDEBUG
181 ++disabled;
182#endif
183 }
184
185 // if this entity's loaded nodes are using more GPU memory than allowed,
186 // let's try to unload those that are not needed right now
187#ifdef QGISDEBUG
188 unloaded = unloadNodes();
189#else
190 unloadNodes();
191#endif
192
193 if ( mBboxesEntity )
194 {
195 QList<QgsAABB> bboxes;
196 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
197 bboxes << n->bbox();
198 mBboxesEntity->setBoxes( bboxes );
199 }
200
201 // start a job from queue if there is anything waiting
202 startJobs();
203
204 mNeedsUpdate = false; // just updated
205
206 if ( pendingJobsCount() != oldJobsCount )
207 emit pendingJobsCountChanged();
208
209 QgsDebugMsgLevel( QStringLiteral( "update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
210 .arg( enabled )
211 .arg( disabled )
212 .arg( mFrustumCulled )
213 .arg( mChunkLoaderQueue->count() )
214 .arg( mReplacementQueue->count() )
215 .arg( unloaded )
216 .arg( t.elapsed() ), 2 );
217}
218
219
220int QgsChunkedEntity::unloadNodes()
221{
222 double usedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this );
223 if ( usedGpuMemory <= mGpuMemoryLimit )
224 {
225 setHasReachedGpuMemoryLimit( false );
226 return 0;
227 }
228
229 QgsDebugMsgLevel( QStringLiteral( "Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
230
231 int unloaded = 0;
232
233 // unload nodes starting from the back of the queue with currently loaded
234 // nodes - i.e. those that have been least recently used
235 QgsChunkListEntry *entry = mReplacementQueue->last();
236 while ( entry && usedGpuMemory > mGpuMemoryLimit )
237 {
238 // not all nodes are safe to unload: we do not want to unload nodes
239 // that are currently active, or have their descendants active or their
240 // siblings or their descendants are active (because in the next scene
241 // update, these would be very likely loaded again, making the unload worthless)
242 if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
243 {
244 QgsChunkListEntry *entryPrev = entry->prev;
245 mReplacementQueue->takeEntry( entry );
246 usedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() );
247 mActiveNodes.removeOne( entry->chunk );
248 entry->chunk->unloadChunk(); // also deletes the entry
249 ++unloaded;
250 entry = entryPrev;
251 }
252 else
253 {
254 entry = entry->prev;
255 }
256 }
257
258 if ( usedGpuMemory > mGpuMemoryLimit )
259 {
260 setHasReachedGpuMemoryLimit( true );
261 QgsDebugMsgLevel( QStringLiteral( "Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
262 }
263
264 return unloaded;
265}
266
267
268QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
269{
270 QList<QgsChunkNode *> activeEntityNodes = activeNodes();
271
272 // it could be that there are no active nodes - they could be all culled or because root node
273 // is not yet loaded - we still need at least something to understand bounds of our scene
274 // so lets use the root node
275 if ( activeEntityNodes.empty() )
276 activeEntityNodes << rootNode();
277
278 float fnear = 1e9;
279 float ffar = 0;
280
281 for ( QgsChunkNode *node : std::as_const( activeEntityNodes ) )
282 {
283 // project each corner of bbox to camera coordinates
284 // and determine closest and farthest point.
285 QgsAABB bbox = node->bbox();
286 float bboxfnear;
287 float bboxffar;
288 Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar );
289 fnear = std::min( fnear, bboxfnear );
290 ffar = std::max( ffar, bboxffar );
291 }
292 return QgsRange<float>( fnear, ffar );
293}
294
295void QgsChunkedEntity::setShowBoundingBoxes( bool enabled )
296{
297 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
298 return;
299
300 if ( enabled )
301 {
302 mBboxesEntity = new QgsChunkBoundsEntity( this );
303 }
304 else
305 {
306 mBboxesEntity->deleteLater();
307 mBboxesEntity = nullptr;
308 }
309}
310
311void QgsChunkedEntity::updateNodes( const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
312{
313 for ( QgsChunkNode *node : nodes )
314 {
315 if ( node->state() == QgsChunkNode::QueuedForUpdate )
316 {
317 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
318 node->cancelQueuedForUpdate();
319 }
320 else if ( node->state() == QgsChunkNode::Updating )
321 {
322 cancelActiveJob( node->updater() );
323 }
324
325 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
326
327 QgsChunkListEntry *entry = new QgsChunkListEntry( node );
328 node->setQueuedForUpdate( entry, updateJobFactory );
329 mChunkLoaderQueue->insertLast( entry );
330 }
331
332 // trigger update
333 startJobs();
334}
335
336void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext )
337{
338 QList<QgsChunkNode *> toRemoveFromLoaderQueue;
339
340 // Step 1: collect all entries from chunk loader queue that would get frustum culled
341 // (i.e. they are outside of the current view of the camera) and therefore loading
342 // such chunks would be probably waste of time.
343 QgsChunkListEntry *e = mChunkLoaderQueue->first();
344 while ( e )
345 {
346 Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate );
347 if ( Qgs3DUtils::isCullable( e->chunk->bbox(), sceneContext.viewProjectionMatrix ) )
348 {
349 toRemoveFromLoaderQueue.append( e->chunk );
350 }
351 e = e->next;
352 }
353
354 // Step 2: remove collected chunks from the loading queue
355 for ( QgsChunkNode *n : toRemoveFromLoaderQueue )
356 {
357 mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() );
358 if ( n->state() == QgsChunkNode::QueuedForLoad )
359 {
360 n->cancelQueuedForLoad();
361 }
362 else // queued for update
363 {
364 n->cancelQueuedForUpdate();
365 mReplacementQueue->takeEntry( n->replacementQueueEntry() );
366 n->unloadChunk();
367 }
368 }
369
370 if ( !toRemoveFromLoaderQueue.isEmpty() )
371 {
372 QgsDebugMsgLevel( QStringLiteral( "Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 );
373 }
374}
375
376
377int QgsChunkedEntity::pendingJobsCount() const
378{
379 return mChunkLoaderQueue->count() + mActiveJobs.count();
380}
381
382struct ResidencyRequest
383{
384 QgsChunkNode *node = nullptr;
385 float dist = 0.0;
386 int level = -1;
387 ResidencyRequest() = default;
388 ResidencyRequest(
389 QgsChunkNode *n,
390 float d,
391 int l )
392 : node( n )
393 , dist( d )
394 , level( l )
395 {}
396};
397
398struct
399{
400 bool operator()( const ResidencyRequest &request, const ResidencyRequest &otherRequest ) const
401 {
402 if ( request.level == otherRequest.level )
403 return request.dist > otherRequest.dist;
404 return request.level > otherRequest.level;
405 }
406} ResidencyRequestSorter;
407
408void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneContext )
409{
410 QSet<QgsChunkNode *> nodes;
411 QVector<ResidencyRequest> residencyRequests;
412
413 using slotItem = std::pair<QgsChunkNode *, float>;
414 auto cmp_funct = []( const slotItem & p1, const slotItem & p2 )
415 {
416 return p1.second <= p2.second;
417 };
418 int renderedCount = 0;
419 std::priority_queue<slotItem, std::vector<slotItem>, decltype( cmp_funct )> pq( cmp_funct );
420 pq.push( std::make_pair( root, screenSpaceError( root, sceneContext ) ) );
421 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
422 {
423 slotItem s = pq.top();
424 pq.pop();
425 QgsChunkNode *node = s.first;
426
427 if ( Qgs3DUtils::isCullable( node->bbox(), sceneContext.viewProjectionMatrix ) )
428 {
429 ++mFrustumCulled;
430 continue;
431 }
432
433 // ensure we have child nodes (at least skeletons) available, if any
434 if ( !node->hasChildrenPopulated() )
435 {
436 // Some chunked entities (e.g. tiled scene) may not know the full node hierarchy in advance
437 // and need to fetch it from a remote server. Having a blocking network request
438 // in createChildren() is not wanted because this code runs on the main thread and thus
439 // would cause GUI freezes. Here is a mechanism to first check whether there are any
440 // network requests needed (with canCreateChildren()), and if that's the case,
441 // prepareChildren() will start those requests in the background and immediately returns.
442 // The factory will emit a signal when hierarchy fetching is done to force another update
443 // of this entity to create children of this node.
444 if ( mChunkLoaderFactory->canCreateChildren( node ) )
445 {
446 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
447 }
448 else
449 {
450 mChunkLoaderFactory->prepareChildren( node );
451 }
452 }
453
454 // make sure all nodes leading to children are always loaded
455 // so that zooming out does not create issues
456 double dist = node->bbox().center().distanceToPoint( sceneContext.cameraPos );
457 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
458
459 if ( !node->entity() )
460 {
461 // this happens initially when root node is not ready yet
462 continue;
463 }
464 bool becomesActive = false;
465
466 // QgsDebugMsgLevel( QStringLiteral( "%1|%2|%3 %4 %5" ).arg( node->tileId().x ).arg( node->tileId().y ).arg( node->tileId().z ).arg( mTau ).arg( screenSpaceError( node, sceneContext ) ), 2 );
467 if ( node->childCount() == 0 )
468 {
469 // there's no children available for this node, so regardless of whether it has an acceptable error
470 // or not, it's the best we'll ever get...
471 becomesActive = true;
472 }
473 else if ( mTau > 0 && screenSpaceError( node, sceneContext ) <= mTau )
474 {
475 // acceptable error for the current chunk - let's render it
476 becomesActive = true;
477 }
478 else
479 {
480 // This chunk does not have acceptable error (it does not provide enough detail)
481 // so we'll try to use its children. The exact logic depends on whether the entity
482 // has additive strategy. With additive strategy, child nodes should be rendered
483 // in addition to the parent nodes (rather than child nodes replacing parent entirely)
484
485 if ( node->refinementProcess() == Qgis::TileRefinementProcess::Additive )
486 {
487 // Logic of the additive strategy:
488 // - children that are not loaded will get requested to be loaded
489 // - children that are already loaded get recursively visited
490 becomesActive = true;
491
492 QgsChunkNode *const *children = node->children();
493 for ( int i = 0; i < node->childCount(); ++i )
494 {
495 if ( children[i]->entity() || !children[i]->hasData() )
496 {
497 // chunk is resident - let's visit it recursively
498 pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) );
499 }
500 else
501 {
502 // chunk is not yet resident - let's try to load it
503 if ( Qgs3DUtils::isCullable( children[i]->bbox(), sceneContext.viewProjectionMatrix ) )
504 continue;
505
506 double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos );
507 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
508 }
509 }
510 }
511 else
512 {
513 // Logic of the replace strategy:
514 // - if we have all children loaded, we use them instead of the parent node
515 // - if we do not have all children loaded, we request to load them and keep using the parent for the time being
516 if ( node->allChildChunksResident( mCurrentTime ) )
517 {
518 QgsChunkNode *const *children = node->children();
519 for ( int i = 0; i < node->childCount(); ++i )
520 pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) );
521 }
522 else
523 {
524 becomesActive = true;
525
526 QgsChunkNode *const *children = node->children();
527 for ( int i = 0; i < node->childCount(); ++i )
528 {
529 double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos );
530 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
531 }
532 }
533 }
534 }
535
536 if ( becomesActive )
537 {
538 mActiveNodes << node;
539 // if we are not using additive strategy we need to make sure the parent primitives are not counted
540 if ( node->refinementProcess() != Qgis::TileRefinementProcess::Additive && node->parent() && nodes.contains( node->parent() ) )
541 {
542 nodes.remove( node->parent() );
543 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
544 }
545 renderedCount += mChunkLoaderFactory->primitivesCount( node );
546 nodes.insert( node );
547 }
548 }
549
550 // sort nodes by their level and their distance from the camera
551 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
552 for ( const auto &request : residencyRequests )
553 requestResidency( request.node );
554}
555
556void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
557{
558 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
559 {
560 Q_ASSERT( node->replacementQueueEntry() );
561 Q_ASSERT( node->entity() );
562 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
563 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
564 }
565 else if ( node->state() == QgsChunkNode::QueuedForLoad )
566 {
567 // move to the front of loading queue
568 Q_ASSERT( node->loaderQueueEntry() );
569 Q_ASSERT( !node->loader() );
570 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
571 {
572 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
573 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
574 }
575 }
576 else if ( node->state() == QgsChunkNode::Loading )
577 {
578 // the entry is being currently processed - nothing to do really
579 }
580 else if ( node->state() == QgsChunkNode::Skeleton )
581 {
582 if ( !node->hasData() )
583 return; // no need to load (we already tried but got nothing back)
584
585 // add to the loading queue
586 QgsChunkListEntry *entry = new QgsChunkListEntry( node );
587 node->setQueuedForLoad( entry );
588 mChunkLoaderQueue->insertFirst( entry );
589 }
590 else
591 Q_ASSERT( false && "impossible!" );
592}
593
594
595void QgsChunkedEntity::onActiveJobFinished()
596{
597 int oldJobsCount = pendingJobsCount();
598
599 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
600 Q_ASSERT( job );
601 Q_ASSERT( mActiveJobs.contains( job ) );
602
603 QgsChunkNode *node = job->chunk();
604
605 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
606 {
607 Q_ASSERT( node->state() == QgsChunkNode::Loading );
608 Q_ASSERT( node->loader() == loader );
609
610 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Load " ) + node->tileId().text(), node->tileId().text() );
611 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Load" ), node->tileId().text() );
612
613 QgsEventTracing::ScopedEvent e( "3D", QString( "create" ) );
614 // mark as loaded + create entity
615 Qt3DCore::QEntity *entity = node->loader()->createEntity( this );
616
617 if ( entity )
618 {
619 // The returned QEntity is initially enabled, so let's add it to active nodes too.
620 // Soon afterwards updateScene() will be called, which would remove it from the scene
621 // if the node should not be shown anymore. Ideally entities should be initially disabled,
622 // but there seems to be a bug in Qt3D - if entity is disabled initially, showing it
623 // by setting setEnabled(true) is not reliable (entity eventually gets shown, but only after
624 // some more changes in the scene) - see https://github.com/qgis/QGIS/issues/48334
625 mActiveNodes << node;
626
627 // load into node (should be in main thread again)
628 node->setLoaded( entity );
629
630 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
631
632 emit newEntityCreated( entity );
633 }
634 else
635 {
636 node->setHasData( false );
637 node->cancelLoading();
638 }
639
640 // now we need an update!
641 mNeedsUpdate = true;
642 }
643 else
644 {
645 Q_ASSERT( node->state() == QgsChunkNode::Updating );
646 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Update" ), node->tileId().text() );
647 node->setUpdated();
648 }
649
650 // cleanup the job that has just finished
651 mActiveJobs.removeOne( job );
652 job->deleteLater();
653
654 // start another job - if any
655 startJobs();
656
657 if ( pendingJobsCount() != oldJobsCount )
658 emit pendingJobsCountChanged();
659}
660
661void QgsChunkedEntity::startJobs()
662{
663 while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() )
664 {
665 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
666 Q_ASSERT( entry );
667 QgsChunkNode *node = entry->chunk;
668 delete entry;
669
670 QgsChunkQueueJob *job = startJob( node );
671 mActiveJobs.append( job );
672 }
673}
674
675QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
676{
677 if ( node->state() == QgsChunkNode::QueuedForLoad )
678 {
679 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "Load" ), node->tileId().text() );
680 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "Load " ) + node->tileId().text(), node->tileId().text() );
681
682 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
683 connect( loader, &QgsChunkQueueJob::finished, this, &QgsChunkedEntity::onActiveJobFinished );
684 node->setLoading( loader );
685 return loader;
686 }
687 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
688 {
689 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "Update" ), node->tileId().text() );
690
691 node->setUpdating();
692 connect( node->updater(), &QgsChunkQueueJob::finished, this, &QgsChunkedEntity::onActiveJobFinished );
693 return node->updater();
694 }
695 else
696 {
697 Q_ASSERT( false ); // not possible
698 return nullptr;
699 }
700}
701
702void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
703{
704 Q_ASSERT( job );
705
706 QgsChunkNode *node = job->chunk();
707 disconnect( job, &QgsChunkQueueJob::finished, this, &QgsChunkedEntity::onActiveJobFinished );
708
709 if ( qobject_cast<QgsChunkLoader *>( job ) )
710 {
711 // return node back to skeleton
712 node->cancelLoading();
713
714 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Load " ) + node->tileId().text(), node->tileId().text() );
715 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Load" ), node->tileId().text() );
716 }
717 else
718 {
719 // return node back to loaded state
720 node->cancelUpdating();
721
722 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Update" ), node->tileId().text() );
723 }
724
725 job->cancel();
726 mActiveJobs.removeOne( job );
727 job->deleteLater();
728}
729
730void QgsChunkedEntity::cancelActiveJobs()
731{
732 while ( !mActiveJobs.isEmpty() )
733 {
734 cancelActiveJob( mActiveJobs.takeFirst() );
735 }
736}
737
738
739QVector<QgsRayCastingUtils::RayHit> QgsChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const
740{
741 Q_UNUSED( ray )
742 Q_UNUSED( context )
743 return QVector<QgsRayCastingUtils::RayHit>();
744}
745
@ Additive
When tile is refined its content should be used alongside its children simultaneously.
static double calculateEntityGpuMemorySize(Qt3DCore::QEntity *entity)
Calculates approximate usage of GPU memory by an entity.
Definition: qgs3dutils.cpp:155
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume.
Definition: qgs3dutils.cpp:561
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...
Definition: qgs3dutils.cpp:849
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 ...
Definition: qgs3dutils.cpp:875
3
Definition: qgsaabb.h:33
A template based class for storing ranges (lower to upper values).
Definition: qgsrange.h:46
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38