THE CONTEXT
I am adding shadow mapping to my DirectX 12 renderer. So i need to access lights both individually and the whole array. I decided to use a StructuredBuffer to store the lights.
The main constraint i had is that DX12 forces me to use a struct that is 256 bytes aligned to be able to feed it to SetGraphicsRootConstantBufferView.
THE TEST SCENE
Simple scene. One plane two spheres and two lights

THE ISSUE
When i render the scene with my new changes i get black render
SOLUTION
Two possibilities. Either the shadow mapping doesnt work properly(1) either the light buffer is broken(2)
// Here what the DX12 light struct look like. HLSL struct matches this.
struct DX12Light
{
DirectX::XMFLOAT4 position;
DirectX::XMFLOAT4 direction;
DirectX::XMFLOAT4 color;
DirectX::XMFLOAT4X4 transform0;
DirectX::XMFLOAT4X4 transform1;
FLOAT nearZ;
FLOAT farZ;
FLOAT range;
INT type;
INT linearIdx;
FLOAT padding[15];
};
// How the light structured buffer is created:
void LightStructuredBuffer::AllocateLights(const Scene::BaseScene& scene, ID3D12GraphicsCommandList* commandList)
{
const EntityIdentifierArray& lights = scene.getLights();
const uint32 lightCount = static_cast<uint32>(lights.size());
m_curLightCount = lightCount;
_DX12_SAFE_RELEASE(m_array_SB_Handle.buffer);
_DX12_SAFE_RELEASE(m_array_Upload_Heap);
int lightLinearIdx = 0;
std::vector<DX12Light> vec;
for (const auto& lightId : lights)
{
vec.push_back(buildDX12LightEntry(scene, lightId, lightLinearIdx));
++lightLinearIdx;
}
_ASSERT(GraphicResourceAllocatorPtr<DX12_GRAPHIC_ALLOC_PARAMETERS>->createStructuredBuffer(vec, m_array_SB_Handle, (void**)&m_array_Upload_Heap));
const std::wstring defaultHeapName = _STRING("LightStructuredBuffer default heap size= ") + Utilities::toString(lightCount);
m_array_SB_Handle.buffer->SetName(defaultHeapName.c_str());
const std::wstring uploadHeapName = _STRING("LightStructuredBuffer upload heap size= ") + Utilities::toString(lightCount);
m_array_Upload_Heap->SetName(uploadHeapName.c_str());
const HRESULT hr = m_array_Upload_Heap->Map(0, &ReadRangeGPUOnly, reinterpret_cast<void**>(&m_array_CBGPU_Address));
_ASSERT(SUCCEEDED(hr));
commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_array_SB_Handle.buffer, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
}
Possibility(1)
I checked the shadow maps and they are rendered properly


