Applying grayscale filter not working after scene restart

Edit:

I’ve demonstrated this using lab code for replicability’s sake:

You can input the following code into http://labs.phaser.io console.

Click Grayscale, Reset, then grayscale again.

var DemoA = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function DemoA ()
    {
        Phaser.Scene.call(this, { key: 'demoA', active: true });
    },

    preload: function ()
    {
        this.load.image('picA', 'assets/pics/lance-overdose-loader-eye.png');
         this.load.image('picB', 'assets/pics/sukasuka-chtholly.png');
    },

    create: function ()
    {
        this.grayscalePipeline = this.game.renderer.addPipeline('Grayscale', new GrayscalePipeline(this.game));
        const text = this.add.text(0, 0, 'grayscale', {
        color: 'white',
        fontSize: 20
      });
      const newText = this.add.text(0, 50, 'reset scene', {
        color: 'white',
        fontSize: 20
      });

    text.setInteractive({ useHandCursor: true });
    newText.setInteractive({ useHandCursor: true });

    text.on('pointerup', () => {
      this.cameras.main.setRenderToTexture(this.grayscalePipeline);
    });


    newText.on('pointerup', () => {
      this.scene.restart();
    });
    
        this.add.image(400, 300, 'picA');

        this.input.once('pointerdown', function () {

            this.scene.sendToBack();

        }, this);
    }

});

var DemoB = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function DemoB ()
    {
        Phaser.Scene.call(this, { key: 'demoB', active: true });
    },

    preload: function ()
    {
        this.load.image('picB', 'assets/pics/sukasuka-chtholly.png');
    },

    create: function ()
    {
        this.add.image(400, 500, 'picB');
    }

});

var DemoC = new Phaser.Class({

    Extends: Phaser.Scene,

    initialize:

    function DemoC ()
    {
        Phaser.Scene.call(this, { key: 'demoC', active: true });
    },

    preload: function ()
    {
        this.load.image('picC', 'assets/pics/titan-mech.png');
    },

    create: function ()
    {
        this.add.image(300, 300, 'picC');
    }

});

var GrayscalePipeline = new Phaser.Class({

    Extends: Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline,

    initialize:

    function GrayscalePipeline (game)
    {
        Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline.call(this, {
            game: game,
            renderer: game.renderer,
            fragShader:`
                precision mediump float;
                uniform sampler2D uMainSampler;
                varying vec2 outTexCoord;
                void main(void) {
                vec4 color = texture2D(uMainSampler, outTexCoord);
                float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
                gl_FragColor = vec4(vec3(gray), 1.0);
                }`
        });
    } 
});

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    scene: [ DemoB, DemoC, DemoA ]
};

var game = new Phaser.Game(config);

You add the Grayscale pipeline every time SceneA is created. Because pipelines are registered globally for the entire game, this will cause an error if you create the Scene more than once. As the source code of addPipeline will tell you, passing a duplicate key will not register the pipeline you pass in, but still return it. As a result, the sprite will use an unregistered pipeline.

There are three ways you can fix this - two of them involve making sure the pipeline is only registered once. When initializing this.grayscalePipeline, you can check if the Grayscale pipeline already exists and create it only if it doesn’t - this.grayscalePipeline = this.game.renderer.getPipeline('Grayscale') || this.game.renderer.addPipeline('Grayscale', new GrayscalePipeline(this.game));. This works because getPipeline returns null if the given key doesn’t exist and the || operator will evaluate the right side only if the left one is falsey. Really, this is equivalent to the following code:

this.grayscalePipeline = this.game.renderer.getPipeline('Grayscale');

if(this.grayscalePipeline == null){
    this.grayscalePipeline = this.game.renderer.addPipeline('Grayscale', new GrayscalePipeline(this.game));
}

Another way you could fix this is to skip the creation of the pipeline entirely if it already exists:

if(!this.game.renderer.hasPipeline('Grayscale')){
    this.game.renderer.addPipeline('Grayscale', new GrayscalePipeline(this.game))
}

Then, you can pass the key ('Grayscale') instead of a reference (this.grayscalePipeline) to the setRenderToTexture method. The downside of fixing it this way is that you won’t have a reference to the exact pipeline, so you won’t be able to change it, which is why I recommend doing it the first way.

Speaking of changing the pipeline, I’m going to once again stress that a single pipeline instance is global for the whole game. If you need a separate pipeline each time you start the Scene (for example, if you’re using the pipeline in another scene), you can dynamically generate a pipeline key - for example, 'Grayscale_' + Date.now(). The problem with this is that, if you restart the Scene multiple times, you’ll be left with a lot of unused registered pipelines. If you fix it this way, you’ll have to unregister the pipeline (with removePipeline) before the Scene shuts down (which you can detect with a shutdown event from the Scene’s event emitter).