Skip to content

Commit c84f6c0

Browse files
authored
refactor(textures): replace defrag system with a slot reuse system (#650)
The defrag system was introduced to avoid endlessly growing the `this.loadedTextures` however this introduced: * Additional function calls * Idle processing * Potential race conditions when the defrag ran inbetween operations (like 1 frame idle) Having a memory allocation with potentially lots of `null` items isn't going to affect memory usage much. Since we want low-latency, zero-copy, texture memory placement this PR replaces the defrag system with a Slot Reuse system. It simply does: * On texture delete, `null` the place in the texture array * On texture creation, pick a previously `null` spot or `append` if there are none This avoids potential race conditions and the extra overhead of a defragmentation system while keeping the textures neatly packed at the beginning. The only downside is the `this.loadedTextures` array never shrinks, but this is deemed neglible because it will be just `null` entries and only be as large as the largest scene supported while rendering in L3. Which is a tradeoff against zero copy low latency texture memory allocation.
2 parents 15f1b19 + ad0cdbf commit c84f6c0

File tree

2 files changed

+9
-54
lines changed

2 files changed

+9
-54
lines changed

src/core/TextureMemoryManager.ts

Lines changed: 9 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ export class TextureMemoryManager {
122122
private debugLogging: boolean;
123123
private lastCleanupTime = 0;
124124
private baselineMemoryAllocation: number;
125-
private needsDefrag = false;
126125

127126
public criticalCleanupRequested = false;
128127
public doNotExceedCriticalThreshold: boolean;
@@ -191,11 +190,10 @@ export class TextureMemoryManager {
191190
this.memUsed -= texture.memUsed;
192191

193192
if (byteSize === 0) {
194-
// PERFORMANCE: Mark for deletion instead of splice (zero overhead)
193+
// PERFORMANCE: Mark for deletion, slot will be reused later
195194
const index = this.loadedTextures.indexOf(texture);
196195
if (index !== -1) {
197196
this.loadedTextures[index] = null;
198-
this.needsDefrag = true;
199197
}
200198
texture.memUsed = 0;
201199
return;
@@ -204,7 +202,13 @@ export class TextureMemoryManager {
204202
texture.memUsed = byteSize;
205203
this.memUsed += byteSize;
206204
if (this.loadedTextures.indexOf(texture) === -1) {
207-
this.loadedTextures.push(texture);
205+
// PERFORMANCE: Reuse empty slots before appending
206+
const emptyIndex = this.loadedTextures.indexOf(null);
207+
if (emptyIndex !== -1) {
208+
this.loadedTextures[emptyIndex] = texture;
209+
} else {
210+
this.loadedTextures.push(texture);
211+
}
208212
}
209213
}
210214

@@ -225,19 +229,6 @@ export class TextureMemoryManager {
225229
return this.memUsed > this.criticalThreshold;
226230
}
227231

228-
/**
229-
* Check if defragmentation is needed
230-
*
231-
* @remarks
232-
* Returns true if the loadedTextures array has null entries that need
233-
* to be compacted. Called by platform during idle periods.
234-
*
235-
* @returns true if defragmentation should be performed
236-
*/
237-
checkDefrag() {
238-
return this.needsDefrag;
239-
}
240-
241232
/**
242233
* Destroy a texture and null out its array position
243234
*
@@ -250,11 +241,10 @@ export class TextureMemoryManager {
250241
);
251242
}
252243

253-
// PERFORMANCE: Null out array position instead of splice (zero overhead)
244+
// PERFORMANCE: Null out array position, slot will be reused later
254245
const index = this.loadedTextures.indexOf(texture);
255246
if (index !== -1) {
256247
this.loadedTextures[index] = null;
257-
this.needsDefrag = true;
258248
}
259249

260250
// Destroy texture and update memory counters
@@ -334,37 +324,6 @@ export class TextureMemoryManager {
334324
}
335325
}
336326

337-
/**
338-
* Defragment the loadedTextures array by removing null entries
339-
*
340-
* @remarks
341-
* This should be called during idle periods to compact the array
342-
* after null-marking deletions. Zero overhead during critical cleanup.
343-
*/
344-
defragment() {
345-
if (!this.needsDefrag) {
346-
return;
347-
}
348-
349-
// PERFORMANCE: Single-pass compaction
350-
let writeIndex = 0;
351-
for (
352-
let readIndex = 0;
353-
readIndex < this.loadedTextures.length;
354-
readIndex++
355-
) {
356-
const texture = this.loadedTextures[readIndex];
357-
if (texture !== null && texture !== undefined) {
358-
this.loadedTextures[writeIndex] = texture;
359-
writeIndex++;
360-
}
361-
}
362-
363-
// Trim array to new size
364-
this.loadedTextures.length = writeIndex;
365-
this.needsDefrag = false;
366-
}
367-
368327
/**
369328
* Get the current texture memory usage information
370329
*

src/core/platform.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ export const startLoop = (stage: Stage) => {
6666
stage.txMemManager.cleanup();
6767
}
6868

69-
if (stage.txMemManager.checkDefrag() === true) {
70-
stage.txMemManager.defragment();
71-
}
72-
7369
stage.flushFrameEvents();
7470
return;
7571
}

0 commit comments

Comments
 (0)