Android shader condition not working

Hello,

I have a game that I initially developed for Windows. I am now trying to port it to Android.

Pretty much nothing works. I’ve got the game to run, sort of, but none of the shaders behave as expected. However, now I’m encountering behavior which I think should be literally impossible so I am here to ask for help.

I have a texture which has a lot of green (0, 255, 0) color. My pixel shader is supposed to find these green parts and replace them with a certain color. This works great on Windows and allows me to easily re-color models.

float4 baseColor = tex2D(carTextureSampler, PSIn.TexCoords); if (baseColor.r <= 0.05 && baseColor.g >= 0.95 && baseColor.b <= 0.05) { baseColor = CarColor; }

The code samples the texture, checks if the color is close to neon green, and then replaces it with “CarColor” if so.

On Windows the behavior is as expected. On Android, ALL colors are replaced with CarColor. It’s as if the condition is always true no matter what.

Now take a look at this code:
float4 baseColor = tex2D(carTextureSampler, PSIn.TexCoords); if (baseColor.r == -0.05 && baseColor.g == 999999.95 && baseColor.b == -0.05) { baseColor = CarColor; baseColor = float4(0, 0, 1, 1); }
This condition is clearly impossible. I am checking if the channels have these impossible values, and if so, setting the color to blue (0, 0, 1) to debug. Somehow, all of the pixels are set to blue! This condition is still passing even though it should be impossible.

What is going on here? I don’t understand why my shaders aren’t working. I especially don’t understand how anything like this could possibly be happening.

Any help would be appreciated. :frowning:

Video cards are parallel processors as such they evaluate all possible conditions simultaneously and then select the right one at the end.

In other words in a shader, if you have code like so.

if(true)
{
 functionA();
}
else
{
 functionB();
}

Both functions A and B will execute their full code in what you can equate to be separate threads per pixel. At some point later on A or B 's output is selected.

You are probably going to have to post the full shader functions to determine why that if statement is the one falling thru at the end and not discarded.

Oh okay, that makes sense. Thank you for the response!

Here is the full technique:

//Output of the Vertex Shader
struct ShadowedSceneVSOut
{
	float4 Position             : SV_POSITION;
	float4 Pos2DAsSeenByLight    : TEXCOORD0;

	float2 TexCoords            : TEXCOORD1;
	float3 Normal                : TEXCOORD2;
	float4 Position3D            : TEXCOORD3;
};

//Output of the Pixel Shader
struct ShadowedScenePSOut
{
	float4 Color : COLOR0;
};

//Vertex shader. Vertices are VertexPositionNormalTexture
ShadowedSceneVSOut ShadowedSceneVertexShader(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0, float3 inNormal : NORMAL)
{
	ShadowedSceneVSOut Output = (ShadowedSceneVSOut)0;

	float4 worldPosition = mul(inPos, World);
	float4 viewPosition = mul(worldPosition, View);
	Output.Position = mul(viewPosition, Projection);
	Output.Pos2DAsSeenByLight = mul(inPos, xLightsWorldViewProjection); //I use this for shadows
	Output.Normal = normalize(mul(inNormal, (float3x3)World));
	Output.Position3D = mul(inPos, World);
	Output.TexCoords = inTexCoords;

	return Output;
}

//Pixel shader. This is where the weird behavior manifests
ShadowedScenePSOut ShadowedCarPixelShader(ShadowedSceneVSOut PSIn)
{
	ShadowedScenePSOut Output = (ShadowedScenePSOut)0;
	
	//The following line calculates diffuse intensity based on a shadow map.
	float diffuseLightingFactor = DiffuseIntensityWithShadows(PSIn.Pos2DAsSeenByLight, PSIn.Position3D, PSIn.Normal);

	//This is where I do recoloring. For some reason this branch always passes
	float4 baseColor = tex2D(carTextureSampler, PSIn.TexCoords);
	if (baseColor.r <= 0.05 && baseColor.g >= 0.95 && baseColor.b <= 0.05) {
		baseColor = CarColor;
	}
	
	//Calculate final color
	Output.Color = baseColor * (DiffuseColor * diffuseLightingFactor + AmbientColor * AmbientIntensity);

	return Output;
}

