Advertisement

CBVs in Descriptor Heap?

Started by July 23, 2018 03:49 AM
5 comments, last by ChuckNovice 6 years, 6 months ago

I finally started tackling a proper management of the views in my DX12 project. 


So far I implemented a ring buffer to manage a big offline and online descriptor heap to which I copy descriptors in a contiguous way as required by my resource binding (I already have plan to improve on the ring buffer idea to manage fragmentation a little better as I don't want to copy descriptors on every draw call if they are already copied contiguously from a previous call). This concept has been well explained by @Hodgman in this comment : 

For SRV / UAV I have no problem so far, everything work just as I understood the concept.
For CBV I apparently cannot reference them with a descriptor table in the root signature. 
The debug layer report an error saying that a descriptor table is invalid for a CBV root signature parameter even if my root signature was indeed created as a CBV descriptor table at this location so until this question is clear to me I moved all my CBV directly in a root descriptor which seem to be the only option left.

The reported error is this one D3D12 ERROR: CGraphicsCommandList::SetGraphicsRootDescriptorTable: The currently set root signature declares parameter [0] with type CBV, so it is invalid to set a descriptor table here. [ EXECUTION ERROR #708: SET_DESCRIPTOR_TABLE_INVALID]

Now as I noticed, binding a CBV as root descriptor don't even require the CBV to be in a descriptor heap and you don't even need to call ID3D12Device::CreateConstantBufferView at all. You simply pass the GPU virtual address of the resource to ID3D12GraphicsCommandList::SetGraphicsRootConstantBufferView and it all work without complaining.

I'm a bit confused because from how I understand it so far a descriptor heap should only be used when you are going to work with descriptor tables.
Why would the type D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV even exist and allow CBVs in it if CBVs can only be referenced as a root descriptor? I'm also wondering why the ID3D12Device::CreateConstantBufferView call exists at all in that case.

 

There's obviously few details that I didn't catch. Could someone enlighten me on this.

Thanks

34 minutes ago, ChuckNovice said:

The reported error is this one D3D12 ERROR: CGraphicsCommandList::SetGraphicsRootDescriptorTable: The currently set root signature declares parameter [0] with type CBV, so it is invalid to set a descriptor table here. [ EXECUTION ERROR #708: SET_DESCRIPTOR_TABLE_INVALID]

That says that you made root parameter 0 a root CBV, not a descriptor table of CBVs.

Advertisement
3 minutes ago, SoldierOfLight said:

That says that you made root parameter 0 a root CBV, not a descriptor table of CBVs.

With my previous version of the code which gets this error my root parameter 0 is in fact a DescriptorRange of CBV and not a root descriptor.

Are you 100% sure that it is what the error means? The error only mention a "parameter" which still could be anything between a root descriptor and a descriptor table since both of them allow specifying that it's for a CBV.

I will definitely investigate this further meanwhile in case a brain fart made me not notice it.

Positive. A root parameter of type CBV is a root CBV. A root parameter of type descriptor table contains ranges, which may include CBVs in those ranges.

Yeah I use both tables-of-cbvs and root-cbvs. 

Root CBVs are useful when they change every draw, as changing a root param is much easier than creating a new table. As you mentioned this also means you don't need actual CBV descriptors at all, however, you do take on a lot of responsibility here. A Root CBV (with just a pointer - no full descriptor) is not bounds checked by the hardware. If your pointer is incorrect, or if the shader reads past the end of your buffer allocation (e.g. You allocate 256B but the shader tries to read a 1KB structure) then you can crash the GPU.

Tables of CBVs are useful if many draws are going to share the same CB bindings, as they can reuse the same table and just set a single root param. As above, there's also bounds checking safety where if the shader tries to read past the end of the buffer it just gets 0's instead of crashing. 

2 hours ago, SoldierOfLight said:

Positive. A root parameter of type CBV is a root CBV. A root parameter of type descriptor table contains ranges, which may include CBVs in those ranges.

I re-wrote big chunks of code in that area, did some refactoring, double-checked my stuff and it's now working.

I most likely had the root signature of a previous pass bound at that point which would match what you said. On top of that my call to CreateConstantBufferView had a wrong BufferLocation.

Thanks for the answer, I wanted to be sure before spending few hours on this.

1 hour ago, Hodgman said:

Yeah I use both tables-of-cbvs and root-cbvs. 

Root CBVs are useful when they change every draw, as changing a root param is much easier than creating a new table. As you mentioned this also means you don't need actual CBV descriptors at all, however, you do take on a lot of responsibility here. A Root CBV (with just a pointer - no full descriptor) is not bounds checked by the hardware. If your pointer is incorrect, or if the shader reads past the end of your buffer allocation (e.g. You allocate 256B but the shader tries to read a 1KB structure) then you can crash the GPU.

Tables of CBVs are useful if many draws are going to share the same CB bindings, as they can reuse the same table and just set a single root param. As above, there's also bounds checking safety where if the shader tries to read past the end of the buffer it just gets 0's instead of crashing. 

 

Thanks, the usage of root / table is clearer with that explanation. While refactoring my view manager I kept your post into consideration and made it possible to specify whether any view should be used in a table or directly in the root upon creation.

Both ways return an object that encapsulate the necessary binding information of the view in a IConstantBufferView / IShaderResourceView / IUnorderedAccessView like the DX11 way. I can then further validate those information against the root signature when binding if I want.

 

To bind a descriptor table I force the programmer to pre-create a "ResourceBindingState" by specifying an array of IResourceView (IConstantBufferView / IShaderResourceView / IUnorderedAccessView). I use that object to know which views need to be copied contiguously in the shader visible heap and store information on how to bind it later (GPU pointer and such).

 

I can now concentrate on finding a solution for the heap fragmentation since I want to make it possible to keep my ResourceBindingState alive as long as the views don't change.

 

Really thanks for your contribution on the subject.

This topic is closed to new replies.

Advertisement