Can't run multiple shader passes on text

I want to be able to

  1. Draw something to the screen (orange line in this example).
  2. Apply a two pass shader (Gaussian X and Y) that draws on top of the orange line (white text).
  3. Draw on top of that (red text and blue line).

I have two problems

  1. Only one shader pass is applying no matter what I do (I can get the X gaussian or Y gaussian to apply but not both).
  2. The result of the shader is somehow drawing under the orange line.

I’m aware that the shader code and/or blend state may be wrong but I don’t care, I want to get two shader passes working and drawing over the orange line.

I have uploaded a short sample project that demonstrates my issue:
Download at: www.hernblog.com/MonogameGaussianBlur.zip

Here’s the main part of the code:

void DrawBlurredString(string text)
{
    SpriteBatch.End();
            
    // Pass 1 - render the text and make space for the blur
    float textOffset = 5f;
    Vector2 textSize = Font.MeasureString(text);
    Vector2 textureSize = textSize + new Vector2(2f * textOffset, 2f * textOffset);
    RenderTarget2D renderedText = new RenderTarget2D(SpriteBatch.GraphicsDevice,
                                    (int)textureSize.X, (int)textureSize.Y, false,
                                    SurfaceFormat.Color, DepthFormat.None, 1, RenderTargetUsage.DiscardContents);
    SpriteBatch.GraphicsDevice.SetRenderTarget(renderedText);
    SpriteBatch.GraphicsDevice.Clear(Color.Transparent);
    SpriteBatch.Begin();
    SpriteBatch.DrawString(Font, text, new Vector2(textOffset, textOffset), Color.White);
    SpriteBatch.End();

    // Pass 2 - Do the GaussianY shader
    RenderTarget2D partiallyBlurredText = new RenderTarget2D(SpriteBatch.GraphicsDevice,
                                    (int)textureSize.X, (int)textureSize.Y, false,
                                    SurfaceFormat.Color, DepthFormat.None, 1, RenderTargetUsage.DiscardContents);
    SpriteBatch.GraphicsDevice.SetRenderTarget(partiallyBlurredText);
    SpriteBatch.GraphicsDevice.Clear(Color.Transparent);
    SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
    GaussianY.CurrentTechnique.Passes[0].Apply(); // effect not showing up in result
    SpriteBatch.Draw(renderedText, new Vector2(0, 0), null, Color.White);
    SpriteBatch.End();
            
    SpriteBatch.GraphicsDevice.SetRenderTarget(null);

    // Pass 3 - Do the GaussianX shader (back onto the main render target)
    SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
                        
    GaussianX.CurrentTechnique.Passes[0].Apply(); // effect is showing up in the result
    SpriteBatch.Draw(partiallyBlurredText, Vector2.Zero, null, Color.White);
    SpriteBatch.End();

    SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, RasterizerState, null);

    SpriteBatch.DrawString(Font, text, new Vector2(textOffset, textOffset), Color.Red);
}

Edited for clarity.

You are not applying the shader correctly.

SpriteBatch has a method that takes an effect

spriteBatch.Begin(0, null, null, null, null, Effect);

Try using that, I am really not sure what your technique will end up doing to the graphics state

I tried that and it somehow wipes everything I’m trying to draw

// Pass 2 - Do the GaussianY shader
RenderTarget2D partiallyBlurredText = new RenderTarget2D(SpriteBatch.GraphicsDevice,
                                (int)textureSize.X, (int)textureSize.Y, false,
                                SurfaceFormat.Color, DepthFormat.None, 1, RenderTargetUsage.DiscardContents);
SpriteBatch.GraphicsDevice.SetRenderTarget(partiallyBlurredText);
SpriteBatch.GraphicsDevice.Clear(Color.Transparent);
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, GaussianY);
//GaussianY.CurrentTechnique.Passes[0].Apply();
SpriteBatch.Draw(renderedText, new Vector2(0, 0), null, Color.White);
SpriteBatch.End();
            
SpriteBatch.GraphicsDevice.SetRenderTarget(null);

Should be immediate

BlendState.AlphaBlend

Try frompremultiplied

Same result.

We will need to see more of the code then, you could try saving each render target to disc and have a look at them

Might give you a clue

I uploaded a sample project with all the code. I’ve updated the sample project to output the intermediary states. It’s sort of as expected in that the first effect is not applying.

Well a few things jump out at me.

  1. Shader code

I don’t think this is correct

	color += Bitmap.Sample(MeshTextureSampler, input.texCoord, int2(0, -3)) * kernel[0];

I think instead of offsetting the texture coordinates , you are sampling all the points from the same coordinate but different mip levels

I used

color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2(-3 * PixelSize.x , 0)) * kernel[0];

And it seems to be fine

  1. I don’t think you will get the effect you want from this approach , the blur is very subtle at only ± 3 pixels

Have a look at this and see if you can even tell what is going on

Top left original image
Top right original image passed through a y blur
Bottom left top right passed through a x blur

I think you may want a different effect