technique ShadowedCar
{
	pass Pass0
	{
		VertexShader = compile vs_3_0 ShadowedSceneVertexShader();
		PixelShader = compile ps_3_0 ShadowedCarPixelShader();
	}
}

//Used in the shadow method below
float DotProduct(float3 lightPos, float3 pos3D, float3 normal)
{
	float3 lightDir = normalize(lightPos - pos3D);
	return dot(-lightDir, normal);
}

//This method samples a shadow map texture (1024x1024)
float DiffuseIntensityWithShadows(float4 PosFromLight, float4 Position3D, float3 Normal) {
	float2 ProjectedTexCoords;
	ProjectedTexCoords[0] = PosFromLight.x / PosFromLight.w / 2.0f + 0.5f;
	ProjectedTexCoords[1] = -PosFromLight.y / PosFromLight.w / 2.0f + 0.5f;

	float diffuseLightingFactor = 0;
	if ((saturate(ProjectedTexCoords).x == ProjectedTexCoords.x) && (saturate(ProjectedTexCoords).y == ProjectedTexCoords.y))
	{
		float depthStoredInShadowMap = tex2D(ShadowMapSampler, ProjectedTexCoords).r;
		float realDistance = PosFromLight.z / PosFromLight.w;
		if ((realDistance - 1.0f / 100.0f) <= depthStoredInShadowMap)
		{
			diffuseLightingFactor = DotProduct(xLightPos, Position3D, Normal);
			diffuseLightingFactor = saturate(diffuseLightingFactor);
			diffuseLightingFactor *= DiffuseIntensity;
		}
	}
	else {
		diffuseLightingFactor = DotProduct(xLightPos, Position3D, Normal);
		diffuseLightingFactor = saturate(diffuseLightingFactor);
		diffuseLightingFactor *= DiffuseIntensity;
	}
	return diffuseLightingFactor;
}

I tried to add comments to make things a little more clear since its somewhat long.

If I had to guess, I think I may be doing something wrong with my shader method signatures or my use of semantics in the output structs.

I couldn’t reproduce this problem on windows but i vaguely recall running into it myself.

I can offer a couple things you can try just to see if you cant get a better sense of were the problem is coming from.

try using the [flatten] or [branch] directives just above the if statement.

manually reduce out the if statement by turning it into a mathematical expression.
in your case that would mean changing the if statement into something like so,

//if (baseColor.r <= 0.05 && baseColor.g >= 0.95 && baseColor.b <= 0.05) {
//	baseColor = CarColor;
//}
float ta = sign(-baseColor.r + 0.05f); // if positive it is true
float tb = sign(baseColor.g - .95f); // if positive it is true
float tc = sign(-baseColor.b + 0.05f); // if positive it is true
// combine the above into a duel pair of opposite expresions
float NT = max(0.0f , (ta + tb + tc - 2.0f) ); // if answer is true nt will be 1 otherwise 0
float NF = 1.0f - NT;
// blend the pair into a concrete answer
float4 finalAnswer = CarColor * NT + NF * baseColor;
// set then return the result;
output.Color = finalAnswer;
return output;

This way above it really cant branch i don’t think the compiler is smart enough to figure out this entire section is a conditional.
However if that does actually work its kinda bad (if it doesn’t work its probably the evaluation of the condition itself is always true somehow) if this does work it means it really is the if always falling thru and it really shouldn’t be. In this case im not sure what can be done to narrow down what could be cause of it.

You could make a minimal barebones example shader and game1 and submit it as a issue on github so that it can be reproduced and tested by them as this might be a android bug.