Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Resolved Atomic Safety Handle on native array crashing Unity

Discussion in 'Editor & General Support' started by ripefruits22, Mar 23, 2022.

  1. ripefruits22

    ripefruits22

    Joined:
    Nov 26, 2021
    Posts:
    17
    I've been trying to get the data from an array out of a compute shader by getting the data using a compute buffer, and then using converting the data into a native array to then convert to a float array, except I've been getting a null reference exception. When I googled it I found a thread and they said to fix the problem I had to create an atomic safety handle for the native array, so I did, but now Unity just crashes as soon as I press play.
    Here's the code, the problem method is at the bottom:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Diagnostics;
    4. using UnityEngine;
    5. using Unity.Collections.LowLevel.Unsafe;
    6. using Unity.Collections;
    7.  
    8. public class RandomWalkMapGenerator : MonoBehaviour
    9. {
    10.     [Header("Generation Settings")]
    11.     [SerializeField][Range(1, 255)] private int chunkSize;
    12.     [SerializeField] private int numChunks;
    13.     [SerializeField] private ComputeShader walkerCompute;
    14.  
    15.     [Header("Walker Settings")]
    16.     [SerializeField] private float stepIncrement;
    17.     [SerializeField] private int stepSize;
    18.     [SerializeField] private int stepSizeVariance;
    19.  
    20.     [Header("Random Distribution Settings")]
    21.     [SerializeField] private int numSteps;
    22.     [SerializeField] private int numWalkers;
    23.  
    24.     [Header("Fill Settings")]
    25.     [SerializeField] private int areaSize;
    26.     [SerializeField] private int fillSize;
    27.     [SerializeField][Range(0, 1)] private float fillChance;
    28.  
    29.     [Header("Mesh Settings")]
    30.     public float meshHeightMultiplier;
    31.     [SerializeField] private Material rendererMat;
    32.     [SerializeField] private MapDisplay display;
    33.     public AnimationCurve curve;
    34.     [SerializeField] private bool generateMesh;
    35.     [SerializeField] private float scale;
    36.  
    37.  
    38.     private float[] computeMap;
    39.     private float[,] map;
    40.     private float maxVal = 0f;
    41.  
    42.     private Stopwatch timer;
    43.  
    44.     private MeshCollider[] meshColliders;
    45.  
    46.     private ComputeBuffer cB;
    47.  
    48.     private void Start()
    49.     {
    50.         cB = new ComputeBuffer(numChunks * chunkSize * numChunks * chunkSize * 4, sizeof(float));
    51.  
    52.         timer = new Stopwatch();
    53.         if (fillSize < 0)
    54.         {
    55.             fillSize = 1;
    56.         }
    57.         else if (fillSize % 2 == 0)
    58.         {
    59.             fillSize++;
    60.         }
    61.         GenerateMap();
    62.     }
    63.  
    64.     public float[,] GenerateMap()
    65.     {
    66.  
    67.         SetComputeValues(stepIncrement, fillChance, numSteps, fillSize, stepSize, stepSizeVariance, numChunks * chunkSize);
    68.  
    69.         maxVal = 0;
    70.  
    71.         //Create the height maps and set the compute buffer
    72.         computeMap = new float[numChunks * chunkSize * numChunks * chunkSize];
    73.         map = new float[numChunks * chunkSize, numChunks * chunkSize];
    74.         cB.SetData(computeMap);
    75.         walkerCompute.SetBuffer(0, "mapBuffer", cB);
    76.  
    77.  
    78.         timer.Start();
    79.         for (int i = 0; i < numWalkers; i++)
    80.         {
    81.             Vector2Int randomStartPos = GetRandomStartPos(numChunks * chunkSize, numChunks * chunkSize, areaSize);
    82.             walkerCompute.SetVector("pos", new Vector4(randomStartPos.x, randomStartPos.y, 0, 0));
    83.             walkerCompute.Dispatch(0, 1, 1, 1);
    84.         }
    85.         cB.GetData(computeMap);
    86.         map = ArrayPacker.Pack1DArray(RetrieveData(cB), numChunks * chunkSize, numChunks * chunkSize);
    87.         maxVal = GetMaxValue(map);
    88.         timer.Stop();
    89.  
    90.         //turn the map into a height map since the range of the original map is not guaranteed to be 0 to 1
    91.         map = HeightmapGenerator.GenerateHeightmap(map, maxVal);
    92.  
    93.         GenerateTexandMesh();
    94.  
    95.         UnityEngine.Debug.Log(timer.ElapsedMilliseconds);
    96.         timer.Reset();
    97.  
    98.         return map;
    99.     }
    100.  
    101.     private void SetComputeValues(float stepIncrement, float fillChance, int numSteps, int fillSize, int stepSize, int stepSizeVariance, int mapSize)
    102.     {
    103.  
    104.         walkerCompute.SetFloat("stepIncrement", stepIncrement);
    105.         walkerCompute.SetFloat("fillChance", fillChance);
    106.         walkerCompute.SetInt("maxSteps", numSteps);
    107.         walkerCompute.SetInt("fillSize", fillSize);
    108.         walkerCompute.SetInt("stepSize", stepSize);
    109.         walkerCompute.SetInt("stepSizeVariance", stepSizeVariance);
    110.         walkerCompute.SetInt("mapSize", mapSize);
    111.  
    112.     }
    113.  
    114.     public void GenerateTexandMesh()
    115.     {
    116.  
    117.         rendererMat.mainTexture = TextureGenerator.TextureFromHeightMap(map);
    118.  
    119.         if (generateMesh)
    120.         {
    121.             meshColliders = GetComponentsInChildren<MeshCollider>();
    122.             if (meshColliders.Length != 0)
    123.             {
    124.                 for (int i = 0; i < meshColliders.Length; i++)
    125.                 {
    126.                     Destroy(meshColliders[i].gameObject);
    127.                 }
    128.             }
    129.  
    130.             //transform.position = new Vector3(-numChunks * chunkSize / 2, 0, -numChunks * chunkSize / 2);
    131.  
    132.             //generating the mesh for the plane.
    133.             GetComponent<ChunkGenerator>().GenerateChunks(numChunks, chunkSize, map);
    134.  
    135.             gameObject.transform.localScale = new Vector3(scale, scale, scale);
    136.         }
    137.     }
    138.  
    139.     public float[,] StackMaps(float[,] map1, float[,] map2, int offsetX, int offsetY)
    140.     {
    141.         float[,] stackedMap = map1;
    142.  
    143.         for (int y = 0; y < map2.GetLength(1); y++)
    144.         {
    145.             for (int x = 0; x < map2.GetLength(0); x++)
    146.             {
    147.                 stackedMap[x + offsetX, y + offsetY] += map2[x, y];
    148.             }
    149.         }
    150.  
    151.         return stackedMap;
    152.     }
    153.  
    154.     private Vector2Int GetRandomStartPos(int width, int height, int distanceFrom)
    155.     {
    156.         Vector2Int midPos = new Vector2Int((int)(width / 2), (int)(height / 2));
    157.  
    158.         midPos.x += Random.Range(-distanceFrom, distanceFrom + 1);
    159.         midPos.y += Random.Range(-distanceFrom, distanceFrom + 1);
    160.  
    161.         return midPos;
    162.     }
    163.  
    164.     private float GetMaxValue(float[,] map)
    165.     {
    166.         float maxVal = 0;
    167.  
    168.         for (int y = 0; y < map.GetLength(1); y++)
    169.         {
    170.             for (int x = 0; x < map.GetLength(0); x++)
    171.             {
    172.                 maxVal = (map[x, y] > maxVal) ? map[x, y] : maxVal;
    173.             }
    174.         }
    175.  
    176.         return maxVal;
    177.     }
    178.  
    179.     private unsafe float[] RetrieveData(ComputeBuffer computeBuffer)
    180.     {
    181.         NativeArray<float> nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<float>(cB.GetNativeBufferPtr().ToPointer(), computeBuffer.count * computeBuffer.stride, Unity.Collections.Allocator.Persistent);
    182.         //NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create());
    183.  
    184.         float[] map = nativeArray.ToArray();
    185.  
    186.         return map;
    187.     }
    188. }
    189.  
     
  2. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    Your problem is that ComputeBuffer.GetNativeBufferPtr() does not return a pointer to the buffer data: it returns a pointer to the graphics API handle to the compute buffer, so this will likely crash Unity. The only way to get that data is to copy it by calling GetData(), which you are already doing in line 85 but not using for anything.
     
  3. ripefruits22

    ripefruits22

    Joined:
    Nov 26, 2021
    Posts:
    17
    I tried using the GetData() method before but for some reason it made every element of the array NaN, although I'm not sure if that's because I was doing something wrong with getting the data.
     
  4. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    It's possible your compute shader produced NaN.
     
  5. ripefruits22

    ripefruits22

    Joined:
    Nov 26, 2021
    Posts:
    17
    The compute shader shouldn't produce NaN as it basically just adds a value iteratively. Here's the code for the Compute shader if you want to check it out:
    Code (CSharp):
    1. // Each #kernel tells which function to compile; you can have many kernels
    2. #pragma kernel CSMain
    3.  
    4. RWStructuredBuffer<float> mapBuffer;
    5. int2 pos;
    6. float stepIncrement;
    7. float fillChance;
    8. int maxSteps;
    9. int fillSize;
    10. int stepSize;
    11. int stepSizeVariance;
    12. int mapSize;
    13.  
    14.  
    15. float hash(float2 p)  // a good enough PRNG generator
    16. {
    17.     p  = 50.0*frac( p*0.3183099 + float2(0.71,0.113));
    18.     return -1.0+2.0*frac( p.x*p.y*(p.x+p.y) );
    19. }
    20.  
    21. float noise( in float2 p )
    22. {
    23.     float2 i = floor( p );
    24.     float2 f = frac( p );
    25.  
    26.     float2 u = f*f*(3.0-2.0*f);
    27.  
    28.     return lerp( lerp( hash( i + float2(0.0,0.0) ),
    29.                      hash( i + float2(1.0,0.0) ), u.x),
    30.                 lerp( hash( i + float2(0.0,1.0) ),
    31.                      hash( i + float2(1.0,1.0) ), u.x), u.y);
    32. }
    33.  
    34. int2 RandomStep(int2 coord, int fillSize, int stepSize, int stepNum, int mapSize){
    35.     float prn = noise(float2(coord.x * stepNum, coord.y * stepNum));
    36.  
    37.     if(prn <= 1){ //cases for where to step for which number is generated and see if the step is in bounds
    38.         if(coord.x + (stepSize + fillSize) < mapSize){
    39.             return coord + float2(stepSize, 0);
    40.         }
    41.         if(coord.x + (stepSize + fillSize) > mapSize){
    42.             return coord + float2(-stepSize, 0);
    43.         }
    44.     } if(prn <=2 && prn > 1){
    45.         if(coord.y + (stepSize + fillSize)  < mapSize){
    46.             return coord + float2(0, stepSize);
    47.         }
    48.         if(coord.y + (stepSize + fillSize) > mapSize){
    49.             return coord + float2(0, -stepSize);
    50.         }
    51.     } if(prn <=3 && prn > 2){
    52.         if(coord.x - (stepSize + fillSize) > 0){
    53.             return coord + float2(-stepSize, 0);
    54.         }
    55.         if(coord.x - (stepSize + fillSize) < 0){
    56.             return coord + float2(stepSize, 0);
    57.         }
    58.     } if(prn <=4 && prn > 3){
    59.         if(coord.x - (stepSize + fillSize) > 0){
    60.             return coord + float2(0, -stepSize);
    61.         }
    62.         if(coord.x - (stepSize + fillSize) < 0){
    63.             return coord + float2(0, stepSize);
    64.         }
    65.     }
    66.  
    67.     return coord;
    68. }
    69.  
    70. void FillGrid(int2 coords, int fillSize, float stepIncrement, float fillChance, float mapSize){
    71.  
    72.  
    73.     int coordOffset = (fillSize - 1) / 2;
    74.  
    75.     for(int y = -coordOffset; y < coordOffset; y++){
    76.  
    77.         for(int x = -coordOffset; x < coordOffset; x++){
    78.             //fills in the 1D array in the way a 2D array would be filled
    79.             mapBuffer[coords.x + x + ((coords.y + y) * mapSize)] += stepIncrement;
    80.  
    81.         }
    82.  
    83.     }
    84. }
    85.  
    86.  
    87. [numthreads(64,1,1)]
    88. void CSMain (uint3 id : SV_DispatchThreadID)
    89. {
    90.    
    91.     for(int i = 0; i < maxSteps; i++){
    92.  
    93.         switch(fillSize){ //fill around the pixel
    94.  
    95.             case 1:
    96.                 mapBuffer[pos.x + (pos.y * mapSize)] += stepIncrement;
    97.                 break;
    98.             default:
    99.                 FillGrid(pos, fillSize, stepIncrement, fillChance, mapSize);
    100.                 break;
    101.         }
    102.  
    103.         int stepLength = stepSize + round(noise(float2(i * stepSize, i * stepSize)) * stepSizeVariance);
    104.         pos = RandomStep(pos, fillSize, stepLength, i, mapSize); //step in some direction
    105.  
    106.     }
    107.  
    108. }
    109.  
     
  6. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    I can't pinpoint exactly why it's outputting NaN, but at glance I can tell that youre not using compute shaders as they are intended to be used and need to learn more about how they work at a fundamental level.

    The first red flag is that you don't use the SV_DispatchThreadID id input anywhere, even though your kernel group size is 64 threads. When you dispatch a CS, you tell it how many groups will be executed, and each group launches the number of threads specified in groupsize. That's the entire thing compute shaders are good at: running hundreds+ threads in parallel, and you're not using any of that.

    You have 64 threads doing the exact same thing and stomping each other's work by writing to potentially the same locations in the buffer.

    Which brings up the 2nd issue: memory synchronization. Having multiple threads all incrementing random indices in the buffer will not work as expected because threads will try to fech data from cache as much as possible to avoid high latency reads from memory. This means the value written by one thread is very likely to not be seen by others immediately, unless you use interlocked operations or a globally coherent buffer plus memory synchronization.

    You need to rethink your algorithm from scratch for it to work as a compute shader, it's a different programming paradigm. Even what you're doing right now worked as is, it would probably be slower than running on the CPU using Burst because it's only using one GPU thread, and those aren't really very fast in isolation.
     
  7. ripefruits22

    ripefruits22

    Joined:
    Nov 26, 2021
    Posts:
    17
    Well in that case I might just go back to running the CPU code since that worked fine. at least until I figure out how to get that code to work properly for the GPU. Thanks for the help.