Working with StructuredBuffer in HLSL (DirectX 11)

Started by
7 comments, last by adam7 3 years ago

Hi, I want to implement a StructuredBuffer in my shader so I can pass in an undefined number of lights. From my research I have decided that the best way to do this is to use a StructuredBuffer. However, I cannot find any good tutorials that document how to use it. Can anyone point me in the right direction? Currently, I am having trouble initializing the buffer in my C++ shader code; I am unsure about some of the parameters such as the StructureByteStride. Here is my current code which gives an error when calling CreateBuffer:

D3D11_BUFFER_DESC sbDesc;
		sbDesc.ByteWidth = sizeof(CLight);
		sbDesc.Usage = D3D11_USAGE_DYNAMIC;
		sbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		sbDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		sbDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
		sbDesc.StructureByteStride = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

		D3D11_SUBRESOURCE_DATA subResourceData2;
		subResourceData2.pSysMem = (void*)_lights.data();
		subResourceData2.SysMemPitch = 0;
		subResourceData2.SysMemSlicePitch = 0;

		result = device->CreateBuffer(&sbDesc, &subResourceData2, &m_lightBuffer2);
		if(FAILED(result)){
			cout << "error" << endl;
			return false;
		}

		D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
		srvDesc.Format = DXGI_FORMAT_UNKNOWN;
		srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
		srvDesc.Buffer.FirstElement = 0;
		srvDesc.Buffer.NumElements = _lights.size();

		result = device->CreateShaderResourceView(m_lightBuffer2.Get(), &srvDesc, &m_pSRV);
		if (FAILED(result)) {
			cout << "error" << endl;
			return false;
		}

And later to set the resource:

 D3D11_MAPPED_SUBRESOURCE mappedResource2;
 result = deviceContext->Map(m_lightBuffer2.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource2);
 if (FAILED(result))
 {
  cout << "error" << endl;
  return false;
 }

 size_t sizeInBytes = _lights.size();
 memcpy_s(mappedResource2.pData, sizeInBytes, _lights.data(), sizeInBytes);

 deviceContext->Unmap(m_lightBuffer2.Get(), 0);

 deviceContext->PSSetShaderResources(7, 1, &m_pSRV);

I am using ComPtr's for the buffer and the shader resource view:

Microsoft::WRL::ComPtr<ID3D11Buffer> m_lightBuffer2;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_pSRV;

Advertisement

StructureByteStride should be the size of each individual element in your buffer. In other words, it's the size of the struct that you'll use to declare the structured buffer in your HLSL code. It looks like “_lights” is a std::vector, so you'll probably want to create it with the sizeof(T), where “T” is the type used for your std::vector. The code for creating the shader resource view looks ok, and so does your code for updating the contents of the buffer and binding it to the pixel shader stage.

For making sure you're doing things right, I recommend enabling the D3D debug layer for debug builds of your code. It will output helpful messages that can often tell you what you did wrong. I also recommend using ID3D11InfoQueue to tell the debug layer to immediately break into the debugger when it encounters an error.

@MJP Thanks for your advice. I have managed to enable the debug layer so I'll look through it to see what's wrong.

So the error given by D3D is

