Search Unity

Sampling more than 16 textures in a computeshader

Discussion in 'Shaders' started by Chuptys, Feb 26, 2018.

  1. Chuptys

    Chuptys

    Joined:
    Mar 19, 2014
    Posts:
    18
    Hi! I'm trying to blend a lot of textures together using a compute shader, but it fails when the number of sampled textures goes beyond 16.

    I'm guessing it has something to do with the maximum allowed sampler amount (the docs say up to 128 textures and 16 samplers are supported).
    However, I'm reusing the same sampler over and over again.

    Here's some quick test code I wrote (it just combines multiple black/white masks):

    Code (CSharp):
    1. #pragma kernel TexCountTest
    2.  
    3. Texture2D<float4>
    4. Tex01, Tex02, Tex03, Tex04, Tex05,
    5. Tex06, Tex07, Tex08, Tex09, Tex10,
    6. Tex11, Tex12, Tex13, Tex14, Tex15,
    7. Tex16, Tex17, Tex18, Tex19, Tex20;
    8.  
    9. RWTexture2D<float4> Output;
    10. SamplerState MyLinearClampSampler;
    11.  
    12. float4 ProcessTex(float4 base_value, Texture2D<float4> tex, float2 uv)
    13. {
    14.     float4 value = tex.SampleLevel(MyLinearClampSampler, uv, 0);
    15.     float max_value = max(base_value.r, value.r);
    16.     return float4(max_value, max_value, max_value, 1);
    17. }
    18.  
    19. [numthreads(8,8,1)]
    20. void TexCountTest (uint3 id : SV_DispatchThreadID)
    21. {
    22.     float w,h;
    23.     Output.GetDimensions(w, h);
    24.     float2 uv = float2(id.x/w, id.y/h);
    25.    
    26.     float4 value = float4(0,0,0,0);
    27.  
    28.     value = ProcessTex(value, Tex01, uv);
    29.     value = ProcessTex(value, Tex02, uv);
    30.     value = ProcessTex(value, Tex03, uv);
    31.     value = ProcessTex(value, Tex04, uv);
    32.     value = ProcessTex(value, Tex05, uv);
    33.     value = ProcessTex(value, Tex06, uv);
    34.     value = ProcessTex(value, Tex07, uv);
    35.     value = ProcessTex(value, Tex08, uv);
    36.     value = ProcessTex(value, Tex09, uv);
    37.     value = ProcessTex(value, Tex10, uv);
    38.     value = ProcessTex(value, Tex11, uv);
    39.     value = ProcessTex(value, Tex12, uv);
    40.     value = ProcessTex(value, Tex13, uv);
    41.     value = ProcessTex(value, Tex14, uv);
    42.     value = ProcessTex(value, Tex15, uv);
    43.     value = ProcessTex(value, Tex16, uv);
    44.     value = ProcessTex(value, Tex17, uv);
    45.     value = ProcessTex(value, Tex18, uv);
    46.     value = ProcessTex(value, Tex19, uv);
    47.     value = ProcessTex(value, Tex20, uv);
    48.  
    49.     Output[id.xy] = value;
    50. }
    It works just as long as only 16 textures are sampled (so without lines 44-47). The moment it is increased to 17 textures, I'm getting quite random results, depending on the shader (in this instance, only the first 2 textures get blended).

    Any thoughts on how to solve this?
     
    MarkusGod likes this.
  2. scrawk

    scrawk

    Joined:
    Nov 22, 2012
    Posts:
    804
    Im not sure why your hitting the sampler limit but if you cant find a solution you can use a texture array instead of many textures. Its would also be easier to manage.

    Disclaimer - I have not tested this and have not uses texture arrays in compute shaders so you may run into a issue but it should work and does compile.

    Either that or use multiple dispatch passes ( 1-4 textures at a time ) which accumulate into the output tex.

    Code (CSharp):
    1. #pragma kernel TexCountTest
    2.  
    3. Texture2DArray<float4> Tex;
    4.  
    5. RWTexture2D<float4> Output;
    6. SamplerState MyLinearClampSampler;
    7.  
    8. float4 ProcessTex(float4 base_value, Texture2DArray<float4> tex, float2 uv, int i)
    9. {
    10.     float4 value = tex.SampleLevel(MyLinearClampSampler, float3(uv, i), 0);
    11.     float max_value = max(base_value.r, value.r);
    12.     return float4(max_value, max_value, max_value, 1);
    13. }
    14.  
    15. [numthreads(8, 8, 1)]
    16. void TexCountTest(uint3 id : SV_DispatchThreadID)
    17. {
    18.     float w, h;
    19.     Output.GetDimensions(w, h);
    20.     float2 uv = float2(id.x / w, id.y / h);
    21.  
    22.     float4 value = float4(0, 0, 0, 0);
    23.  
    24.     [unroll]
    25.     for(int i = 0; i < 19; i++)
    26.         value = ProcessTex(value, Tex, uv, i);
    27.  
    28.  
    29.     Output[id.xy] = value;
    30. }
     
    Last edited: Feb 27, 2018
  3. Chuptys

    Chuptys

    Joined:
    Mar 19, 2014
    Posts:
    18
    Thanks for your suggestions!

    Copying the textures in a texture array and using that array in the computeshader definately works, but the overhead of putting the textures in the texture array is larger than the actual blending operation :(

    The multiple passes is exactly what I'm trying to avoid, but as long as I'm not getting more than 16 texture reads in the compute shader there's not much else to do than batching them...
     
  4. Chuptys

    Chuptys

    Joined:
    Mar 19, 2014
    Posts:
    18
    Also, when working with 'normal' shaders there's a macro you can use for declaring textures without sampler (
    UNITY_DECLARE_TEX2D_NOSAMPLER), but this doesn't seem to compile in compute shaders?
     
  5. Chuptys

    Chuptys

    Joined:
    Mar 19, 2014
    Posts:
    18
  6. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    UNITY_DECLARE_TEX2D_NOSAMPLER simply declares a Texture2D on platforms that support separation of sampler from texture, so it wouldn't be much help there either as you're already declaring Texture2Ds.

    I notice you're using 2017.3.0f3 in your bug report, have you tried updating to latest .3 to see if it was already fixed? Current release is 2017.3.1p2.
     
  7. Chuptys

    Chuptys

    Joined:
    Mar 19, 2014
    Posts:
    18
    I just tested it in 2017.3.1p3, the bug still persists ;)