3
\$\begingroup\$

Ok guys I have had some pretty good success on this site but I feel that this is a pretty complex question.

I am trying to do tile based deferred lighting using DirectX 11 and the compute shader. Basically I am splitting up the screen into tiles and figuring out which lights hit the pixels in that tile. Then once I get to the pixel shader I only calculate the lighting for the current pixel based on what lights hit that pixel. (I'm pretty sure that I can find the lights that hit the pixel and the lighting calculations for that pixel solely in the compute shader but for now I am not doing that). So currently I think there is two ways that I can get the information to the compute shader. The tile data needs to come in a RWStructuredBuffer so that I can write to it and then use it during other parts of the GPU pipeline in a ShaderResourceView. The thing that I am not sure how to do is what is the best way to get all of the light data to the compute shader? I think I could do it in a constant buffer or a groupshared buffer. I believe the latter of the two is faster but I am not sure how it works and I cannot find much information on it. And if I was to use a constant buffer I would need to house an array of lights in that buffer and I am not sure how I would do that either because I do not want to hard code an array size in the shader.

Here is some code samples to show you what I am doing.

struct ComputeIn
{
    uint3 DTid  : SV_DispatchThreadID;
    int index   : SV_GroupIndex;
};

struct TileData
{
    float4 position;
    float3 normal;
    float2 texCoord;
    vector<float, 8> something;
};

struct PointLightData
{
    float4 color;
    float3 pos;
    float radius;
};

//If I use a cbuffer for the light data
cbuffer LightData
{
    //How do I make an array of lights in a cbuffer
    Array of lights here!
};

RWStructuredBuffer<TileData> tileData : register(u0);

//If I use a groupshared
groupshared PointLightData pointLights : register(u1);

[numthreads(1, 1, 1)]
void CS( ComputeIn input )
{
    //How do I iterate through the groupshared buffer?
    //groupshared[?];
    //

}

One more thing. I may be incorrect in assuming this but I assume that memory on the GPU is similar to memory outside of the GPU. If I have a buffer that is passes to the GPU and I know how my data is lined up inside of this buffer, I should be able to traverse through this memory to get to what data I need. My question is how do you traverse memory on the GPU. If I have a this.

struct PointLightData
{
    float4 color;
    float3 pos;
    float radius;
};

StructuredBuffer<PointLightData> pointLights : register(u0);

[numthreads(1, 1, 1)]
void CS( ComputeIn input )
{
    //FIGURE OUT WHAT LIGHTS HIT THIS TILE!

}

I assume that pointLights is a "pointer" to the beginning of this memory. How would I go about traversing all of the memory inside of this buffer? Suppose I know the total size of the memory and will not go out of bounds?

If anyone can shed some light on any of these topics I would greatly appreciate it!

Thanks!

\$\endgroup\$
4
\$\begingroup\$

A structured buffer is probably the best way to get the light data into the compute shader. As you pointed out, there are some disadvantages to using a constant buffer: you would have to specify a fixed array size, and constant buffers also have worse packing (things tend to have to be 16-byte-aligned with them), while structured buffers don't have these drawbacks.

Groupshared memory would not be appropriate for getting data into the shader. It is for threads within the threadgroup to share data amongst each other. Groupshared memory cannot be initialized or set from outside the shader. It does not have a register (u0 etc.) since it is not used to communicate outside the shader.

You can define a structured buffer pretty much like you showed:

struct PointLightData
{
    float4 color;
    float3 pos;
    float radius;
};

StructuredBuffer<PointLightData> pointLights : register(t0);

However, note that for a read-only buffer or texture, it goes in a texture register (t0), not a UAV one. This holds true on the CPU side as well; you'll create an SRV for the buffer and set it to an SRV slot, rather than a UAV slot. You can make this a dynamic buffer (USAGE_DYNAMIC) and use Map to update it each frame with the current set of lights, much like you would do a dynamic vertex buffer.

In the shader, the structured buffer just behaves like an array. You can access elements of it by subscripting:

for (int i = 0; i < numLights; ++i)
{
    PointLightData curLight = pointLights[i];
    // do stuff with this light...
}

Finally, you probably actually don't want the tile data to be in an RWStructuredBuffer UAV. A UAV would be useful if you wanted to store some data generated by this compute shader and then use it later, in a different dispatch or draw call. However, tile-based deferred lighting is typically all done in one dispatch. This would be the place to use groupshared memory. Each threadgroup corresponds to one tile, so you would use numthreads(16, 16, 1) for 16x16 tiles (or whatever size you choose). Each threadgroup generates a list of lights into its own groupshared memory, then applies those lights to each pixel in the tile. This way the per-tile light lists never need to go off the chip to global memory; they just stay in the fast local memory until they're used.

\$\endgroup\$
  • \$\begingroup\$ Thanks a lot for this answer Nathan. It will help me out a lot. I just have one question. If I am going to use the groupshared memory on the compute shader to build the tiles and apply the lighting to each pixel, how do i get this tile information to the pixel shader without a separate draw call? The original dispatch will run the compute shader but then I'm not sure how to get this tile information to the pixel shader when I draw my screen space quad with all of the gbuffer information. Thanks again for all your help! \$\endgroup\$ – Mike Sawayda Oct 13 '13 at 17:30
  • 1
    \$\begingroup\$ @MikeSawayda Tiled deferred lighting does both the culling and the lighting in one pass, in the same shader. So you won't have a dispatch to generate tile info and then a full-screen quad to consume it - you'll have just the one dispatch that first does the light culling, then reads the G-buffer, does the lighting, and writes out the lit pixel values via a UAV - all in one big compute shader. \$\endgroup\$ – Nathan Reed Oct 13 '13 at 18:03
  • \$\begingroup\$ Thank you again for your help! It makes more sense now. \$\endgroup\$ – Mike Sawayda Oct 15 '13 at 12:58
  • \$\begingroup\$ One more thing. Is there a way to create a UAV using the back buffer texture? If so how do I do that? \$\endgroup\$ – Mike Sawayda Oct 24 '13 at 21:24
  • \$\begingroup\$ @MikeSawayda Well, you could try getting the back buffer's ID3D11Texture2D interface and creating the UAV normally from that. But I suspect it won't work, as the back buffer created by the system probably doesn't have the BIND_UNORDERED_ACCESS flag. \$\endgroup\$ – Nathan Reed Oct 24 '13 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.