Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Bug Graphics.DrawProcedural on Vulkan - "Shader requires vertex data"

Discussion in 'General Graphics' started by mptp, Apr 16, 2021.

  1. mptp

    mptp

    Joined:
    May 30, 2014
    Posts:
    29
    Hello!

    I'm trying to render vertex data that exists in a StructuredBuffer, generated from a compute shader using Graphics.DrawProcedural.

    Everything works perfectly on Windows DX11, but on Android (Vulkan) my data isn't being rendered, and I get the following error in logcat:

    Shader requires vertex data and is not compatible with DrawIndexedNullGeometry

    I get a value of 72 for SystemInfo.maxComputeBufferInputsVertex, so the GPU definitely seems to support StructuredBuffers. I feel like this should definitely be working, so I'm wondering whether this is perhaps a Unity bug?
    I've also confirmed that SUPPORT_STRUCTUREDBUFFER is being defined on Vulkan.

    Details:
    • Unity Version: 2019.4.23f1
    • Graphics API: Vulkan
    • Phone: Xiaomi Mi 9T Pro
    • Android Version: 10 QKQ1.190825.002
    Edit: I just tried it on the most recent version of Unity, 2021.1.3f1, and I get an identical error

    Has anyone else had success getting Graphics.DrawProcedural to work on Android?

    I've made a very pared-down example, which still has the error - literally just building a procedural quad on the CPU and sending it to a shader as a ComputeBuffer. Works on PC, fails with the same error on Android.

    The example project is attached to the post (31kB unity project)

    =====================================================================

    Here's the code:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class MinimalTest : MonoBehaviour
    4. {
    5.     public Material DrawMaterial;
    6.     private ComputeBuffer _buffer;
    7.     private GraphicsBuffer _indexBuffer;
    8.     private Bounds _bounds;
    9.  
    10.     public void Awake()
    11.     {
    12.         _buffer = new ComputeBuffer(4, sizeof(float) * 4);
    13.         _buffer.SetData(new[]
    14.         {
    15.             new Vector4(-0.5f, 0f, -0.5f, 0.0f),
    16.             new Vector4(-0.5f, 0f,  0.5f, 0.3f),
    17.             new Vector4( 0.5f, 0f,  0.5f, 0.6f),
    18.             new Vector4( 0.5f, 0f, -0.5f, 1.0f),
    19.         });
    20.  
    21.         _indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Index, 6, sizeof(int));
    22.         _indexBuffer.SetData(new[] {0, 1, 2, 2, 3, 0});
    23.      
    24.         _bounds = new Bounds(Vector3.zero, Vector3.one * 10000f);
    25.      
    26.         DrawMaterial.SetBuffer("_VertexBuffer", _buffer);
    27.     }
    28.  
    29.     public void Update()
    30.     {
    31.         Graphics.DrawProcedural(DrawMaterial, _bounds, MeshTopology.Triangles, _indexBuffer, _indexBuffer.count);
    32.     }
    33.  
    34.     public void OnDestroy()
    35.     {
    36.         _buffer.Release();
    37.         _indexBuffer.Release();
    38.     }
    39. }
    And here's the shader:

    Code (CSharp):
    1. Shader "Test/MinimalTest"
    2. {
    3.     SubShader
    4.     {
    5.         Tags { "RenderType"="Opaque" }
    6.  
    7.         CGPROGRAM
    8.         #pragma surface surf Standard fullforwardshadows vertex:vert
    9.         #pragma target 5.0
    10.      
    11. #if SHADER_TARGET >= 35 && (defined(SHADER_API_D3D11) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL) || defined(SHADER_API_SWITCH) || defined(SHADER_API_VULKAN) || (defined(SHADER_API_METAL) && defined(UNITY_COMPILER_HLSLCC)))
    12.         #define SUPPORT_STRUCTUREDBUFFER
    13. #endif
    14.      
    15. #ifdef SUPPORT_STRUCTUREDBUFFER
    16.         StructuredBuffer<float4> _VertexBuffer;
    17. #endif
    18.  
    19.         struct Input {
    20.             float4 color : COLOR;
    21.         };
    22.      
    23.         struct appdata
    24.         {
    25.             float4 vertex : POSITION;
    26.             float3 normal : NORMAL;
    27.             uint vid : SV_VertexID;
    28.         };
    29.      
    30.         void vert(inout appdata v, out Input o)
    31.         {
    32.             UNITY_INITIALIZE_OUTPUT(Input, o);
    33. #ifdef SUPPORT_STRUCTUREDBUFFER
    34.             float4 CurrentVertex = _VertexBuffer[v.vid];
    35.          
    36.             v.vertex.xyz = CurrentVertex;
    37.             v.normal = float3(0, 0, 1);
    38.             o.color = float4(1, 1, 1, 1);
    39. #endif
    40.         }
    41.  
    42.         void surf (Input IN, inout SurfaceOutputStandard o)
    43.         {
    44.             o.Albedo = float4(1, 1, 1, 1);
    45.         }
    46.         ENDCG
    47.     }
    48. }
     

    Attached Files:

    Last edited: Apr 16, 2021
  2. florianpenzkofer

    florianpenzkofer

    Unity Technologies

    Joined:
    Sep 2, 2014
    Posts:
    479
    Your vertex shader (indirectly) reads the vertex position of appdata and because DrawProcedural does not bind any vertex buffers we generate an error and skip the draw to avoid potentially crashing.

    As a workaround for this case you can add 'v.vertex.w = 0.0;' after 'v.vertex.xyz = CurrentVertex;'.
    By overwriting 'v.vertex' completely Unity's shader compiler can strip the code that would otherwise read the input vertex position.
     
  3. mptp

    mptp

    Joined:
    May 30, 2014
    Posts:
    29
    Oohhhh snap it works! THANK YOU!!!

    This is actually a huge deal for me - I've basically given up on Android / Oculus Quest development for the last two-ish years since this problem broke all my compute shader stuff and that's 90% of my work. I'm so happy right now c':

    It would be cool if the error was a little more specific or if perhaps the documentation on DrawProcedural mentioned the requirement that the vertex shader completely overwrites the data with the POSITION semantic.
     
  4. tvirolai

    tvirolai

    Unity Technologies

    Joined:
    Jan 13, 2020
    Posts:
    79
    There is a solution for this that is not even a workaround:



    Code (CSharp):
    1. void vert(inout appdata v, out Input o)
    Should actually be

    Code (CSharp):
    1. void vert(out appdata v, out Input o)
    The inout qualifier means that it wants to read from it. By explicitly specifying it as out there is no need to do any fancy tricks to get the compiler to optimize it away. And that's the intented way to use it. inout basically means that the shader indeed wants a vertexbuffer, and thus cannot be used with the DrawProcedural.

    Though in this particular case it's not fully supported, as the surface shader system wants you to actually have a vertexbuffer.
     
    Last edited: May 20, 2021
  5. mptp

    mptp

    Joined:
    May 30, 2014
    Posts:
    29
    That makes a lot of sense - thanks for the clarity, I'll definitely do it that way from now on!