Marsir
Marsir

Reputation: 119

A strange bug about GPU Instancing on phones with android version 7.0 and below equipped Adreno

When i use GPU instancing, i found that there is a problem that is difficult to understand on mobile phones equipped with Qualcomm processors with Android version 7.0 and below. It seems to be related to the memory layout,but only happens on the phone types described above.Let's reproduce this problem step by step. First, there is a shader for GPU Instancing

    Shader "Unlit/UniformTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
 
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            CGPROGRAM
 
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma multi_compile _ NO_SPRITE_RENDERER
 
            #include "UnityCG.cginc"
 
            #ifdef UNITY_INSTANCING_ENABLED
 
            UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
                UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
                UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
            UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
     
            #define _RenderData    UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
            #define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
 
            #endif
 
            #ifndef UNITY_INSTANCING_ENABLED
            float4 _RenderData;
            float4 _MainTexOffset;
            #endif
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            sampler2D _MainTex;
 
            v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                #ifdef UNITY_INSTANCING_ENABLED
                float4 renderData = _RenderData;
                v.vertex.xy += renderData.zw;
                o.uv = v.uv * _MainTexOffset.xy + _MainTexOffset.zw;
                #else
                o.uv = v.uv;
                #endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                fixed4 col = tex2D(_MainTex, i.uv);
                #ifdef UNITY_INSTANCING_ENABLED
                float4 renderData = _RenderData;
                col.a *= renderData.x;
                #endif
                return col;
            }
            ENDCG
        }
    }
}

And there is a program to use this shader to draw 5 quad meshs at once:

public class DrawTest : MonoBehaviour
{
   public Shader shader;
   public Texture2D tex;
   private Mesh m_mesh;
   private Material m_mat;
   private MaterialPropertyBlock m_block;
   private Matrix4x4[] m_renderArr;

   void Start()
   {
       m_mat = new Material(shader);
       m_mat.enableInstancing = true;
       m_mat.SetTexture("_MainTex", tex);
       m_block = new MaterialPropertyBlock();
       m_block.SetVectorArray("_RenderDataArray",
           new Vector4[] {
               new Vector4(0.5f, 0f, 0f, 0f),
               new Vector4(0.3f, 0f, 0f, 0f),
               new Vector4(0.6f, 0f, 0f, 0f),
               new Vector4(0.8f, 0f, 0f, 0f),
               new Vector4(1f, 0f, 0f, 0f)
           });
       m_block.SetVectorArray("_MainTexOffsetArray",
           new Vector4[] {
               new Vector4(1f, 1f, 0.2f, 0f),
               new Vector4(1f, 1f, 0.4f, 0f),
               new Vector4(1f, 1f, 0.6f, 0f),
               new Vector4(1f, 1f, 0.8f, 0f),
               new Vector4(1f, 1f, 1f, 0f)
           });
       m_renderArr = new Matrix4x4[5];
       m_renderArr[0] = Matrix4x4.Translate(new Vector3(1.5f, 2.5f, -2f));
       m_renderArr[1] = Matrix4x4.Translate(new Vector3(-1.5f, 2.5f, -2f));
       m_renderArr[2] = Matrix4x4.Translate(new Vector3(1.5f, -1f, -2f));
       m_renderArr[3] = Matrix4x4.Translate(new Vector3(-1.5f, -1f, -2f));
       m_renderArr[4] = Matrix4x4.Translate(new Vector3(0f, 0.75f, -2f));

       var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
       m_mesh = quad.GetComponent<MeshFilter>().sharedMesh;
       Destroy(quad);

   }

   void Update()
   {
       Graphics.DrawMeshInstanced(m_mesh, 0, m_mat, m_renderArr, m_renderArr.Length, m_block);
   }

