Jump to content
  • Advertisement
WFP

Abstracting Resource Binding for D3D12 and Vulkan

Recommended Posts

Main question: How are you all nowadays abstracting resource binding between D3D12 and Vulkan?

The graphics backend for my engine was initially written in Direct3D 12, and the abstraction layer very closely mimicked that with the goal of being as thin as possible (many calls basically amount to passthroughs).

I've recently started porting my graphics backend to Vulkan. I've actually found this to be very straightforward for the majority of the effort, the two largest differences of course being render target/framebuffer management and resource binding. Of those two topics, resource binding is the main area I have hang-ups on for best abstraction and implementation, and that is where I'm hoping for advice. I've got this working "well-enough", which is to say I can bind resources and they get where they need to go, but the setup I have feels brittle, for lack of a better word, and I consider it to be very much in a rough draft type of state.

I've read a few posts here on the topic, but many are a few years old at this point. I'm sure best practices have evolved over time as people become more accustomed to working with both APIs.  For example, this post is almost three years old to the date (https://www.gamedev.net/forums/topic/678860-descriptor-binding-dx12-and-vulkan/).


Secondary question: Mostly out of curiosity, how universal is support for bindless textures?

More specifically, if the targets are PC and current generation consoles, can a bindless model be fully adopted, or are there any platforms in this set that do not support it?


Last question: What are your thoughts on a root constant/push constant versus a draw ID vertex buffer?

I implemented this type of side-along vertex buffer a long while back in the engine's D3D11 days in order to bind one large constant buffer and index into it with an instance offset at draw time, and have honestly just not revisited its necessity until I reviewed it while making notes before I started working on the Vulkan port. Basically, I bind the actual vertex buffer and additionally to a known slot bind a draw ID vertex buffer that is just incrementing 0-4095 where the index is accessed through the startInstanceLocation parameter of draw calls. It certainly works well enough and keeps the number of setConstantBufferView-type calls very low, but it seems a root/push constant would achieve the same outcome without the additional vertex buffer being bound. Honestly, I should probably just profile and see, but figured I'd ask for general consensus while I was here :)


Thanks for your time!

Share this post


Link to post
Share on other sites
Advertisement
Posted (edited)

I can only comment the second question. On AMD GCN, accesses to a vertex buffer and a constant buffer go via different caches (K$ for constants, standard L1/L2 for vertices/indices if I recall right). So your performance might vary a bit, although I don't expect too much.

The AMD GCN chips do support 'bindless textures', in the sense that they already fetch the texture/buffer descriptors from memory based on constants or anything else. It's even possible to synthetise a descriptor out of thin air in the shader. That should answer about the consoles :)

Edited by pcmaster

Share this post


Link to post
Share on other sites

Thank you, that is helpful! I'm fairly certain bindless is a safe bet for the other console, as well, since it's supposed to be Tegra X1-based, and bindless textures are specifically called out on this page: https://shield.nvidia.com/blog/nextgenx1.

Share this post


Link to post
Share on other sites
6 hours ago, WFP said:

How are you all nowadays abstracting resource binding between D3D12 and Vulkan?

I support D3D11 as well, but have an API that pretends that there are 8 "resource list" binding slots. A "resource list" is an API object kind of like a constant buffer, but it contains an array of resource handles.

In D11, this can be implemented as an array of views at binding time, and a contiguous block of t# registers in the shader. In D12/Vulkan is can be implemented as a group of descriptors, bound by different mechanisms...

3 hours ago, pcmaster said:

I can only comment the second question. On AMD GCN, accesses to a vertex buffer and a constant buffer go via different caches (K$ for constants, standard L1/L2 for vertices/indices if I recall right). So your performance might vary a bit, although I don't expect too much.

GCN doesn't have any special handling for different buffer types - cbuffers, SRVs, VBs, etc are all just buffer descriptors. The difference is whether you're indexing them with a scalar/uniform value, or a value that varies per thread/vertex/pixel.

CBV values will be scalar loads (unless doing varying array indexing into a cbuffer), but if you load from an SRV using a scalar value such as the draw ID, then it will also use the scalar load hardware path.

6 hours ago, WFP said:

What are your thoughts on a root constant/push constant versus a draw ID vertex buffer?

The draw ID itself is likely supplied as a root/push-constant on some hardware platforms 😉

If you need more than ~60 bytes worth of data, then a typical fetch is probably better than the push constants.

Share this post


Link to post
Share on other sites

Thanks for the reply, that's a helpful way to think about it.

I've actually been working on a way to remove the concept of a descriptor heap directly, and instead submit ResourceBinding structs (several at a time as a list), and have some metadata that's stored at root signature and PSO creation time sort out the right place for them to land. It helps keep the API a little smaller/cleaner, and also lets me do a little bit of basic validation to ensure the root signature (pipeline layout for Vulkan) matches the incoming bindings.

It seems like this is going to be an even better approach since it can also obscure the underlying details of descriptor pools/sets. I'll keep at it and see how it goes.

And thanks for the tip regarding the draw ID 😃

Share this post


Link to post
Share on other sites

For D3D12 at least, you're good for bindless SRV's if the device supports Tier 2 resource binding. You can check which hardware supports that this handy chart. Tier 2 still restricts what you can do for CBV's and UAV's, but those are easier to work around in my opinion.

Share this post


Link to post
Share on other sites

Thanks! I'd seen the MSDN page (https://docs.microsoft.com/en-us/windows/desktop/direct3d12/hardware-support) before, but it's great to have an additional resource to turn to for referencing actual hardware support. That combined with Steam's hardware survey seems like a really handy combination.

And yeah, I probably should've specified that by bindless I was more specifically referring to SRVs. I can see it as useful for CBVs and UAVs, but so far haven't had issue working even within their low tier limits.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!