D3D11 ERROR: ID3D11Device::CreateBuffer: pInitialData->pSysMem cannot be NULL. [ STATE_CREATION ERROR #65: CREATEBUFFER_INVALIDINITIALDATA]

Which I assume is a result of the following code:

D3D11_SUBRESOURCE_DATA subResourceData2;
subResourceData2.pSysMem = (void*)_lights.data();
subResourceData2.SysMemPitch = 0;
subResourceData2.SysMemSlicePitch = 0;

Since the light vector can be empty at the start, because it may not contain any lights, how can I initialize the pSysMem value when creating the subresource?

If you don't want to initialize the buffer when you create it, just pass NULL as the pInitialData argument to ID3D11Device::CreateBuffer. Since you've created it with DYNAMIC usage, you can just call Map later on to fill the contents of the buffer.

Oh by the way, it looks like your ByteWidth parameter is incorrect. ByteWidth should the total size of the buffer in bytes, so in this case you probably want it to be sizeof(YourLightStruct) * MaxNumberOfLights.

@mjp Setting pInitialData to NULL seems to have worked, but I found that an alternative solution was to set pSysMem to &_lights. Honestly I am not sure what the difference is. For now I'm just going with your solution.

 D3D11_BUFFER_DESC sbDesc;
 sbDesc.ByteWidth = sizeof(CLight) * 8;
 cout << sizeof(CLight) << endl;
 sbDesc.Usage = D3D11_USAGE_DYNAMIC;
 sbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
 sbDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
 sbDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
 sbDesc.StructureByteStride = sizeof(CLight);
 cout << sizeof(CLight) << endl;

 D3D11_SUBRESOURCE_DATA subResourceData2;
 subResourceData2.pSysMem = 0;
 subResourceData2.SysMemPitch = 0;
 subResourceData2.SysMemSlicePitch = 0;

 result = device->CreateBuffer(&sbDesc, NULL, &m_lightBuffer2);
 if(FAILED(result)){
  cout << "ERROR 1" << endl;
  return false;
 }

 D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
 srvDesc.Format = DXGI_FORMAT_UNKNOWN;
 srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
 srvDesc.Buffer.FirstElement = 0;
 srvDesc.Buffer.NumElements = 8;

 result = device->CreateShaderResourceView(m_lightBuffer2, &srvDesc, &m_pSRV);
 if (FAILED(result)) {
  cout << "ERROR 2" << endl;
  return false;
 }

However when trying to set the output color of the object to one of the light colours at index 0 just to test if there is data in the buffer, this does not seem to work. The object is black when it should be white (1.0f, 1.0f, 1.0f).

struct Light {
   float4   PositionWS;
   float4   Color;
   float    Range;
   float    Intensity;
   float2  Padding;
};

StructuredBuffer<Light> lights : register(t9);

color = lights[0].Color.xyz;

Not the whole code obviously but you get the point… I will try to fix this and report the solution. Here is my code for how I load the lights vector into the shader resource slot:

D3D11_MAPPED_SUBRESOURCE mappedResource2;
 result = deviceContext->Map(m_lightBuffer2, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource2);
 if (FAILED(result))
 {
  cout << "ERROR: 3" << endl;
  return false;
 }

 size_t sizeInBytes = lights.size() * sizeof(CLight);
 cout << lights.size() *  sizeof(CLight)  << endl;
 memcpy_s(mappedResource2.pData, sizeInBytes, lights.data(), sizeInBytes);

 deviceContext->Unmap(m_lightBuffer2, 0);

 deviceContext->PSSetShaderResources(9, 1, &m_pSRV);

What type is _lights? It looks like it is probably a std::vector based on your usage of it? If you pass a pointer to that through pSysMem, the runtime is going to fill your buffer with whatever is at the other end of that pointer, which in this case would be the internal members of the std::vector (and perhaps whatever memory follows that, since your buffer is likely larger than the size of a std::vector). This is almost certainly not what you want: if you want to fill your buffer with the elements in your std::vector, just add enough entries to it before calling data(). Or alternatively create a temporary array on the stack of CLight's, and pass the address of that.

As for your data mismatch, it looks like you're calling Map correctly and properly copying the data into the buffer. Does the layout of your Light struct in HLSL exactly match the layout of the CLight structure you're using in C++? It may be helpful to grab a capture in RenderDoc and see what the full contents of your buffer look like at the time of your Draw call.

@mjp I downloaded RenderDoc as per your suggestion and it turns out the buffer was full of random data… I guess this is because CLight was a class and not a struct. I changed it to a struct and now everything works fine. Thanks for your help!!

This topic is closed to new replies.

Advertisement