Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice
  2. Ever participated in one our Game Jams? Want pointers on your project? Our Evangelists will be available on Friday to give feedback. Come share your games with us!
    Dismiss Notice

Resolved How to store and read data strictly in GPU

Discussion in 'Shaders' started by carcasanchez, Jun 30, 2020 at 4:28 PM.

  1. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    86
    Hello there:
    I have a very simple problem since months ago, but the solution has been difficult to catch.
    I am working on a shader, and I need to store two variables (a float3 and float4) to check them in the next frame.
    Since it is a shader that will run in a hell lot of instances, I am trying to keep it optimal. So I don't want to use anything that go back and forth from the CPU to the GPU.
    Just what I want: do calculations in shader > store in GPU > check the data in the next frame.

    I have not managed to do that, in part due to lack of documentation. I think this is possible to do, because I have seen post-processing efects that use previous frame data, and seems like a common problem that someone should have already solved.

    I have heard of RWBuffers, Compute Shaders and Render Textures, but every single example I have found retrieves the information back to CPU, which is something I want to avoid.
    I theory, all ways are valid for me (the RW buffer seems like the most straightforward), but I have not managed to find and example of how I can do all the functionality in shader code (exceptuating things like initializing the buffer)
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,798
    They did something like this for the original Adam demo.
    https://github.com/Unity-Technologi...arch?q=_ShadowParams&unscoped_q=_ShadowParams
    The first shader there sets the values on the
    RWStructureBuffer
    , but the buffer still needs to be created and assigned as a random write target from the CPU. The number in the
    register(u2)
    relates to the index used by the
    Graphics.SetRandomWriteTarget(2, m_ShadowParamsCB);
    in the c# code used to set the buffer as a random write target. For other non-compute shaders, as long as that SetRandomWriteTarget is still assigned (loading a level or alt tab may clear it, but there's no real cost to just calling that every update), you can read from the buffer by adding the same
    RWStructureBuffer
    line into the shader. Alternatively you can also assign the buffer as a global property with
    Shader.SetGlobalBuffer("_BufferName", buffer);
    if you don't need other shaders to be able to read from it. Compute shaders need to have the buffer assigned directly onto the compute shader, which is what the example c# in the link above does.
     
  3. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    86
    Thanks for the literature.
    So, if I understood well, the first shader (CopyShadowParams) just adds the data to a RWBuffer that is linked to a register (u2).
    Then, the CPU needs to do a Graphics.SetRandomWriteTarget to maintain the data from frame to frame, and then the Compute shader can read it again. Did I say it correctly?

    If this is true, could I just write the data within the shader, set the Target after running it, and then read that data whithin the same shader in the next frame? Or it is mandatory to separate into different shaders?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,798
    The order of operations here is:
    1. CPU side c# script tells GPU it needs to create a compute buffer of a specific size, and to bind it to a read/write register. Bind same buffer to global property as a read only buffer.
    2. GPU side shader can then write to said register with a set of data that's equal in size to the buffer that's been bound.
    3. GPU side shader can read from register, or structured buffer.
    Unless you call GetData() on the buffer, it's only ever on the GPU. But you do have to tell the GPU to create & assign the buffer first.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,798
    For further clarification, you can write to and read from the same buffer in the same shader, and data will (most of the time) stay from whatever was written for the previous frame. But understand, if you write to the buffer, it's being written to by every vertex or pixel of that mesh being drawn with it, depending on if you're writing to it from the vertex or fragment shader. It might make sense to read from the buffer first to get the previous data, then overwrite it with the updated data. But if you have a quad with 4 vertices, the previous data will be the previous vertex's output, not necessarily the previous frame's. If you're doing it in the pixel shader, it'll be the previous pixel's data ... and the order pixels draw in for a single object isn't guaranteed, so it might be any random pixel the object is visible on. I'm not even sure the vertex execution order is entirely guaranteed for larger meshes.
     
  6. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    86
    Thank you for your detailed response. Everything works perfect and in with very straightforward code.
    It's a pity this is not clearly documented anywhere, we were stuck for months on it and, at the end of the day, it is relatively easy to do.

    EDIT: I have encountered a little issue. Since all shaders write to the same register, if I have two or more c# spawners in scene, they will overwrite the same register, won't they?
    It is possible to make the RWBuffer a no-register one, without losing functionallity, or my only way is to arrange the system in C# to avoid overwritting?
     
    Last edited: Jul 2, 2020 at 3:09 PM
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    8,798
    Yes.

    Nope. That register is the only way a vertex or pixel shader can write out, other than the color they output to the current render target(s).

    A work around is instead of creating a buffer with a single struct, you create a buffer with an array of those structs. The array will need to be however many of these objects you expect to have running at any time. Then on the script side you'll need to assign each object it's own unique index with a material property. When you retire an object, that needs to be tracked so that index can be reused at some later date. Depending on what your use case is, you may need to have a shader that can initialize / clear the value when reusing an index to prevent pops.
     
  8. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    86
    Ok, thank you again for the insight.
     
unityunity