Skip to content

Commit 6aeb34a

Browse files
authored
fix: memory leaks in renderer systems (#11581)
* fix: memory leaks Ensures proper cleanup and avoids memory leaks * fix test * Fixes resource leaks and improves cleanup Addresses memory leaks by implementing proper resource cleanup. This includes: - Adding destroy methods to relevant classes and systems - Registering pools and caches with a collector for centralized management. - Ensuring resources are released when objects are destroyed or when the renderer is destroyed. - Prevents errors by checking for null values before destroying resources. These changes improve application stability and prevent potential performance issues related to excessive memory usage. * fix batch return * tidy pipes * rename to GlobalResourceRegistry * change option name * fix index * pr feedback * PR feedback
1 parent 4c7875d commit 6aeb34a

File tree

28 files changed

+238
-9
lines changed

28 files changed

+238
-9
lines changed

src/environment/ImageLike.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export interface ImageLike extends EventTarget
2626
/** Returns a Promise that resolves once the image is decoded. */
2727
decode(): Promise<void>;
2828

29+
/** Removes the image from the DOM and cleans up resources. */
30+
remove(): void;
31+
2932
onload: ((this: GlobalEventHandlers, ev: Event) => any) | null;
3033
onerror: ((this: GlobalEventHandlers, ev: Event) => any) | null;
3134
}

src/events/EventSystem.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ export class EventSystem implements System<EventSystemOptions>
499499
/** Destroys all event listeners and detaches the renderer. */
500500
public destroy(): void
501501
{
502+
EventsTicker.destroy();
502503
this.setTargetElement(null);
503504
this.renderer = null;
504505
this._currentCursor = null;

src/events/EventTicker.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ class EventsTickerClass
131131

132132
this._update();
133133
}
134+
135+
/** Destroys the event ticker. */
136+
public destroy(): void
137+
{
138+
this.removeTickerListener();
139+
this.events = null;
140+
this.domElement = null;
141+
this._deltaTime = 0;
142+
this._didMove = false;
143+
this._tickerAdded = false;
144+
this._pauseUpdate = true;
145+
}
134146
}
135147

136148
/**

src/rendering/batcher/shared/Batcher.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { uid } from '../../../utils/data/uid';
22
import { ViewableBuffer } from '../../../utils/data/ViewableBuffer';
33
import { deprecation } from '../../../utils/logging/deprecation';
4+
import { GlobalResourceRegistry } from '../../../utils/pool/GlobalResourceRegistry';
45
import { fastCopy } from '../../renderers/shared/buffer/utils/fastCopy';
56
import { type BLEND_MODES } from '../../renderers/shared/state/const';
67
import { getAdjustedBlendModeBlend } from '../../renderers/shared/state/getAdjustedBlendModeBlend';
@@ -75,6 +76,22 @@ export class Batch implements Instruction
7576
const batchPool: Batch[] = [];
7677
let batchPoolIndex = 0;
7778

79+
GlobalResourceRegistry.register({
80+
clear: () =>
81+
{
82+
// check if the first element has a destroy method
83+
if (batchPool.length > 0)
84+
{
85+
for (const item of batchPool)
86+
{
87+
if (item) item.destroy();
88+
}
89+
}
90+
batchPool.length = 0; // clear the array
91+
batchPoolIndex = 0;
92+
},
93+
});
94+
7895
function getBatchFromPool()
7996
{
8097
return batchPoolIndex > 0 ? batchPool[--batchPoolIndex] : new Batch();
@@ -759,6 +776,8 @@ export abstract class Batcher
759776

760777
public destroy()
761778
{
779+
if (this.batches === null) return;
780+
762781
for (let i = 0; i < this.batches.length; i++)
763782
{
764783
returnBatchToPool(this.batches[i]);
@@ -768,7 +787,7 @@ export abstract class Batcher
768787

769788
for (let i = 0; i < this._elements.length; i++)
770789
{
771-
this._elements[i]._batch = null;
790+
if (this._elements[i]) this._elements[i]._batch = null;
772791
}
773792

774793
this._elements = null;

src/rendering/mask/color/ColorMaskPipe.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export class ColorMaskPipe implements InstructionPipe<ColorMaskInstruction>
103103

104104
public destroy()
105105
{
106+
(this._renderer as null) = null;
106107
this._colorStack = null;
107108
}
108109
}

src/rendering/renderers/__tests__/UniformBatch.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GpuUniformBatchPipe } from '../gpu/GpuUniformBatchPipe';
22
import { SchedulerSystem } from '../shared/SchedulerSystem';
3+
import { SystemRunner } from '../shared/system/SystemRunner';
34
import { RenderableGCSystem } from '../shared/texture/RenderableGCSystem';
45

56
import type { WebGPURenderer } from '../gpu/WebGPURenderer';
@@ -13,6 +14,9 @@ describe('UniformBatch', () =>
1314
const uniformBatchPipe = new GpuUniformBatchPipe({
1415
scheduler,
1516
renderableGC: new RenderableGCSystem({} as WebGPURenderer),
17+
runners: {
18+
destroy: new SystemRunner('destroy'),
19+
},
1620
} as WebGPURenderer);
1721

1822
const bufferResource = uniformBatchPipe.getArrayBufferResource(new Float32Array(32));

src/rendering/renderers/gl/geometry/GlGeometrySystem.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,5 +487,6 @@ export class GlGeometrySystem implements System
487487
this.gl = null;
488488
this._activeVao = null;
489489
this._activeGeometry = null;
490+
this._geometryVaoHash = null;
490491
}
491492
}

src/rendering/renderers/gl/shader/GlProgram.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ export class GlProgram
130130
* @internal
131131
*/
132132
public readonly _key: number;
133+
/**
134+
* A cache key used to identify the program instance.
135+
* @internal
136+
*/
137+
public _cacheKey: string;
133138

134139
/**
135140
* Creates a shiny new GlProgram. Used by WebGL renderer.
@@ -188,6 +193,8 @@ export class GlProgram
188193
this._uniformBlockData = null;
189194

190195
this.transformFeedbackVaryings = null;
196+
197+
programCache[this._cacheKey] = null;
191198
}
192199

193200
/**
@@ -204,6 +211,7 @@ export class GlProgram
204211
if (!programCache[key])
205212
{
206213
programCache[key] = new GlProgram(options);
214+
programCache[key]._cacheKey = key;
207215
}
208216

209217
return programCache[key];

src/rendering/renderers/gl/shader/GlShaderSystem.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ export class GlShaderSystem
197197
}
198198

199199
this._programDataHash = null;
200+
this._shaderSyncFunctions = null;
201+
this._activeProgram = null;
202+
(this._renderer as null) = null;
203+
this._gl = null;
200204
}
201205

202206
/**

src/rendering/renderers/gl/texture/GlTextureSystem.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,14 @@ export class GlTextureSystem implements System, CanvasGenerator
438438
.forEach((source) => this.onSourceDestroy(source));
439439

440440
(this.managedTextures as null) = null;
441-
441+
this._glTextures = null;
442+
this._glSamplers = null;
443+
this._boundTextures = null;
444+
this._boundSamplers = null;
445+
this._mapFormatToInternalFormat = null;
446+
this._mapFormatToType = null;
447+
this._mapFormatToFormat = null;
448+
(this._uploads as null) = null;
442449
(this._renderer as null) = null;
443450
}
444451

0 commit comments

Comments
 (0)