Runtime compilation error on shader code

Hello, I’m trying to port some shaders from XNA4.0 to Monogame, but I’m getting a weird error. The compilation on MGFX goes great, but when the actual code runs I get shader compilation error on drawing the mesh:

Unhandled Exception:
System.InvalidOperationException: Shader Compilation Failed occurred

Here’s the shader code:

float4x4 World;
float4x4 View;
float4x4 Projection;
float specularIntensity = 0.8f;
float specularPower = 0.5f; 
texture Texture;
sampler diffuseSampler = sampler_state
{
    Texture = <Texture>;
    MAGFILTER = LINEAR;
    MINFILTER = LINEAR;
    MIPFILTER = LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

texture SpecularMap;
sampler specularSampler = sampler_state
{
    Texture = <SpecularMap>;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

texture NormalMap;
sampler normalSampler = sampler_state
{
    Texture = <NormalMap>;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

struct VertexShaderInput
{
    float4 Position : SV_POSITION;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
    float3 Binormal : BINORMAL0;
    float3 Tangent : TANGENT0;
};

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD0;
    float2 Depth : TEXCOORD1;
    float3x3 tangentToWorld : TEXCOORD2;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    float4 worldPosition = mul(float4(input.Position.xyz,1), World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    output.TexCoord = input.TexCoord;
    output.Depth.x = output.Position.z;
    output.Depth.y = output.Position.w;

    // calculate tangent space to world space matrix using the world space tangent,
    // binormal, and normal as basis vectors
    output.tangentToWorld[0] = mul(input.Tangent, World);
    output.tangentToWorld[1] = mul(input.Binormal, World);
    output.tangentToWorld[2] = mul(input.Normal, World);

    return output;
}
struct PixelShaderOutput
{
    float4 Color : COLOR0;
    float4 Normal : COLOR1;
    float4 Depth : COLOR2;
};

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
    PixelShaderOutput output;
    output.Color = tex2D(diffuseSampler, input.TexCoord);
    
    float4 specularAttributes = tex2D(specularSampler, input.TexCoord);
    //specular Intensity
    output.Color.a = specularAttributes.r;
    
    // read the normal from the normal map
    float3 normalFromMap = tex2D(normalSampler, input.TexCoord);
    //tranform to [-1,1]
    normalFromMap = 2.0f * normalFromMap - 1.0f;
    //transform into world space
    normalFromMap = mul(normalFromMap, input.tangentToWorld);
    //normalize the result
    normalFromMap = normalize(normalFromMap);
    //output the normal, in [0,1] space
    output.Normal.rgb = 0.5f * (normalFromMap + 1.0f);

    //specular Power
    output.Normal.a = specularAttributes.a;

    output.Depth = input.Depth.x / input.Depth.y;
    return output;
}
technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

Do you have any idea what’s wrong here?

I don’t see anything wrong. I know it doesn’t help but I’ll mention the usual suspects I checked in case it sparks any ideas.

  • You use every column of your parameter matrices so the compiler won’t optimize them away and shift your parameters.
  • Your pixel shader input exactly matches your vertex shader output.
  • You don’t read from the SV_POSITION field in the pixel shader.
  • You take textures as parameters and use tex2D so you won’t run into some of the common mistakes with texture and sampler registers.

I don’t remember how off the top of my head, but if you set a breakpoint and inspect the effect when it’s loaded, you should be able to get the GLSL string which might have a more obvious bug.

So, time to answer my own question :slight_smile: The issue I had is due to the fact (I think) that Monogame on Android does not support setting multiple rendertargets. If you do so, it will fail when trying to populate the rendertargets therefore in code this will be the structure:

struct PixelShaderOutput
{
    float4 Color : COLOR0;
    float4 Normal : COLOR1;
    float4 Depth : COLOR2;
};

So, if that’s not clear how to fix it: just refactor the code into multiple methods so that a single pass will return only one value. On level of C#, you need to set each rendertarget separately and draw the scene obviously for each rendertarget separately.

Hmm I thought MonoGame had Android MRT support, assuming your devide supports GLES 3.0. I don’t know the easiest way to check other than maybe googling it for your specific device. Either it’s too old or you’re right that Android MRT was never working.

It’s Sony XA1 https://www.gsmarena.com/sony_xperia_xa1-8596.php so it should support it I think :slight_smile:

You’re right, that GPU should support GLES 3.1. Are you on MG 3.7?

Here’s the PR I was thinking of, but the discussion left it unclear whether it added MRT support. I thought the only requirement was GLES 3.0+ but maybe not. https://github.com/MonoGame/MonoGame/pull/5993#issuecomment-335048230

Yes, I’m on 3.7.1. It would be cool to have muliple rendertargets feature running one day if it is possible :slight_smile: