PIX report that my structured buffer is garbage. Why?

6 hours ago 3
ARTICLE AD BOX

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
old scene

THE ISSUE
When i render the scene with my new changes i get black rendernew scene

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
shadow map 1

shadow map 2

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

pix display

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!
scatters

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!

Read Entire Article