phaserjs / phaserjs/phaser

Memory leak in TilemapLayerWebGLRenderer

Open
#7,296 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
40.3k
Forks
7.2k
PR merge metrics
No merged PRs in 30d

Description

Version

  • Phaser Version: 4.0.0
  • Operating system: MacOS Monterey v12.7.6
  • Browser: all browsers

Description

When Phaser.Game is destroyed and a new instance is created on the same page, retained heap memory for TilemapLayerWebGLRenderer nearly doubles with each cycle.

Example Test Code (in phaser sandbox)

class MainScene extends Phaser.Scene {

    constructor() {
        super({ key: "MainScene" });
    }

    preload() {
        this.load.setBaseURL('https://cdn.phaserfiles.com/v385');
        this.load.image('walls_1x2', 'assets/tilemaps/tiles/walls_1x2.png');
    }

    create() {
        const map = this.make.tilemap({ width: 200, height: 200, tileWidth: 32, tileHeight: 32 });
        const tiles = map.addTilesetImage('walls_1x2', null, 32, 64);

        const layer = map.createBlankLayer('layer1', tiles);

        layer.randomize(0, 0, map.width, map.height, [ 0, 1, 2, 3, 4, 5, 6, 7 ]);
    }
}

let game = null;
const getGame = () => game;

function createGame() {
    game = new Phaser.Game({
        type: Phaser.AUTO,
        width: 800,
        height: 800,
        backgroundColor: '#111111',
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH
        },
        scene: [ MainScene ]
    });
}

function destroyGame(gameInstance) {
    gameInstance.events.once('destroy', () => {
        game = null;
    });

    gameInstance.destroy(true);
}

setInterval(() => {
    if (!getGame()) {
        createGame();
    } else {
        destroyGame(getGame());
    }
}, 6000);

Additional Information

Possible issue at src/tilemaps/TilemapLayerWebGLRenderer.js :
Module-level singleton retains glTexture reference across Phaser.Game instances. Declares two module-level objects that persist for the entire lifetime of the JavaScript module (i.e. they are never GC'd).
During every render call, the live glTexture from the active Tileset is written into texturerData:
texturerData.frame.source.glTexture = tileset.glTexture;

When WebGLRenderer.destroy() is called (via game.destroy()), it correctly iterates glTextureWrappers and calls .destroy() on each. However, texturerData.frame.source.glTexture is never nulled out.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/tilemaps/TilemapLayerWebGLRenderer.js and inspect the module-level objects and the texturerData.frame.source.glTexture assignment during rendering. Reproduce the cycle with the provided Phaser.Game creation and destruction example, then verify with heap measurements that retained TilemapLayerWebGLRenderer memory no longer grows after repeated cycles.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
performance, web-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.