Search Unity

How to write to an unordered access compute buffer from a pixel shader?

Discussion in 'Shaders' started by sebj, May 11, 2016.

  1. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    Hello,

    I am trying to write a pixel shader that makes arbitrary writes into a RWStructuredBuffer<> ComputeBuffer. Writing to these buffers from the pixel shader should be supported according to MSDN, and is hinted at by the Unity docs.

    However, I cannot make it work. There are no errors, the buffer that I pass to GetData() simply fails to be filled with anything. I have tried both Material.SetBuffer() and Graphics.SetRandomWriteTarget().

    My test is quite simple - I have a vertex shader that passes through position data, that is set in the script to render a triangle in NDC. In the fragment shader, a constant is written to a fixed index in the RWStructuredBuffer. I invoke the shader with Graphics.DrawProcedural(). I would expect after calling GetData() for this constant to be present, but it is not.

    RenderDoc shows that the pixel shader is making a store_structured call to the correct location in the buffer, with the correct value.

    An example project showing this is here: https://www.dropbox.com/s/g88506jl1qwe7m9/UAVWriteDemo.zip?dl=0
    (I tried to upload a ZIP and a RAR here but kept getting an error about the file being too large, even though its only 2MB)

    What else must I do to write to a RWStructuredBuffer<> from a pixel shader?

    (I have seen the examples in this thread, they have been very useful, but the UAV write example uses a pixel shader to copy from one texture to another, whereas I want to exercise the entire rasterisation pipeline, and to render into a compute buffer, rather than a render target.)

    Sj
     
  2. shadowndacorner

    shadowndacorner

    Joined:
    Mar 9, 2013
    Posts:
    16
    You're binding it to a register in the shader, right? Like so:
    Code (csharp):
    1. uniform RWStructuredBuffer<float> buffer : register(u1);
     
  3. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    Thank you shadowndacorner. I was not doing that. I have added the binding. It doesn't fix the issue unfortunately, however I now see the compute buffer bound to the slot in the resources view in RenderDoc, which I didn't see before.

    I can explore the buffer from the Output Merger pipeline view, and see that the value from my pixel shader is indeed being written.
     
    Last edited: May 11, 2016
  4. shadowndacorner

    shadowndacorner

    Joined:
    Mar 9, 2013
    Posts:
    16
    I tweaked your project a bit, works now.

    Obviously it's not perfect code - it by no means reflects best practices, other than lazy instantiation and using OnEnable/OnDisable to set things up and clean them up. Also Shader.Find vs Resources.Load. But let me know if you want me to explain any of the changes.

    Also, a key thing to note is that it (generally) binds UAVs starting at 1, not 0. More on that here.
     
  5. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    Thank you very much shadowndacorner.

    From your example I have determined that it was declaring the destination ComputeBuffer locally that was the cause of the odd behaviour. If I move that declaration into the class itself as in your code, it works. Interestingly, the ComputeBuffer holding the vertex data can still be declared locally with no obvious ill effects.

    I can't think why this would occur - its not being optimized away or destroyed early. (Pointer to something on the stack being passed to another thread behind the scenes, perhaps?)

    Not that it matters. My example was some quick and dirty attempt to understand out how it all worked, but seems it was a little too much so, I'll be more careful next time. Thanks again!
     
  6. Miggy

    Miggy

    Joined:
    Feb 9, 2010
    Posts:
    76
    I'm having trouble writing to a RWStructuredBuffer from a pixel shader too. The project you linked doesn't appear to be working any more either. I tested it using Unity 5.4.3. Does it work for you?
     
  7. KellyJelly

    KellyJelly

    Unity Technologies

    Joined:
    Sep 29, 2016
    Posts:
    12
    Sorry to bring up an old thread, but if there are still people struggling with this the reason this happens is because when you call Graphics.SetRandomWriteTarget() with the third argument set to default or false, it will bind the compute buffer to the Compute Shader stage thereby unbinding it from the OM (pixel shader) stage.

    you have two options for getting this to work:
    you can call Graphics.SetRandomWriteTarget(..., ..., true) which preserves the counter value (possibly not the behavior you want).
    or
    you can call Graphics.SetRandomWriteTarget(..., ..., false) which will bind the buffer to the Compute Shader pipeline and thereby unbind it from the pixel shader stage, and then re-bind it to the pixel shader stage by calling Graphics.SetRenderTarget() with something that is not the current render target. This is not ideal because maybe the current render target is the one you want, and then you'd have to call Graphics.SetRenderTarget() again to set it back to the currently set RT.

    Sorry that neither of these work-arounds are ideal, i'm currently investigating an engine side fix for this.
     
  8. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    Hi KellyJelly,

    Thanks for telling us more about this part of the API. Is there some bit of documentation we can go to to learn comprehensively about the random write targets and how they should be used in Unity?

    For example, I had no idea until now that Graphics.SetRenderTarget would affect the binding of random write targets.

    Sj
     
  9. KellyJelly

    KellyJelly

    Unity Technologies

    Joined:
    Sep 29, 2016
    Posts:
    12
    Unfortuantely no, there is no place to learn these things. I had to figure out what was going on by digging through the code, when this issue bit me while i was working on a personal project over the weekend.

    If you are interested in why setting render targets also binds the UAVs, then you can take a look at this function from the MSDN docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476465(v=vs.85).aspx
    and you will see that render targets and UAVs must be bound at the same time when using them in the pixel shader.

    i am working on a fix that will likely result in Graphics.SetRandomWriteTarget() taking a fourth parameter which specifies which stage you want the UAV to bound to - either compute shader or pixel shader.
     
    PrimalCoder and julienkay like this.
  10. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    I see, thanks. Can you tell from the code if multiple calls to SetRandomWriteTarget (with different buffers) should be supported?

    What I find is, if I create a script which creates a buffer, binds it and renders to it with DrawProcedural(), if I have two instances of that script the buffer for at least one will not be bound correctly.

    The first RenderDoc capture attached shows this. The first two calls (Draw(3)) are the DrawProcedural() calls. You can see in the callstack and the Render Targets & UAVs view that one buffer is bound during both calls. The second capture is with ClearRandomWriteTargets() called *after* the DrawProcedural() and you see in this case that no UAV is bound for either call.

    I raised a bug about this a while back (860349) but thought I'd look again given what you said above, but unfortunately trying the first workaround has no effect.
     

    Attached Files:

    jason-fisher likes this.
  11. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    I just spent about 3 hours on this. Wow. That's super unintuitive because it also happens for the SetRandomWriteTarget with the RenderTexture overload, forcing you to call SetRenderTarget after. Please fix or warn future users. Thanks for tracking this down, I almost gave up.
     
    jason-fisher likes this.
  12. Devilwhale

    Devilwhale

    Joined:
    Jul 29, 2015
    Posts:
    4
    So I got this demo working simply by adding a model and material with the UAVRender shader in the scene.
    This allows the shader to process data per frame, otherwise it never gets put on the gpu.
    Thanks this is good example of how to set and read buffers to a shader, I am doing something similar with compute shaders talking to pixel shaders etc.

    With compute shaders you have to disbatch them to get them run, in this case adding a active renderer to the scene.
    Did not test captures, just write.
    upload_2017-11-21_13-42-35.png
    upload_2017-11-21_13-39-29.png
     
  13. RubyLux

    RubyLux

    Joined:
    Apr 24, 2014
    Posts:
    5
    How are you supposed to bind a compute buffer that needs to be accessed by a compute shader, but has data written to the same buffer by a fragment shader? I am targeting only dx11.

    Ive had this happen by pure luck if the fragment shader doesnt write to a gbuffer so the (u1) binding doesnt get messed with by unity.
    If you write a custom fragment shader that needs to write to the gbuffer, then you cant explicitly use register(u1) because u0-u3 are reserved by the render target binding that unity needs to do for the MRT.

    Also been trying to write to multiple compute buffer with register(u4-u7) in compute+frag shader seems impossible to get working.
    DrawMeshInstancedIndirect seems to also fail to make the appropriate UAV bindings happen so you cant write to compute buffer inside a frag shader.
    It would save people weeks if not months of time if unity could provide a working example of this.

    I really need a method to explicitly force buffers to bind, because renderdoc captures show these bindings dont reliably happen. I've tried this using everything from unity 5.6 to 2017.3.b06
     
  14. jjxtra

    jjxtra

    Joined:
    Aug 30, 2013
    Posts:
    1,464
    Can we read a compute buffer from a fragment shader regardless of the stage?
     
  15. winning11123

    winning11123

    Joined:
    Apr 29, 2014
    Posts:
    72
    I know this is necromancy, but I ran into this issue today too. Almost gave up but seemed I had to use u2 and u3 with DrawMeshInstancedIndirect.

    for me in void Update():
    Code (CSharp):
    1. Graphics.ClearRandomWriteTargets();
    2.         meshRayMarch_PrepassCube_Material.SetBuffer("_sort_Data", cb_sort_Data);
    3.  
    4.         meshRayMarch_PrepassCube_Material.SetBuffer("_data_Extraction_Counter", cb_Data_Extraction_Counter);
    5.         meshRayMarch_PrepassCube_Material.SetBuffer("_data_Extraction", cb_Data_Extraction);
    6.  
    7.         Graphics.SetRandomWriteTarget(2, cb_Data_Extraction, true);
    8.         Graphics.SetRandomWriteTarget(3, cb_Data_Extraction_Counter, true);
    9.  
    10.         Graphics.DrawMeshInstancedIndirect(......);
    in DrawMeshInstancedIndirect frag shader:
    Code (CSharp):
    1. uniform RWStructuredBuffer<float> _data_Extraction : register(u2);
    2.             uniform RWStructuredBuffer<float> _data_Extraction_Counter : register(u3);
    then for me in OnPostRender I do my compute shader:
    Code (CSharp):
    1. void OnPostRender()
    2.     {
    3.         //--------------------------------------------------
    4.         Graphics.ClearRandomWriteTargets();
    5.         //execute compute shader
    6.         CS_CubePrePass_Render_Test_Execute();
    7.         //--------------------------------------------------
    8.     }
     
    scorpioservo and jolix like this.
  16. Blinkq

    Blinkq

    Joined:
    Apr 19, 2015
    Posts:
    21
    Hello. How to do that, if I store my history texture in RTHandle ? How to set persist couner value to True ?