   private float delta;
   private float sliderValue = 0.5f;
   private void OnGUI()
   {
       GUILayout.Space(300f);
       var val = GUILayout.HorizontalSlider(sliderValue, 0f, 1f, GUILayout.Width(200f), GUILayout.Height(50f));
       delta = val - sliderValue;
       if (!Mathf.Approximately(0f, delta))
       {
           Transform camTrans = Camera.main.transform;
           camTrans.position += camTrans.forward * delta * 10f;
           delta = 0f;
       }
       sliderValue = val;
   }

   private void OnDestroy()
   {
       Destroy(m_mat);
   }
}

now we see the result,the right top quad is flickering: result of image using above code

If we change the shader code,move _MainTexOffsetArray to another uniform buffer:

            #ifdef UNITY_INSTANCING_ENABLED
 
            UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
                UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)    
            UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
     
            UNITY_INSTANCING_BUFFER_START(AnotherDataPerDraw)
                UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
            UNITY_INSTANCING_BUFFER_END(AnotherDataPerDraw)
 
            #define _RenderData    UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
            #define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(AnotherDataPerDraw, _MainTexOffsetArray)
 
            #endif

Another result

if we revert the shader code and add another property between _RenderDataArray and _MainTexOffsetArray:

           #ifdef UNITY_INSTANCING_ENABLED
 
            UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
                UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
                UNITY_DEFINE_INSTANCED_PROP(float2,_FlipArray)
                UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
            UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
     
            #define _RenderData    UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
            #define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
            #define _Flip         UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw,_FlipArray)
 
            #endif

this time more quad is flickering: enter image description here

If we let fragment shader does not directly use uniform, but uses vertex interpolation,then every thing is OK.I'm not going to show normal pictures here.

Shader "Unlit/UniformTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
 
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            CGPROGRAM
 
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma multi_compile _ NO_SPRITE_RENDERER
 
            #include "UnityCG.cginc"
 
            #ifdef UNITY_INSTANCING_ENABLED
 
            UNITY_INSTANCING_BUFFER_START(CustomDataPerDraw)
                UNITY_DEFINE_INSTANCED_PROP(float4, _RenderDataArray)
                UNITY_DEFINE_INSTANCED_PROP(float2,_FlipArray)
                UNITY_DEFINE_INSTANCED_PROP(float4,_MainTexOffsetArray)
            UNITY_INSTANCING_BUFFER_END(CustomDataPerDraw)
     
            #define _RenderData    UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _RenderDataArray)
            #define _MainTexOffset UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw, _MainTexOffsetArray)
            #define _Flip         UNITY_ACCESS_INSTANCED_PROP(CustomDataPerDraw,_FlipArray)
 
            #endif
 
            #ifndef UNITY_INSTANCING_ENABLED
            float4 _RenderData;
            float4 _MainTexOffset;
            #endif
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                // Fragment shader does not directly use uniform variable, but uses vertex interpolation
                float alpha : SD_ALPHA;
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            sampler2D _MainTex;
 
            v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                #ifdef UNITY_INSTANCING_ENABLED
                float4 renderData = _RenderData;
                v.vertex.xy += renderData.zw;
                o.uv = v.uv * _MainTexOffset.xy + _MainTexOffset.zw;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // Fragment shader does not directly use uniform variable, but uses vertex interpolation
                o.alpha = renderData.x;
                #else
                o.uv = v.uv;
                o.alpha = 1.0;
                #endif
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                fixed4 col = tex2D(_MainTex, i.uv);
                #ifdef UNITY_INSTANCING_ENABLED
                // Fragment shader does not directly use uniform variable, but uses vertex interpolation
                col.a *= i.alpha;
                #endif
                return col;
            }
            ENDCG
        }
    }
}

Again, in our many tests, only on mobile phones equipped with Qualcomm processors with Android version 7.0 and below had this problem; Use RenderDoc to see more details, uniform data is not passed into the fragment Shader. I I'm not sure what the reason is. Is it because the vertex shader and fragment shader in the same shader program cannot use uniform data at the same time in Android 7.0 or is this a bug?

Upvotes: 2

Views: 1185

Answers (0)

Related Questions