How to Use Xcode to Find Out the Shader Code Bug?

I recently discovered a small bug related to the surface shader in Unity. You can see the rendering artifact shown below.

the rendering artifact
Sometimes people often attribute similar rendering artifacts to using HDR with bloom effect, but in fact the surface shader is wrong, at least as discussed in this article.
the bloom issue

So I write this article to record the process of debugging this issue. At the same time, this article will also introduce how to use xcode tools to debug shader code to find existing rendering bugs.

The Debugging process

If you want to investigate rendering issues on iOS, just build and run your project from Xcode, then click the camera button on Xcode’s debugging toolbar to enable the Metal Frame Debugger.

ref apple doc

Then select the render encoder that you are interested in, such as the encoder related to the downsample of bloom.

bloom process

As you can see, at the beginning of the downsample process of bloom, there is a pixel that does not look normal. At this time, I think we find the target. But during the bloom process, the texture has been downsampled. To find the original pixel, let’s investigate the render encoder before bloom to see if there are any more valuable discoveries.

encoder 0

At first glance, nothing seems abnormal, then let’s modify the attachment’s setting to filter out our target. (By the way, right click on the attachment, you can choose whether to display visible overlays such as wireframe or flip vertical etc)

encoder 0

It’s here! Like the most shining star in the night sky. 😄

After finding the target pixel, we can use the shader profiler tool to debug the pixel shader code for this pixel. Seeing that its value is very abnormal, I am almost certain that there is a bug in this “Legacy Shaders/Bumped Specular” shader.

R: 50176.0 G: 4992.0

Select the target pixel, then click the Debug button. Let’s see what the issue is!

R: 50176.0 G: 4992.0

    u_xlat16_3.x = dot(input.TEXCOORD1.xyz, float3(u_xlat16_2.xyz));
    u_xlat16_3.y = dot(input.TEXCOORD2.xyz, float3(u_xlat16_2.xyz));
    u_xlat16_3.z = dot(input.TEXCOORD3.xyz, float3(u_xlat16_2.xyz));
    u_xlat16_0.x = dot(u_xlat16_3.xyz, u_xlat16_3.xyz);
    u_xlat16_0.x = rsqrt(u_xlat16_0.x);
    u_xlat0.xyz = float3(u_xlat16_0.xxx) * float3(u_xlat16_3.xyz);

As you can see, the result of dot(u_xlat16_3.xyz, u_xlat16_3.xyz).x is 0, then the rsqrt operator operates on the value. Yes, the reciprocal square root of 0 is ideally infinity and the square root of negative values ideally returns NaN (Not a Number). I think we find the shader code bug.

Simply put, the shader code does an unsafe normalize here.

normalize for a float3 vector could be implemented like this.

float3 normalize(float3 v)
{
  return rsqrt(dot(v,v))*v;
}

The ShaderLab Code

As many Unity developers know, the shader we write in Unity is called ShaderLab, and then Unity will compile the ShaderLab into shader code for the graphics library/platform.

And the Legacy Shaders / Bumped Specular is a built-in shader. So we can download the shaderlab code from the Unity download page and use an IDE open the shader file.

surface shader code

As shown above, the code of this shader is very very simple, and there is no normalize operation for the vector. Why?

Because it is a surface shader. And surface shaders in Unity is a code generation approach that makes it much easier to write lit shaders than using low level vertex/pixel shader programs.

If you want to see the real shaderlab code that generated from a surface shader, you can click the show generated code button on the shader inspector window.

show generated code

Then we get the real vertex/pixel shaderlab code that will be compiled into the native shader code later. Furthermore, we find the unsafe normalization operation generated by Unity.

generated shader code

Workaround

Of course, the purpose of this article, as I said at the beginning of the article, is mainly to record the process of debugging shader code bugs and introduce the Xcode shader profiler tool. But how should this issue or similar issues be solved?

I personally think it is just not to use the surface shaders. Because most of their code is generated by Unity and you can’t control. You can use traditional standard shaders or consider new shaders for scriptable render pipelines.

Developing and Debugging Metal Shaders

I recently find microsoft learn helpful. Check it out!

Microsoft Learn


Subscribe To Jiadong Chen's Blog

Avatar
Jiadong Chen
Senior Software Developer

Husbandã…£Programmerã…£Authorã…£Bloggerã…£Speakerã…£Microsoft @MVPAward In Developer Technologiesã…£Previously @Unity3d ã…£Opinions = my own.

comments powered by Disqus

Related