phaserjs / phaserjs/phaser

DynamicTexture constructor leaks a WebGL texture on every create/destroy cycle (4.2.1)

Open
#7,379 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.2.1
  • Operating system: MAC OSX (latest)
  • Browser: Chrome (latest)

Description

In WebGL mode, creating and removing a DynamicTexture leaves one WebGL texture wrapper retained in
renderer.glTextureWrappers.

The DynamicTexture constructor allocates a texture through the base Texture/TextureSource constructor, then replaces
frame.source.glTexture with this.drawingContext.texture without disposing the original wrapper.

Removing the DynamicTexture destroys the replacement, but the original remains registered with the renderer. In an
isolated reproduction, the original WebGL texture also remains valid according to gl.isTexture().

Expected: after each create/remove cycle, the wrapper count returns to its baseline.

Actual: the retained wrapper count increases by one per cycle.

Example Test Code

Run against unpatched Phaser 4.2.1:

<!doctype html>
<html>
<body>
<script src="https://cdn.jsdelivr.net/npm/phaser@4.2.1/dist/phaser.js"></script>
<script>
new Phaser.Game({
    type: Phaser.WEBGL,
    width: 64,
    height: 64,
    audio: { noAudio: true },
    scene: {
        create: function () {
            const renderer = this.game.renderer;
            const baseline = renderer.glTextureWrappers.length;

            for (let i = 0; i < 4; i++) {
                const key = `texture-leak-${i}`;
                this.textures.addDynamicTexture(key, 8, 8);
                this.textures.remove(key);

                console.log(
                    `Cycle ${i + 1}: retained wrappers =`,
                    renderer.glTextureWrappers.length - baseline
                );
            }
        }
    }
});
</script>
</body>
</html>

Expected counts: 0, 0, 0, 0.
Actual counts: 1, 2, 3, 4.

Additional Information

Disposing the original texture before replacing it in the DynamicTexture constructor eliminates the growth in the
isolated reproduction:

if (!isCanvas)
{
    var frame = this.get();
    renderer.deleteTexture(frame.source.glTexture);
    frame.source.glTexture = this.drawingContext.texture;
}

Related history: #6669 reports a DynamicTexture create/destroy memory leak in Phaser 3.70. This report concerns the
DrawingContext replacement path in 4.2.1; I have not established whether it shares the same underlying cause.

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 at the DynamicTexture constructor and trace the textures.addDynamicTexture/remove path, especially the replacement of frame.source.glTexture and renderer.glTextureWrappers. Run the HTML reproduction in WebGL and verify that each create/remove cycle returns the wrapper count to its baseline and no original texture remains valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
computer-graphics, game-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.