QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
Loading...
Searching...
No Matches
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.
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 ...
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