rexrainbow / phaser3-rex-notes

Notes of phaser3 engine
MIT License
1.21k stars 263 forks source link

Drop Shadow pipeline broken in Phaser 3.70 #395

Closed LaP0573 closed 7 months ago

LaP0573 commented 10 months ago

Updated Phaser to 3.70, as well as rex plugin to 1.60.7, and enabling the Drop Shadow pipeline on a game object leads to odd flickering of the resolution.

Here is an example, moving before and after applying the pipeline (the black line has the pipeline applied to it):

https://github.com/rexrainbow/phaser3-rex-notes/assets/9264791/90fee911-bcd1-4504-a84c-fff363e3bfb3

rexrainbow commented 10 months ago

Can't reproduce this case.

Here is my test case :

Running on chrome, no flickering occurs.

moufmouf commented 10 months ago

Hey @LaP0573 , hey @rexrainbow ,

Interesting! I'm facing the exact same issue is Maxime on a very similar project (hello Gather friends!)

Capture vidéo du 2023-11-24 15-21-07.webm

For me, it is the "rexOutlinePipeline" that makes this error.

We are initializing the plugin a bit differently (from the Game config) this way:

https://github.com/workadventure/workadventure/blob/95b5f3ed8c329997c5d3f081b9655f6cd75c0daf/play/src/svelte.ts#L154

Could it be related? (also, we are using the last tagged version of rex plugin which seems to be different from the one used in the Codepen that is not yet tagged)

rexrainbow commented 10 months ago

@moufmouf Can't reproduce your case neither. ( my test code, it has camera scrolling ).

LaP0573 commented 10 months ago

Mmmh very odd, one detail is that we're applying the shader to Phaser.GameObject.Line objects (which @moufmouf is not so I'm unsure it's related, but figured I'd point it out just in case)

moufmouf commented 10 months ago

Hey @rexrainbow ,

Thanks a lot for being so reactive, you are awesome.

Looking at your example, I can actually reproduce the issue sometimes. It does look exactly the same but on the first click to move the object, there seems to be a single frame with glitches in it.

See:

Capture vidéo du 2023-11-24 17-40-44.webm

(the glitch really happens only on the first frame in your example but happens all over the place on our side)

Also, I noticed this happens in Chrome, but not in Firefox (though in Firefox, the effect seems to fail silently sometimes)

I'd be happy to help debug this further, but I'm not sure where to look...

rexrainbow commented 10 months ago

Mmmh very odd, one detail is that we're applying the shader to Phaser.GameObject.Line objects (which @moufmouf is not so I'm unsure it's related, but figured I'd point it out just in case)

Can't reproduce by Line game object neither. Tested on

rexrainbow commented 10 months ago

@moufmouf I still can't reproduce that glitch bug in my site.

Here is a shadow effect by built-in shadow effect (gameObject.postFX.addShadow(...)), any click to move this game object. Could you check the render result in your environment?

tongliang999 commented 10 months ago

Hey @rexrainbow ,

Thanks a lot for being so reactive, you are awesome.

Looking at your example, I can actually reproduce the issue sometimes. It does look exactly the same but on the first click to move the object, there seems to be a single frame with glitches in it.

See:

Capture.video.du.2023-11-24.17-40-44.webm (the glitch really happens only on the first frame in your example but happens all over the place on our side)

Also, I noticed this happens in Chrome, but not in Firefox (though in Firefox, the effect seems to fail silently sometimes)

I'd be happy to help debug this further, but I'm not sure where to look...

I encountered the same glitches issue.It seems that the first frame after adding a postfx will be randomly painted. Temporally fixed by overriding the postBatch method of the PostFXPipeline class:

Phaser.Renderer.WebGL.Pipelines.PostFXPipeline.prototype.postBatch = function(gameObject) {
  if (!this.hasBooted) {
    this.bootFX();
  } else {
    this.onDraw(this.currentRenderTarget);
  }
  //
  this.onPostBatch(gameObject);
  return this;
};
rexrainbow commented 10 months ago

@tongliang999 The original logic of postBatch method is

    postBatch: function (gameObject)
    {
        if (!this.hasBooted)
        {
            this.bootFX();
        }

        this.onDraw(this.currentRenderTarget);

        this.onPostBatch(gameObject);

        return this;
    },

