ComputeShader: StructuredBuffer sometimes empty in Metal

Hi all,

I’m trying to access a StructuredBuffer from a Compute Shader targeting Metal on a MacBook Pro, and finding that it is sometimes full of zeros the first time the shader runs…

My shader is really simple and just returns green if the first item in the StructuredBuffer has a non-zero value and red otherwise…

I only ever set the value for an item to 1 so I know it should always be non-zero (green) however it seems to sometimes be zero (red).

Is this a bug?

Unity Version: 2020.1.3f1

Shader Code:

#pragma kernel CSMain

RWTexture2D<float4> Result;

struct SomeObject
{
    float value;
};

StructuredBuffer<SomeObject> _Objects;
uint _ObjectsCount;

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    if (_ObjectsCount > 0 && !any(_Objects[0].value)) {
        SomeObject s = _Objects[0];
        // Red
        Result[id.xy] = float4(1, 0, 0, 1);
    } else {
        // Green
        Result[id.xy] = float4(0, 1, 0, 1);
    }
}

Script Code (Attached to the Camera):

using System.Collections.Generic;
using UnityEngine;

struct SomeObject
{
    public float value;
}

public class MyComputeShaderTest : MonoBehaviour
{
    public ComputeShader RayTracingShader;

    private RenderTexture _target;
    private ComputeBuffer _objectBuffer;

    private int _renderCount = 0;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_renderCount > 0)
        {
            return;
        }

        RayTracingShader.SetBuffer(0, "_Objects", _objectBuffer);
        RayTracingShader.SetInt("_ObjectsCount", _objectBuffer.count);

        // Make sure we have a current render target
        InitRenderTexture();

        // Set the target and dispatch the compute shader
        RayTracingShader.SetTexture(0, "Result", _target);
        int threadGroupsX = Mathf.CeilToInt(Screen.width / 8.0f);
        int threadGroupsY = Mathf.CeilToInt(Screen.height / 8.0f);
        RayTracingShader.Dispatch(0, threadGroupsX, threadGroupsY, 1);
        // Blit the result texture to the screen
        Graphics.Blit(_target, destination);

        _renderCount++;
    }

    private void InitRenderTexture()
    {
        if (_target == null || _target.width != Screen.width || _target.height != Screen.height)
        {
            // Release render texture if we already have one
            if (_target != null)
                _target.Release();
            // Get a render target for Ray Tracing
            _target = new RenderTexture(Screen.width, Screen.height, 0,
                RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
            _target.enableRandomWrite = true;
            _target.Create();
        }
    }

    private void OnEnable()
    {
        List<SomeObject> objects = new List<SomeObject>();
        // Add a number of random objects
        for (int i = 0; i < 100; i++)
        {
            SomeObject o = new SomeObject();
            o.value = 1;
            objects.Add(o);
        }
        // Assign to compute buffer
        _objectBuffer = new ComputeBuffer(objects.Count, 4);
        _objectBuffer.SetData(objects);
    }

    private void OnDisable()
    {
        if (_objectBuffer != null)
            _objectBuffer.Release();
    }
}

Seems possible to work around this by “warming up” the shader

private void OnEnable()
    {
        ...

        RayTracingShader.Dispatch(0, 1, 1, 1);
    }

This is probably not a fix and just masking an underlying timing issue