Search Unity

Compute Shader results look like undefined behaviour

Discussion in 'General Graphics' started by ChaiKnight, Sep 27, 2019.

  1. ChaiKnight

    ChaiKnight

    Joined:
    Apr 11, 2013
    Posts:
    6
    Hello,

    I'm currently running a compute shader on the output of a replacement shader. Basically, my replacement shader yields a black and white image depending on which objects are in the view. The replacement shader is shown below, but it is indeed fairly simple:
    Code (CSharp):
    1. Shader "Hidden/Effect"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "black" {}
    6.         _OppTex ("Texture", 2D) ="white" {}
    7.     }
    8.     SubShader
    9.     {
    10.         Tags{"RenderType"="Opaque"}
    11.         Cull Back ZWrite On ZTest LEqual
    12.  
    13.         Pass
    14.         {
    15.            
    16.        
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2f
    30.             {
    31.                 float2 uv : TEXCOORD0;
    32.                 float4 vertex : SV_POSITION;
    33.             };
    34.  
    35.             v2f vert (appdata v)
    36.             {
    37.                 v2f o;
    38.                 o.vertex = UnityObjectToClipPos(v.vertex);
    39.                 o.uv = v.uv;
    40.                 return o;
    41.             }
    42.  
    43.             sampler2D _MainTex;
    44.  
    45.             float4 frag (v2f i) : SV_Target
    46.             {
    47.                 return float4(0,0,0,1);
    48.             }
    49.             ENDCG
    50.         }
    51.     }
    52.     SubShader
    53.     {
    54.         Tags {"RenderType"="OOI"}
    55.         Cull Back ZWrite On ZTest LEqual
    56.        
    57.         Pass {
    58.             CGPROGRAM
    59.             #pragma vertex vert
    60.             #pragma fragment frag
    61.  
    62.             #include "UnityCG.cginc"
    63.  
    64.             struct appdata
    65.             {
    66.                 float4 vertex : POSITION;
    67.                 float2 uv : TEXCOORD0;
    68.             };
    69.  
    70.             struct v2f
    71.             {
    72.                 float2 uv : TEXCOORD0;
    73.                 float4 vertex : SV_POSITION;
    74.             };
    75.  
    76.             v2f vert (appdata v)
    77.             {
    78.                 v2f o;
    79.                 o.vertex = UnityObjectToClipPos(v.vertex);
    80.                 o.uv = v.uv;
    81.                 return o;
    82.             }
    83.  
    84.             sampler2D _OppTex;
    85.  
    86.             float4 frag (v2f i) : SV_Target
    87.             {
    88.                 return float4(1,1,1,1);
    89.             }
    90.             ENDCG
    91.         }
    92.     }
    93. }
    After this, I run a compute shader to extract the white-pixel edges of an area determined by each object's Render components. The code for this is shown below:

    Code (CSharp):
    1. private List<ClassRect> GetBoundingBoxesInSceneNew([NotNull] IEnumerable<GameObject> objectsOfInterest,
    2.         int width, int height)
    3.     {
    4.         if (objectsOfInterest == null) throw new ArgumentNullException(nameof(objectsOfInterest));
    5.  
    6.         maskCam.Render();
    7.         Graphics.Blit(maskCam.targetTexture, copyTexture);
    8.  
    9.         ComputeBuffer buffer = new ComputeBuffer(4,16);
    10.         ComputeBuffer output = new ComputeBuffer(4,16);
    11.  
    12.         List<ClassRect> list = new List<ClassRect>();
    13.  
    14.         //COMPUTE TEST
    15.         foreach(var obj in objectsOfInterest)
    16.         {
    17.             if (!obj.activeSelf) continue;
    18.  
    19.             var rendB = GetRendererCameraBounds(obj.GetComponentsInChildren<Renderer>());
    20.  
    21.             int[] data = {Mathf.RoundToInt(rendB.xMin),
    22.                 Mathf.RoundToInt(rendB.yMin),
    23.                 Mathf.RoundToInt(rendB.xMax),
    24.                 Mathf.RoundToInt(rendB.yMax)};
    25.            
    26.             buffer.SetData(data);
    27.             output.SetData(new int[] { width + 1, height + 1, -1, -1});
    28.            
    29.             boundsCompute.SetBuffer(Kernel, "Bounds", buffer);
    30.             boundsCompute.SetBuffer(Kernel, "Output", output);
    31.        
    32.             boundsCompute.Dispatch(Kernel,
    33.                 Mathf.Max((data[2] - data[0]) / 8,1),
    34.                 Mathf.Max((data[3] - data[1]) / 8,1),
    35.                 1);
    36.  
    37.             int[] outArr = new int[4];
    38.             output.GetData(outArr);
    39.            
    40.             Rect r = new Rect(outArr[0], outArr[1],
    41.                 outArr[2] - outArr[0], outArr[3] - outArr[1]);
    42.  
    43.             //the output array starts like this, so if no pixels were found it will continue to be like that
    44.             if (r.xMin > r.xMax || r.yMin > r.yMax)
    45.             {
    46.                 //continue;
    47.                 Debug.Log(r);
    48.             }
    49.            
    50.             r = CutRect(r, width, height);
    51.  
    52.             list.Add(new ClassRect(obj.name.Replace("(Clone", ""),r));
    53.         }
    54.        
    55.         buffer.Dispose();
    56.         output.Dispose();
    57.  
    58.         return list;
    59.  
    60.     }
    As you can see, I feed it with the bounds of the object and attempt to extract the actual white pixel bounds. The compute shader is shown below, and this is where it gets really weird:

    Code (CSharp):
    1. // Each #kernel tells which function to compile; you can have many kernels
    2. #pragma kernel CSMain
    3.  
    4. // Create a RenderTexture with enableRandomWrite flag and set it
    5. // with cs.SetTexture
    6. RWTexture2D<float4> Input;
    7. RWStructuredBuffer<int> Bounds;
    8. RWStructuredBuffer<int> Output;
    9.  
    10. [numthreads(8,8,1)]
    11. void CSMain (uint2 id : SV_DispatchThreadID)
    12. {
    13.     uint i = (uint)Bounds[0];
    14.     uint j = (uint)Bounds[1];
    15.    
    16.     uint2 pos = id.xy;
    17.     pos.x += i;
    18.     pos.y += j;
    19.    
    20.     float4 color = Input[pos];
    21.    
    22.     if(color.g >= 0.5)
    23.     {
    24.         InterlockedMin(Output[0], (int)pos.x);
    25.         InterlockedMin(Output[1], (int)pos.y);
    26.         InterlockedMax(Output[2], (int)pos.x);
    27.         InterlockedMax(Output[3], (int)pos.y);
    28.         Input[pos] = float4(1,0.5,0.5,1);
    29.     }
    30. }
    This is a super simple atomic pixel bounds checker which takes a render texture and bounds and outputs the, I would assume, white pixel bounds (or rather, pixel bounds where the green channel is greater than .5). However, that's not what happens. Instead,
    color.g > X
    never ever evaluates to true. However, I can get it to evaluate to true if I instead write
    color.g <= -4294967295
    which colours all the black pixels light red (which was supposed to happen for white pixels to test the statement).
    color.g >= -4294967295
    never evaluates to true.

    As you can see, I set black and white pixels in the replacement shader, but the Compute Shader does not interpret them that way. My RenderTextures use 24 bit depth and I've tested nearly all possible color formats. I'm at my wit's end here and would love some input on what to test next.
     
  2. ChaiKnight

    ChaiKnight

    Joined:
    Apr 11, 2013
    Posts:
    6
    Any insight on this?

    I can sort of work around it by moving the relevant code to an else statement, but it requires that I assign a new colour to
    Input[pos]
    in the if block (I'm trying to squeeze out some performance here, I can't be doing this arbitrarily). This indicates to me that there is a severe issue with read-only textures and data types here, but I can't for the life of me figure out why.

    I looked at the compiled code with a teammate, and it strangely converts the Input texture to r32f, and assigns the lookup like so:
    Code (CSharp):
    1.    u_xlati0.x = int(Bounds_buf[0].value[(0 >> 2) + 0]);
    2.     u_xlati0.y = int(Bounds_buf[1].value[(0 >> 2) + 0]);
    3.     u_xlati1 = u_xlati0.xyyy + ivec4(gl_GlobalInvocationID.xyyy);
    4.     u_xlat0 = imageLoad(Input, u_xlati1.xw).x;
    5.  
     
    Last edited: Oct 2, 2019
  3. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Not sure if it will make a difference but the stride of these buffers appears to be 4, not 16.
     
  4. ChaiKnight

    ChaiKnight

    Joined:
    Apr 11, 2013
    Posts:
    6
    Ah right, that is a remnant from some tests. I used 4 at first.