For this to work i need to access the light individually. I simply pass the light D3D12_GPU_VIRTUAL_ADDRESS to the command list
inline D3D12_GPU_VIRTUAL_ADDRESS LightStructuredBuffer::getLightGPUVirtualAddress(const uint32 lightLinearIdx)
{
return m_array_SB_Handle.buffer->GetGPUVirtualAddress() + lightLinearIdx * DX12LightCB_NbBytes/*256*/;
}
There:
// D3D12_GPU_VIRTUAL_ADDRESS lightCBVirtualAdress = LightStructuredBufferSingleton::instance()->getLightGPUVirtualAddress(lightLinearIdx);
void RenderShadowMaps::pushRenderShadowMapCommands(RenderShadowMapsPushArgs& data, D3D12_GPU_VIRTUAL_ADDRESS lightGPUVirtualAdress, ID3D12GraphicsCommandList* commandList)
{
AutoRenderEventTracker autoTracker(commandList, _STRING("RenderShadowMap"));
commandList->SetGraphicsRootSignature(m_renderShadowMap_RootSignature);
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
DescriptorHandle& DepthDescriptorHandle = ShadowMapManagerSingleton::instance()->getRenderingDepthDescriptorHandle();
commandList->OMSetRenderTargets(1, &m_dummyRenderTargetDescriptorHandle.getCpuHandle(), FALSE, &DepthDescriptorHandle.getCpuHandle());
commandList->ClearDepthStencilView(DepthDescriptorHandle.getCpuHandle(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// ------- HERE ------------
commandList->SetGraphicsRootConstantBufferView(0, lightGPUVirtualAdress);
// -------------------------
// etc...
The HLSL constant buffer look like this:
cbuffer LightStructCB : register(b0)
{
DX12Light light;
};
Possibility(2) The light structured buffer is broken.
I checked the buffer in PIX and i get this

Absolute garbage. There is only two lights normally :O
What am i doing wrong?. And why accessing individual lights when rendering shadow maps work? this is weird!
Here how the light array is bound to the commmand list:
commandList->SetGraphicsRootDescriptorTable(0, LightStructuredBufferSingleton::instance()->getDefaultHeapSRV().getGpuHandle());
// --------------------------------------------------------------------------------------------------------
ADDITIONAL INFORMATION
Here how a structured buffer is created:
bool DX12Renderer::createStructuredBuffer(const void* data, Dx12StructuredBufferHandle& dst, uint32_t sizeofElem, uint32_t count, void** uploadHeapDest)
{
const ArrayBufferResource resourceData = createArrayBufferRecource(data, sizeofElem, count, true, uploadHeapDest);
if (resourceData.bufferSize == 0u || resourceData.buffer == nullptr)
{
return false;
}
dst.buffer = resourceData.buffer;
createSRV(dst, sizeofElem, count);
createUAV(dst, sizeofElem, count);
return true;
}
DX12Renderer::ArrayBufferResource DX12Renderer::createArrayBufferRecource(const void* data, uint32_t sizeofElem, uint32_t count, bool allowUAV, void** uploadHeapDest)
{
ArrayBufferResource retData;
retData.bufferSize = sizeofElem * count;
CD3DX12_RESOURCE_DESC defaultHeapResDesc = CD3DX12_RESOURCE_DESC::Buffer(retData.bufferSize);
if (allowUAV)
defaultHeapResDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
// Create default heap
HRESULT hr = D3D12Device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&defaultHeapResDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&retData.buffer));
if (FAILED(hr))
{
_TRACE_STD("DX12Renderer::createArrayBufferRecource failed");
return ArrayBufferResource();
}
// create upload heap
ID3D12Resource* vBufferUploadHeap;
hr = D3D12Device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(retData.bufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vBufferUploadHeap));
if (FAILED(hr))
{
_TRACE_STD("DX12Renderer::createArrayBufferRecource failed");
_DX12_SAFE_RELEASE(retData.buffer);
return ArrayBufferResource();
}
// Store buffer data in upload heap.
D3D12_SUBRESOURCE_DATA bufferData = {};
bufferData.pData = reinterpret_cast<BYTE*>(const_cast<void*>(data));
bufferData.RowPitch = retData.bufferSize;
bufferData.SlicePitch = retData.bufferSize;
// We are now creating a command with the command list to copy the data from the upload heap to the default heap.
UpdateSubresources(m_commandBuffers[CommandType::Direct].commandList, retData.buffer, vBufferUploadHeap, 0, 0, 1, &bufferData);
// Transition the buffer data from copy destination state to vertex buffer state.
m_commandBuffers[CommandType::Direct].commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(retData.buffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER));
if (uploadHeapDest)
*uploadHeapDest = vBufferUploadHeap;// What about the upload heap state????
else
m_pendingResources.push_back(vBufferUploadHeap);
return retData;
}
void DX12Renderer::createSRV(Dx12StructuredBufferHandle& dst, uint32_t sizeofElem, uint32_t count)
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Buffer.NumElements = count;
srvDesc.Buffer.StructureByteStride = sizeofElem;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
dst.srvDescriptorHandle = m_cbs_srv_uavAllocator->allocate({ dst.buffer }, srvDesc);
}
void DX12Renderer::createUAV(Dx12StructuredBufferHandle& dst, uint32_t sizeofElem, uint32_t count)
{
// Not tested. Unused for now
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.Buffer.CounterOffsetInBytes = 0;
uavDesc.Buffer.NumElements = count;
uavDesc.Buffer.StructureByteStride = sizeofElem;
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
dst.uavDescriptorHandle = m_cbs_srv_uavAllocator->allocate({ dst.buffer }, uavDesc);
}
I use the same createStructuredBuffer function when creating scatters. And it works well. I mean without the new changes!

void ScatterTransformStructuredBuffers::resetBuffers(const Scene::BaseScene& scene, ID3D12GraphicsCommandList* commandList)
{
freeAllGpuEntries();
const EntityIdentifierArray& scatters = scene.getScatters();
for (uint32_t i = 0u; i < scatters.size(); ++i)
{
const EntityIdentifier& scatterEntityId = scatters[i];
const Graphics::Scatter::DatabaseScatterPtr scatter = Graphics::Scatter::getScatterPtr_FromEntity(scatterEntityId);
if (!scatter->isRealtimeEnabled())
continue;
const EntityIdentifierArray& generatedGroups = scatter->getGeneratedMeshGroups();
if (generatedGroups.empty())
continue;
std::vector<MeshGroupData> groupDatas(generatedGroups.size());
for (uint32_t j = 0u; j < groupDatas.size(); ++j)
{
groupDatas[j] = computeMeshGroupData(generatedGroups[j]);
}
Dx12StructuredBufferHandle bufferHandle;
_ASSERT(GraphicResourceAllocatorPtr<DX12_GRAPHIC_ALLOC_PARAMETERS>->createStructuredBuffer(groupDatas, bufferHandle));
std::wstring defaultHeapName = _STRING("ScatterTransformStructuredBuffers default heap index = ") + Utilities::toString(i);
bufferHandle.buffer->SetName(defaultHeapName.c_str());
m_scatterGPUEntries.emplace(scatterEntityId, bufferHandle);
}
}
THANKS for helping!