I don’t think the shader is an issue because if you look at the first image I posed you can see the X shader is working it’s just the Y that’s not applying. You can swap where I’m doing each shader and then the Y works and not the X. So I think it’s just not applying the shader when I change the render target or something. Even if the effect is not working quite right it’s still that case that I want to be able to apply two convolutions to make a gaussian regardless of exactly how the shader works so I would still like to fix the issue of the shader not applying and worry about the exact shader code later.

I don’t think your shaders are doing what you expect them to do.

So ignore the fact you are doing two passes, lets just think about a single pass.

You supple the shader an image that is RED text on a black background, and apply a gaussian blur in a direction.

The blur will sample 7 pixels in a direction.

Since you supplied it a texture that is red and black., the result should ONLY include red and black

So where is the white coming from?

If the white , well blurred drop shadow, is what you are trying to achieve then there are far better ways of doing it than a couple of gaussian blurs

What are you trying to achieve?

Maybe we can point you at a better solution.

The white text is having blur applied, the red text is just a baseline so you can see no Y blurring is happening. I am doing a sort of drop shadow here but I need the blur in other places as well for other things so even if i did a different text drop shadow I would still want a two pass gaussian. I don’t think it’s unresonable to want to figure out how to do a two pass shader.

I think you are right that something is wrong with the shader or I have the wrong blend state set but what I want to be able to do regardless or what the exact shader code is draw something (orange line), then draw text with two shaders applied (white), then draw on top (red text and blue line).

So the two problems I’m having are first, how do I get two shaders to apply, and second, how to I preserve the orange line underneath. If I can do that I can sort the exact shader code out later.

Well here is an image to show you what a working gaussian blur looks like zoomed in a hell of a lot

Top original image
Middle gaussian blur in y direction
Bottom gaussian blur in both

You don’t get the effect you are looking for

If you then draw the text over the top in red , it looks like this

You are only applying the blur over three pixels, in the old days when we had 320 by 200 displays, that would look great.

Even in the days when games made it to hi-res, which in those days was 640 by 480 pixels, it would be okay

These days when 1920 by 1080 is low res, three pixels won’t be worth doing.

I think you should tell us what you really want to do, gives us an example image, and we will help you come up with something.

I am certain the shaders are broken in you current code, but even if they were working, I don’t think you would be happy.

Please ignore the context, I just want to run two shader passes and have it draw over stuff that’s already been drawn. The context of why is not important. Can I see the code you are using for that text above?

Very simple

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.SetRenderTarget(unblurred);
        GraphicsDevice.Clear(Color.Transparent);
        spriteBatch.Begin();
        spriteBatch.DrawString(LargeFont, "Un-blurred text", new Vector2(10, 10), Color.White);
        spriteBatch.End();

        GraphicsDevice.SetRenderTarget(yblur);
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, blur_effect, null);
        spriteBatch.Draw(unblurred, Vector2.Zero, Color.White);
        spriteBatch.End();

        GraphicsDevice.SetRenderTarget(xblur);
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, blur_effect2, null);
        spriteBatch.Draw(yblur, Vector2.Zero, Color.White);
        spriteBatch.End();

        GraphicsDevice.SetRenderTarget(null);
        GraphicsDevice.Clear(Color.CornflowerBlue);
        spriteBatch.Begin();
        spriteBatch.Draw(unblurred, new Vector2(0, 0), Color.White);
        spriteBatch.Draw(yblur, new Vector2(512, 0), Color.White);
        spriteBatch.Draw(xblur, new Vector2(0, 512), Color.White);
        spriteBatch.End();
        base.Draw(gameTime);
    }

As I said I think your shaders are broken

#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif

Texture2D SpriteTexture;
float2 PixelSize;
static const float kernel[7] = { 0.00598, 0.060626, 0.241843, 0.383103, 0.241843, 0.060626, 0.00598 };

sampler2D SpriteTextureSampler = sampler_state
{
	Texture = <SpriteTexture>;
};

struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 TextureCoordinates : TEXCOORD0;
};

float4 MainPS(VertexShaderOutput input) : COLOR
{
float4 color = float4(0, 0, 0, 0);

color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2(-3 * PixelSize.x , 0)) * kernel[0];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2(-2 * PixelSize.x , 0)) * kernel[1];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2(-1 * PixelSize.x , 0)) * kernel[2];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates) * kernel[3];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2( 1 * PixelSize.x , 0)) * kernel[4];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2( 2 * PixelSize.x , 0)) * kernel[5];
color += tex2D(SpriteTextureSampler,input.TextureCoordinates + float2( 3 * PixelSize.x , 0)) * kernel[6];

return color * input.Color;
}

technique SpriteDrawing
{
pass P0
{
	PixelShader = compile PS_SHADERMODEL MainPS();
}
};

To do a double pass in the technique you just define p0 p1

for example

technique SpriteDrawing
{
pass P0
{
	PixelShader = compile PS_SHADERMODEL FirstPS();
}
pass P1
{
	PixelShader = compile PS_SHADERMODEL SecondPS();
}
};

Im pretty sure that sprite batch’s draw is set up so it will automatically call the second pass shader.
Of course you need a second shader and im guessing it will probably want to offset the draw position.

The P denotes pass the number 0 the index in shader lingo so your defining P[0] = … and P[1] =…