Search Unity

How can we access thread group and global variables from threads in compute shader?

Discussion in 'Shaders' started by mahdiii, Apr 27, 2017.

  1. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I want to access global variables from each thread.Also I would like to define variables shared in thread group only.
    How can I implement that?
     
  2. LukasCh

    LukasCh

    Unity Technologies

    Joined:
    Mar 9, 2015
    Posts:
    102
    Accessing the global variables is pretty straight forward. So this info can be found in our manuals https://docs.unity3d.com/Manual/ComputeShaders.html.

    You are looking for groupshared parameter for variables. (https://msdn.microsoft.com/en-us/library/windows/desktop/bb509706(v=vs.85).aspx)

    So this parameter basically specifies that variable will only be used by threads in group. However I really recommend doing more research about how compute thread groups works before using.

    There is lots of dangers with them and I give you one of the examples here:

    C#
    Code (csharp):
    1. private const int groupSize = 256;
    2.  
    3.     public ComputeShader compute;
    4.     private ComputeBuffer buffer;
    5.  
    6.     private void Start()
    7.     {
    8.         buffer = new ComputeBuffer(groupSize, sizeof(int));
    9.         compute.SetBuffer(0, "_Results", buffer);
    10.         compute.Dispatch(0, 1, 1, 1);
    11.  
    12.         var results = new int[groupSize];
    13.         buffer.GetData(results);
    14.         buffer.Release();
    15.  
    16.         var message = "";
    17.         foreach (var result in results)
    18.         {
    19.             message = message + result.ToString() + " ";
    20.         }
    21.         Debug.Log(message);
    22.     }
    Compute
    Code (csharp):
    1.  
    2. #pragma kernel main
    3.  
    4. #define GROUP_SIZE_X 256
    5.  
    6. RWStructuredBuffer<uint> _Results;
    7.  
    8. groupshared uint data[GROUP_SIZE_X];
    9.  
    10. [numthreads(GROUP_SIZE_X,1,1)]
    11. void main(uint groupIndex : SV_GroupIndex)
    12. {
    13.     data[groupIndex] = groupIndex;
    14.  
    15.     // Firstly you need to know that threads in group execute same instruction one by one.
    16.     // However every GPU has the maximum number of threads in the group and
    17.     // If this number is exceeded in compute shader it gets split into multiple waves.
    18.     // So this is where we do sync if this case happens, if not compiler will remove it.
    19.     // https://msdn.microsoft.com/en-us/library/windows/desktop/ff471404(v=vs.85).aspx
    20.     GroupMemoryBarrierWithGroupSync();
    21.  
    22.     // Here we force the race condition
    23.     _Results[groupIndex] = data[GROUP_SIZE_X - 1 - groupIndex];
    24. }
     
    mahdiii likes this.
  3. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    Thank you. so we need groupshared variable for group shared memory that threads in one group can access (read and write) it. please explain shared variables too
    global variables like below can be accessed(read and written) from all threads? but when all threads were executed, the values of global variables set to initial values? how can we keep the values (static?)
    Code (CSharp):
    1. #pragma kernel main
    2. #define GROUP_SIZE_X 256
    3. RWStructuredBuffer<uint> _Results;
    4. uint data[GROUP_SIZE_X];// global variables
    5. [numthreads(GROUP_SIZE_X,1,1)]
    6. void main(uint groupIndex : SV_GroupIndex)
    7. {
    8. }
     
  4. npatch

    npatch

    Joined:
    Jun 26, 2015
    Posts:
    247
    Can you elaborate on this example a bit? I'm in a similar predicament but I can't understand why this is an issue. Supposedly the barrier makes all threads execute up until all data[groupIndex] assignments have completed,even if you mess with the indexing for data[] access for the Result, there shouldn't be any write going on at this point, only reads.

    How does the race condition occur?
     
  5. LukasCh

    LukasCh

    Unity Technologies

    Joined:
    Mar 9, 2015
    Posts:
    102
    Usually in unity non buffer values are constant, so they should act as read only (But I not 100% as it might differ per graphics api). I recommend experimenting with variable specifiers https://msdn.microsoft.com/en-us/library/windows/desktop/bb509706(v=vs.85).aspx.

    I might a bit confused you with my example. Race-condition will only happen if there is no barrier.

    I will try to explain with simple scenario with no barrier:
    - Image that our maximum threads count in group is 1 (Well why not? :O).
    - However in shader we specify GROUP_SIZE_X as 2.
    - It means we going to have GROUP_SIZE_X/maximum_threads_count_in_group=2 wavefronts (https://blogs.msdn.microsoft.com/nativeconcurrency/2012/03/26/warp-or-wavefront-of-gpu-threads/).
    - So you can image these wavefronts like threads separately working on your shader, the first one we will call A and the second one B.
    - B executes: data[groupIndex] = groupIndex; // data[1] = 1
    - B executes: _Results[groupIndex] = data[GROUP_SIZE_X - 1 - groupIndex]; //_Results[1] = data[2 - 1 - 1]; as A have not executed anything so far _Results[1] = data[0] = trash value
    - RACE CONDITION

    And now with barrier:
    ...
    - B executes: data[groupIndex] = groupIndex; // data[1] = 1
    - B executes: GroupMemoryBarrierWithGroupSync(); // It forces B wavefront to wait for A
    - A executes: data[groupIndex] = groupIndex; // data[0] = 0
    - A executes: GroupMemoryBarrierWithGroupSync();
    - B wakes up as all groups reached barrier
    - B executes: _Results[groupIndex] = data[GROUP_SIZE_X - 1 - groupIndex]; //_Results[1] = data[2 - 1 - 1]; this time _Results[1] = data[0] = 0