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