Skip to content

Commit 43105b3

Browse files
wonder-sknyalldawson
authored andcommittedAug 17, 2023
Fix unloading strategy when we hit GPU memory limit for entity
Previously we would remove any nodes that were at the end of the replacement queue, which was causing some bad behavior with nearly infinite loops unloading and reloading chunks. The strategy has been updated to only remove nodes that we know will not be requested again in the next scene update.
1 parent 726171c commit 43105b3

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed
 

‎src/3d/chunks/qgschunkedentity_p.cpp

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,21 @@ static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::Scene
5151
return sse;
5252
}
5353

54+
55+
static bool hasAnyActiveChildren( QgsChunkNode *node, QList<QgsChunkNode *> &activeNodes )
56+
{
57+
for ( int i = 0; i < node->childCount(); ++i )
58+
{
59+
QgsChunkNode *child = node->children()[i];
60+
if ( child->entity() && activeNodes.contains( child ) )
61+
return true;
62+
if ( hasAnyActiveChildren( child, activeNodes ) )
63+
return true;
64+
}
65+
return false;
66+
}
67+
68+
5469
QgsChunkedEntity::QgsChunkedEntity( float tau, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, int primitiveBudget, Qt3DCore::QNode *parent )
5570
: Qgs3DMapSceneEntity( parent )
5671
, mTau( tau )
@@ -111,6 +126,7 @@ QgsChunkedEntity::~QgsChunkedEntity()
111126
}
112127
}
113128

129+
114130
void QgsChunkedEntity::handleSceneUpdate( const SceneState &state )
115131
{
116132
if ( !mIsValid )
@@ -173,20 +189,13 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneState &state )
173189
#endif
174190
}
175191

176-
double usedGpuMemory = QgsChunkedEntity::calculateEntityGpuMemorySize( this );
177-
178-
// unload those that are over the limit for replacement
179-
// TODO: what to do when our cache is too small and nodes are being constantly evicted + loaded again
180-
while ( usedGpuMemory > mGpuMemoryLimit )
181-
{
182-
QgsChunkListEntry *entry = mReplacementQueue->takeLast();
183-
usedGpuMemory -= QgsChunkedEntity::calculateEntityGpuMemorySize( entry->chunk->entity() );
184-
mActiveNodes.removeOne( entry->chunk );
185-
entry->chunk->unloadChunk(); // also deletes the entry
192+
// if this entity's loaded nodes are using more GPU memory than allowed,
193+
// let's try to unload those that are not needed right now
186194
#ifdef QGISDEBUG
187-
++unloaded;
195+
unloaded = unloadNodes();
196+
#else
197+
unloadNodes();
188198
#endif
189-
}
190199

191200
if ( mBboxesEntity )
192201
{
@@ -214,6 +223,51 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneState &state )
214223
.arg( t.elapsed() ), 2 );
215224
}
216225

226+
227+
int QgsChunkedEntity::unloadNodes()
228+
{
229+
double usedGpuMemory = QgsChunkedEntity::calculateEntityGpuMemorySize( this );
230+
if ( usedGpuMemory <= mGpuMemoryLimit )
231+
return 0;
232+
233+
QgsDebugMsgLevel( QStringLiteral( "Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
234+
235+
int unloaded = 0;
236+
237+
// unload nodes starting from the back of the queue with currently loaded
238+
// nodes - i.e. those that have been least recently used
239+
QgsChunkListEntry *entry = mReplacementQueue->last();
240+
while ( entry && usedGpuMemory > mGpuMemoryLimit )
241+
{
242+
// not all nodes are safe to unload: we do not want to unload nodes
243+
// that are currently active, or have their descendants active or their
244+
// siblings or their descendants are active (because in the next scene
245+
// update, these would be very likely loaded again, making the unload worthless)
246+
if ( entry->chunk->parent() && !hasAnyActiveChildren( entry->chunk->parent(), mActiveNodes ) )
247+
{
248+
QgsChunkListEntry *entryPrev = entry->prev;
249+
mReplacementQueue->takeEntry( entry );
250+
usedGpuMemory -= QgsChunkedEntity::calculateEntityGpuMemorySize( entry->chunk->entity() );
251+
mActiveNodes.removeOne( entry->chunk );
252+
entry->chunk->unloadChunk(); // also deletes the entry
253+
++unloaded;
254+
entry = entryPrev;
255+
}
256+
else
257+
{
258+
entry = entry->prev;
259+
}
260+
}
261+
262+
if ( usedGpuMemory > mGpuMemoryLimit )
263+
{
264+
QgsDebugMsgLevel( QStringLiteral( "Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 );
265+
}
266+
267+
return unloaded;
268+
}
269+
270+
217271
QgsRange<float> QgsChunkedEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const
218272
{
219273
QList<QgsChunkNode *> activeEntityNodes = activeNodes();

‎src/3d/chunks/qgschunkedentity_p.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity
147147
void startJobs();
148148
QgsChunkQueueJob *startJob( QgsChunkNode *node );
149149

150+
int unloadNodes();
151+
150152
private slots:
151153
void onActiveJobFinished();
152154

0 commit comments

Comments
 (0)
Please sign in to comment.