Is multipass rendering possible with SpriteBatch?

| | August 6, 2015

I’m trying to implement a bloom effect. This requires three shader passes: a brightness threshold, a horizontal blur, and a vertical blur. It also requires resizes, but these are irrelevant to the question. I’ve also omitted the threshold, as the problem lies in the blurs.

Now, I’ve read that HLSL passes are just syntactic sugar for different shaders, and that switching between them is about as expensive as setting a new effect to the GraphicsDevice. Still, I prefer to keep them in one .fx file.

The passes:

    pass Pass0
    {
        PixelShader = compile ps_2_0 PS_Threshold();
    }
    pass Pass1
    {
        PixelShader = compile ps_2_0 PS_BlurHorizontal();
    }
    pass Pass2
    {
        PixelShader = compile ps_2_0 PS_BlurVertical();
    }

The blurring code:

        private void Blur(SpriteBatch SpriteBatch, RenderTarget2D Source, RenderTarget2D Rendertarget1, RenderTarget2D Destination)
        {
            // Blur pass 1
            Contents.BloomEffect.CurrentTechnique.Passes["Pass1"].Apply();
            SpriteBatch.GraphicsDevice.SetRenderTarget(Rendertarget1);
            SpriteBatch.GraphicsDevice.Clear(Color.Black);
            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, Contents.BloomEffect);
            SpriteBatch.Draw(Source, Rendertarget1.Bounds, Color.White);
            SpriteBatch.End();

            // Blur pass 2
            Contents.BloomEffect.CurrentTechnique.Passes["Pass2"].Apply();
            SpriteBatch.GraphicsDevice.SetRenderTarget(Destination);
            SpriteBatch.GraphicsDevice.Clear(Color.Black);
            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, Contents.BloomEffect);
            SpriteBatch.Draw(Rendertarget1, Destination.Bounds, Color.White);
            SpriteBatch.End();
        }

Finally, the Draw loop:

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(RT1);
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, bloom);
            spriteBatch.Draw(android,
                             new Rectangle(0, 0, 258, 294),
                             Color.White);
            spriteBatch.End();

            Blur(spriteBatch, RT1, RT11, RT1);

            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, null, null, null);
            spriteBatch.Draw(RT1,
                RT1.Bounds,
                Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }

The output, however, is as follows:
Vertically blurred image output

It’s obvious what happens: the last pass is applied twice. I’ve tested the passes individually by snipping them out of the shader, and they work fine. Is EffectPass.Apply() simply not meant to be used with SpriteBatch, or is there another way to change what passes are applied (without putting the passes in different .fx files)?

This has all been in XNA. To make this even weirder, when I do the same thing in Monogame the opposite occurs: the shader will only use Pass0. However, I’m converting the shader to OpenGL there, so that might be a conversion quirk.

EDIT: I fixed this by putting the different passes in different techniques, and switching techniques instead of passes. That doesn’t answer the original question though, so I’ll leave it open.

One Response to “Is multipass rendering possible with SpriteBatch?”

  1. Yes, multipass rendering is possible with SpriteBatch. If you set the current technique of some effect to the desired one, and then pass a reference to that effect to the Begin method, then all passes of that technique will be applied.

    Internally, a sprite batch will do its actual rendering from the RenderBatch method, which is private and called from the public Draw or End methods, depending on whether or you not you have requested immediate or deferred rendering.

    RenderBatch looks like this (the relevant code is in the positive branch):

    private void RenderBatch(Texture2D texture, SpriteInfo[] sprites, int offset, int count)
    {
        if (this.customEffect != null)
        {
            int num2 = this.customEffect.CurrentTechnique.Passes.Count;
            for (int i = 0; i < num2; i++)
            {
                this.customEffect.CurrentTechnique.Passes[i].Apply();
                base._parent.Textures[0] = texture;
                this.PlatformRenderBatch(texture, sprites, offset, count);
            }
        }
        else
        {
            base._parent.Textures[0] = texture;
            this.PlatformRenderBatch(texture, sprites, offset, count);
        }
    }
    

    Since you are passing your custom bloom effect to Begin within your Blur function, you are executing that positive branch, so all passes of your technique are applied both times you call Draw within Blur, yielding incorrect results because those passes are not all meant to be applied to each texture.

    By switching to multiple techniques (as you have), you work around that behavior since each technique only has a single pass. You could also solve the problem by passing null for the effect within your Blur method.

Leave a Reply