It seems a bug in phaser3 engine. In your patch logic, onDraw method won't call if not hasBooted. Because that I can't reproduce the glitch rendering result, I can't open an issue for this case on phase3's repo.

moufmouf commented 10 months ago

Hey @tongliang999 ,

You are right, it is indeed only the first frame after adding a Postfx that has the issue. Because of pretty hard core optimizations I do to avoid useless rendering, it is more visible in my code than elsewhere (I'm stopping the rendering if nothing moves). If I disable those optimizations, I see exactly what you see: just after adding an effect, the first frame has a random glitch.

I managed to make it obvious in the codepen below: on each frame, I either set up the Postfx plugin or remove it.

https://codepen.io/moufmouf/pen/abXjYRV

It definitely looks like a bug in Phaser 3.

photonstorm commented 8 months ago

@tongliang999 The original logic of postBatch method is

    postBatch: function (gameObject)
    {
        if (!this.hasBooted)
        {
            this.bootFX();
        }

        this.onDraw(this.currentRenderTarget);

        this.onPostBatch(gameObject);

        return this;
    },

It seems a bug in phaser3 engine. In your patch logic, onDraw method won't call if not hasBooted. Because that I can't reproduce the glitch rendering result, I can't open an issue for this case on phase3's repo.

The problem is that the bootFX method does everything that the pipeline needs in order for onDraw to work. So the logic is correct if we assume that WebGLPipeline.boot does indeed do all that we need to render, which I believe it does - this is likely why we don't see this manifest on desktops. However, this patch introduces an artificial frame skip. Perhaps something in the boot process it taking longer than a frame to occur, which is why on the second pass, everything is ok.

I can't see a reason to modify postBatch (I don't think it's the root of the issue), but equally it won't break anything either and will still carry on working on desktops, so we can give it a try in v3.80.

moufmouf commented 8 months ago

I can confirm your fix works.

Also: the issue happened not only on mobile, but also on desktop in my case (in Chrome only).

:100: I'm so happy I'll finally be able to upgrade Phaser and get the nice features of 3.70!

tongliang999 commented 8 months ago

@tongliang999 The original logic of postBatch method is

    postBatch: function (gameObject)
    {
        if (!this.hasBooted)
        {
            this.bootFX();
        }

        this.onDraw(this.currentRenderTarget);

        this.onPostBatch(gameObject);

        return this;
    },

It seems a bug in phaser3 engine. In your patch logic, onDraw method won't call if not hasBooted. Because that I can't reproduce the glitch rendering result, I can't open an issue for this case on phase3's repo.

The problem is that the bootFX method does everything that the pipeline needs in order for onDraw to work. So the logic is correct if we assume that WebGLPipeline.boot does indeed do all that we need to render, which I believe it does - this is likely why we don't see this manifest on desktops. However, this patch introduces an artificial frame skip. Perhaps something in the boot process it taking longer than a frame to occur, which is why on the second pass, everything is ok.

I can't see a reason to modify postBatch (I don't think it's the root of the issue), but equally it won't break anything either and will still carry on working on desktops, so we can give it a try in v3.80.

@photonstorm Thanks for your reply. After some deep diving in the source code, I have found that not invoking this.currentRenderTarget.bind() is the cause of the first frame glitch.

In the normal run,currentRenderTarget.bind() is called in WebGLPipeline.preBatch. Due to delayed call of bootFx (in PostFXPipeline.postBatch), currentRenderTarget.bind() is not called before drawing the first frame.

I fix this by adding this.currentRenderTarget.bind() after this.bootFx(), it wokrs well on desktop chrome. And it wont introduce any frame skip.

postBatch: function (gameObject)
    {
        if (!this.hasBooted)
        {
            this.bootFX();

            if (this.currentRenderTarget)
            {
                this.currentRenderTarget.bind();
            }
        }

        this.onDraw(this.currentRenderTarget);

        this.onPostBatch(gameObject);

        return this;
    },
moufmouf commented 8 months ago

Hey @tongliang999

Whoa, so cool! You nailed it. :heart: I took the liberty to open a PR with your solution here: https://github.com/phaserjs/phaser/pull/6728

@photonstorm, hope can merge this before 3.80! :+1: