Search Unity

Compute shader sampling from one mip level while writing to another- manual mipmapping

Discussion in 'Shaders' started by SamOld, Feb 9, 2019.

  1. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
    I am trying to use a compute shader to manually create mipmaps for a 3D texture. I'm running into issues getting a sampler on mipmap level 0 and a RWTexture3D on mipmap level 1 from the same texture to play nicely together. I want this to work everywhere, but tests are being conducted on DX11 with Unity 2018.3.0f2.

    My textures are created like so.

    Code (CSharp):
    1. RenderTexture MakeVoxelTexture()
    2. {
    3.     var texture = new RenderTexture(this.size.x, this.size.z, 0, RenderTextureFormat.ARGB32);
    4.  
    5.     texture.dimension = TextureDimension.Tex3D;
    6.     texture.volumeDepth = this.size.y;
    7.     texture.filterMode = FilterMode.Trilinear;
    8.     texture.useMipMap = true;
    9.     texture.autoGenerateMips = false;
    10.     texture.enableRandomWrite = true;
    11.     texture.Create();
    12.  
    13.     return texture;
    14. }
    My minimum recreation compute shader is as follows.

    Code (CSharp):
    1. #pragma kernel FilterVolume
    2.  
    3. Texture3D<float4> Source;
    4. SamplerState samplerSource;
    5. RWTexture3D<float4> Dest;
    6.  
    7. [numthreads(8,8,8)]
    8. void FilterVolume (uint3 id : SV_DispatchThreadID)
    9. {
    10.     // Sample the center for testing
    11.     float4 val = Source.SampleLevel(samplerSource, float3(0.5, 0.5, 0.5), 0);
    12.     Dest[id] = float4(val.rgb, 1);
    13. }
    14.  
    I'm dispatching the computer shader with a command buffer like so. I have also tried the non-buffer equivalent.

    Code (CSharp):
    1.  
    2. var shader = Resources.Load<ComputeShader>("VoxelFilterCompute");
    3. var filterVolumeKernal = shader.FindKernel("FilterVolume");
    4. var buffer = new CommandBuffer();
    5. buffer.name = "Compute Voxel Mipmaps";
    6. // Here the source is bound to mip 0 and the destination to mip 1
    7. buffer.SetComputeTextureParam(shader, filterVolumeKernal, "Source", texture, 0);
    8. buffer.SetComputeTextureParam(shader, filterVolumeKernal, "Dest", texture, 1);
    9. buffer.DispatchCompute(shader, filterVolumeKernal, 4, 4, 1);
    10. Graphics.ExecuteCommandBuffer(buffer);
    11.  
    I expected to be able to sample from the 0th mip while writing to the 1st. However, the sampler appears to silently fail and return 0. Viewing the dispatch in RenderDoc indicates that the Source Texture3D is unbound.

    I have done the following sanity checks.

    If I have the compute shader output a solid colour, it works fine. Output does not appear to be the issue.

    If I have two separate identical textures, I can correctly generate the 1st mip of one from the 0th mip of the other. This issue only occurs when Source and Dest are from the same texture.

    If I switch the order of my SetComputeTextureParam calls around, the behaviour remains identical. I thought that setting Dest first may result in Source being bound and Dest not, but that is not the case.

    If I change the Texture3D to be another RWTexture3D, I can correctly read and write to different mips from the same texture. However, I of course lose the ability to use samplers for the reading.

    So to get to the question, should/can this work? How?

    I believe that this should be possible. Generating mipmaps with compute shaders is not unheard of and various resources talk about using samplers in that process. I have a feeling that I may have even done it before myself, outside of Unity. I'm posting this here as a sanity check, as I'm starting to suspect that this issue is internal to Unity and not with my code.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Why bind the texture twice? Just sample from the RWTexture and write back to it.
     
  3. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You can’t read from and write to the same texture object normally, apart from using a RWTexture. But you’ll need to do the “bilinear” (really trilinear for 3D textures) sampling yourself by loading all of the the pixel colors in each group and writing out the average. The other option is to use two textures instead, and then using CopyTexture to copy the results back to the original.
     
  5. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
    Oh well if that's true then I'm being a dummy, but I could have sworn that limitation was per subresource not per resource, which would make separate mipmap levels okay. You wouldn't happen to know where I can find documentation that explicitly defines that, would you?

    I know that I can work around it in both of those ways, I just thought it was possible to avoid the performance cost of those options.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    If you're writing for D3D11 directly I'm pretty sure you're right, it's just not something Unity allows because not all compute platforms allow it.
     
  7. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
    Ah, that would make sense, thanks.