首页 > 解决方案 > 安卓 7.0 及以下配备 Adreno 的手机上关于 GPU 实例化的奇怪错误

问题描述

我在使用GPU实例化的时候,发现在安卓7.0及以下搭载高通处理器的手机上存在一个难以理解的问题。这似乎与内存布局有关,但仅在上述手机类型上发生。让我们逐步重现此问题。首先,有一个用于 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
        }
    }
}

还有一个程序可以使用这个着色器一次绘制 5 个四边形网格:

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);
   }
}

现在我们看到结果,右上角的四边形在闪烁: 使用上述代码的图像结果

如果我们更改着色器代码,将 _MainTexOffsetArray 移动到另一个统一缓冲区:

            #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

另一个结果

如果我们恢复着色器代码并在 _RenderDataArray 和 _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

这次更多的四边形在闪烁: 在此处输入图像描述

如果我们让fragment shader不直接使用uniform,而是使用顶点插值,那么一切都OK。我这里就不展示普通图片了。

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
        }
    }
}

同样,在我们的多次测试中,只有搭载高通处理器的安卓7.0及以下版本的手机才会出现这个问题;使用RenderDoc查看更多细节,统一数据没有传入片段Shader。我不确定是什么原因。是因为同一着色器程序中的顶点着色器和片段着色器在Android 7.0中不能同时使用统一数据还是这是一个错误?

标签: androidunity3dopengl-esshadergeometry-instancing

解决方案


推荐阅读