0
\$\begingroup\$

I'm attempting to create a mesh using compute buffers in Unity(2022.2.11). So far I've got the mesh rendering however parts of it periodically flicker frame to frame.

I'm assuming I've done something wrong but I'm having trouble figuring out what is wrong.

There are 4 relevant unity assets.

  1. A Material
  2. A CG Shader
  3. A Compute Shader
  4. A C# script

TriangleRenderShader.shader

Shader "Custom/TriangleRenderShader"
{
    Properties { }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                uint vertexID : SV_VertexID;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            struct DrawTriangle
            {
                float3 vertices[3];
            };

            StructuredBuffer<DrawTriangle> _DrawTrianglesBuffer;

            float4 _Color;

            v2f vert(appdata v)
            {
                v2f o;

                DrawTriangle tri = _DrawTrianglesBuffer[v.vertexID / 3];

                o.vertex = UnityObjectToClipPos(tri.vertices[v.vertexID % 3]);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(1.0, 0.0, 0.0, 1.0);
            }
            ENDCG
        }
    }
}

TriangleComputeShader.compute

#pragma kernel CSMain

struct DrawTriangle
{
    float3 vertices[3];
};

AppendStructuredBuffer<DrawTriangle> _DrawTrianglesBuffer;


struct IndirectArgs
{
    uint numVertices;      // Number of vertices to render
    uint numInstances;     // Number of instances to render
    uint startVertex;      // Index of the first vertex to render
    uint startInstance;    // Index of the first instance to render
    uint unused;
};

RWStructuredBuffer<IndirectArgs> _IndirectArgsBuffer;

[numthreads(8, 8, 8)]
void CSMain (uint3 dispatchThreadID : SV_DispatchThreadID,
             uint3 groupThreadID : SV_GroupThreadID,
             uint3 groupID : SV_GroupID)
{
    // Calculate the offset for this thread group
    float3 offset = float3(groupThreadID.x, groupThreadID.y, groupThreadID.z * 2) * 2;

    DrawTriangle tri = (DrawTriangle)0;
    tri.vertices[0] = float3(0, 1, 0) + offset;
    tri.vertices[1] = float3(1, 1, 0) + offset;
    tri.vertices[2] = float3(1, 0, 0) + offset;
    _DrawTrianglesBuffer.Append(tri);

    // Update the number of verticies to match the number in "_DrawTrianglesBuffer"
    InterlockedAdd(_IndirectArgsBuffer[0].numVertices, 3);
}

TriangleRenderer.cs

using UnityEngine;
using UnityEngine.Rendering;

public class TriangleRenderer : MonoBehaviour
{
    [SerializeField]
    private ComputeShader computeShader;

    private int kernelIndex;
    private uint threadGroupsX;
    private uint threadGroupsY;
    private uint threadGroupsZ;

    private ComputeBuffer drawTrianglesBuffer;
    private ComputeBuffer indirectArgsBuffer;

    private uint[] argsBufferData = new uint[5]
    {
        0,  // Number of vertices to render (Calculated in the compute shader with "InterlockedAdd(_IndirectArgsBuffer[0].numVertices, 3);")
        1,  // Number of instances to render (should only be 1 instance since it should produce a single mesh)
        0,  // Index of the first vertex to render
        0,  // Index of the first instance to render
        0   // Not used
    };

    [SerializeField]
    private Material rendererMaterial;

    private Bounds renderBounds = new Bounds(Vector3.zero, Vector3.one * 1000); // just use a big box so nothing is culled.

    private void Start()
    {
        kernelIndex = computeShader.FindKernel("CSMain");
        computeShader.GetKernelThreadGroupSizes(kernelIndex, out threadGroupsX, out threadGroupsY, out threadGroupsZ);

        int numTriangles = (int)threadGroupsX * (int)threadGroupsY * (int)threadGroupsZ;
        int triangleDataSize = sizeof(float) * 3 * 3;
        drawTrianglesBuffer = new ComputeBuffer(numTriangles * triangleDataSize, triangleDataSize, ComputeBufferType.Append);
        computeShader.SetBuffer(kernelIndex, "_DrawTrianglesBuffer", drawTrianglesBuffer);

        indirectArgsBuffer = new ComputeBuffer(1, argsBufferData.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        computeShader.SetBuffer(kernelIndex, "_IndirectArgsBuffer", indirectArgsBuffer);
    }

    private void Update()
    {
        drawTrianglesBuffer.SetCounterValue(0); // clear append buffer so new triangles are appended to the beginning
        indirectArgsBuffer.SetData(argsBufferData);
        computeShader.Dispatch(kernelIndex, (int)threadGroupsX, (int)threadGroupsY, (int)threadGroupsZ);

        rendererMaterial.SetBuffer("_DrawTrianglesBuffer", drawTrianglesBuffer);

        Graphics.DrawProceduralIndirect(rendererMaterial, renderBounds, MeshTopology.Triangles, indirectArgsBuffer, 0, null, null, ShadowCastingMode.Off, true, gameObject.layer);
    }

    private void OnDestroy()
    {
        drawTrianglesBuffer.Dispose();
        indirectArgsBuffer.Dispose();
    }
}

Setup Notes:

  • Create the 3 files above and an empty scene
  • Make a new material apply the TriangleRenderShader.shader to it
  • Make an empty game object, and apply the TriangleRenderer.cs script to it
  • Drag the compute shader and material into the slots

Initial Assumptions:

  • My initial assumption was that the compute shader hasn't finished running by the time the render shader attempts to render the triangle buffer, however I've attempted to forcibly wait for for the compute shader to finish using "GetData" on one of the buffers but that didn't seem to help at all, so I'm at a loss as to what it could be.
  • My next assumption was I man be creating the drawTrianglesBuffer compute buffer wrong by using an incorrect size or something, but most things I try when changing that only appear to make it worse, so I'm assuming its something else...

Example of the bug: Here is the mesh being rendered incorrectly on the currently paused frame. The green circled areas are spots where only the top rows of the mesh are rendered for whatever reason. These areas are not consistent frame to frame, some frames they all show up, other frames random chunks are missing. Image of parts of the mesh not being rendered Gif of parts of the mesh not being rendered

\$\endgroup\$
2
  • \$\begingroup\$ not familiar with compute shaders but is it possible some kind of synchronization is needed to force the compute shader to finish before the indirect draw starts? \$\endgroup\$
    – user253751
    Apr 12 at 0:48
  • \$\begingroup\$ @user253751 I thought this as well initially, so I made the compute shader only run a single time, used AsyncGPUReadback to wait for the compute shader to finish, then retained the resulting buffer to display a static mesh, but even when doing that, it was missing sections meaning its likely not a synchronization issue. \$\endgroup\$
    – Hex Crown
    Apr 15 at 1:34

1 Answer 1

2
\$\begingroup\$

So I've finally figured out the issue, it was an oversight on my part when calling computeShader.Dispatch, I had forgotten/overlooked that the parameters that associate with the number of thread X/Y/Z groups effectively acts like a multiplier. This was resulting in append buffer attempting to write beyond its max index since in the example I allocate a buffer size of sizeof(float) * 3 * 3 * numTriangles which was resulting in a buffer size of 18,432 (8*8*8) triangles, but when the compute shader was running, it was effectively doing that ((8*8) * (8*8) * (8*8)) triangles. TD;DR: Double check to make sure you're dispatching the expected number of thread groups.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .