RFC: Vulkan structure defaulting for C++11#2669
Conversation
These headers are generated from the updates here: KhronosGroup/Vulkan-Docs#2669
|
If you'd like to add C99 support for defaults, here's an idea: Then one could do this, which is no more onerous than the existing need for setting the Note that C99 allows |
|
If we adopt this, we probably need to start being explicit about what language our samples and other code snippets (such as proposal docs) are written in. This compiles in both C and C++, but would violate valid usage in C. VVL will catch it, but it is a potential footgun for anyone blindly copying example code into C files. That danger is slightly mitigated if the sample code also contains other C++-isms, and so fails to compile anyway. I don't think this is a deal breaker in any way, just a reminder for our future selves to make sure that documentation/proposal docs are clear on this. |
|
Not an exhaustive list, but here are some "sane" defaults that was discussed in the Graphics Programming Discord server (vulkan channel) Some of these are merely common values rather than "true" defaults, but this is to get things moving, not the final list.
|
|
Looking at the generated code in Vulkan-Headers, it shows that the function overloads that take C++20 spans requires VK_NO_PROTOTYPES to not be defined. Put another way, these are only usable without dynamically linking to the Vulkan-Loader, meaning users of Volk (or a custom function loader) could not use these overloads. Seems like a prickly thorn to work around. Can't define overloads for functions that do not exist, nor do we want to add more function overloads. |
Because Volk declares the "functions" as global function pointer variables, you can't use regular C++ function overloading. You can only overload regular functions. This is partly why I created KhronosGroup/Vulkan-Loader#1850 so vulkan-1.lib implements more entry points as regular functions. I'm not yet sure how best to workaround this problem with Volk and other similar utilities. namespaces kind of works, but you can't use |
|
If these overloads require linking, them I'm not likely to use them nor advocate for them as one of the benefits of not linking is the ability to get the driver's functions and call those directly (or whatever the first layer in the chain is). Vulkan-hpp has many examples of overloads without requiring linking, this is because they implement their own Volk-like function loader and tables of function pointers, then have the overloads call those. Which pretty much means the only way I can see to get non-linking to vulkan-1.lib to work with overloads is to have the headers implement all the gubbins of volk, which is a big change. |
Could we move this discussion about overload functions to the other PR #2671, since this PR is about structure defaulting. All the overloads are header-only |
|
Ah yes, my bad. Will continue discussion there. |
|
Does this not re-implement what is already being offered in Vulkan-Hpp? Of course, it is more lean, but as already mentioned above, the Vulkan C headers would then be enabling C++ features. This might end up clashing with or creating duplicate work alongside Vulkan-Hpp in the future, while also blurring the lines of what each set of headers is designed for (C vs C++). |
Yes there is overlap between what this is doing and what is already in Vulkan-Hpp. The key difference is that the defaulting done by this PR affects the standard Vulkan API structs like VkPipelineRasterizationStateCreateInfo, and doesn't require the app to use something different like vk::PipelineRasterizationStateCreateInfo and its enums like vk::PolygonMode::eFill. I'm not saying Vulkan-Hpp is bad, what it has done is great, I love it, and developers can embrace Vulkan-Hpp if it works for them. This is an alternative for more traditional C/C++-lite developers as a seamless choice to incrementally improve their quality of life. Replacing Vulkan-Hpp is not a goal of this work. There are already some __cplusplus things in the standard headers for type safety. |
|
Vulkan-Hpp structs/enums are binary compatible with the original Vulkan C structs. You can just cast between them if need be. This allows a very easy step-by-step transition to C++ if people want that, bringing all the advantages of that language along with it. The What exactly is this trying to achieve that Vulkan-Hpp does not already provide? Don't get me wrong, I like the features you propose here, it is partially why I use Vulkan-Hpp personally. I just feel like this is trying to shoehorn C++ idioms into C headers. |
|
It'll most probably boil down to personal preferences. As for me, I don't like vulkan-hpp that much (modern C++, long build times), and I prefer the C headers. Having a C header that sits somewhere in between vulkan.h and vulkan.hpp would be something I'd personally use. That's also constant feedback from the community asking for something like this. As this is going to be opt-in (as far as I understand) I don't see any issues with this. If people want a bit of C++ with the C headers to make things easier, they opt in. If they don't they use th C style. If they want more, they use vulkan.hpp. With that all bases are covered. |
|
The complaints about long build times I can totally understand. That's definetely something that needs improvement. Though, if there's constant community feedback about asking for this, then it is likely best to have it in |
|
That aside, @pdaniell-nv @charles-lunarg since it is part of the discussion, what are your thoughts on having opinionated defaults for the struct members in the first place? Such as setting It is easy to know what to expect when everything is zero-initialized, but those defaults could muddy the waters a little. Could easily be a breaking change to change them later down the line too. |
In my opinion, providing vk.xml is the method for catering to a continuum of preferences. It is unclear to me why it is useful to pick a specific interval on this c-style - modern cpp scale by the official C binding when this mechanism exists. |
|
My understanding is that to maintain compatibility with existing code, this can’t be enabled automatically under C++11 rules. Initializing members would break code like this that was previously valid (and idiomatic): I believe this is fixed in C++14 and the new definitions would work with existing initialization like this there. |
|
I guess for c, the best is to just generate your own helpers from the xml. I once did this by creating two defines for each vulkan struct. It looked like this. #ifdef VK_VERSION_1_1
#define CRVkBindBufferMemoryInfo(...) { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO, ##__VA_ARGS__ }
#define CLVkBindBufferMemoryInfo(name,...) VkBindBufferMemoryInfo name = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO, ##__VA_ARGS__ }
Eg for // init as rvalue
VkBindBufferMemoryInfo info = CRVkBindBufferMemoryInfo(NULL, buffer,...);
// init as lvalue with name info
CRVkBindBufferMemoryInfo(info, NULL, buffer,...);
info.memoryOffset - 0;Extending this with different combinations of prefixes to set pNext always to For cvkChain(head, middle, ..., tail)which was autogenerated up to a certain count #define CVK_EXPAND(x) x
#define CVK_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,N,...) N
#define CVK_NARG(...) CVK_EXPAND(CVK_ARG_N(__VA_ARGS__,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define CVK_CHAIN_1_(V) CVK_CHAIN_1 V
#define CVK_CHAIN_2_(V) CVK_CHAIN_2 V
#define CVK_CHAIN_3_(V) CVK_CHAIN_3 V
#define CVK_CHAIN_4_(V) CVK_CHAIN_4 V
#define CVK_CHAIN_2(A,B) A.pNext = &B
#define CVK_CHAIN_3(_0,_1,_2) CVK_CHAIN_2(_0,_1); CVK_CHAIN_2(_1,_2)
#define CVK_CHAIN_4(_0,_1,_2,_3) CVK_CHAIN_3(_0,_1,_2); CVK_CHAIN_2(_2,_3)
#define CVK_CHAIN_5(_0,_1,_2,_3,_4) CVK_CHAIN_4(_0,_1,_2,_3); CVK_CHAIN_2(_3,_4)
#define CVK_CHAIN__(N, ...) CVK_CHAIN_ ## N ## _((__VA_ARGS__))
#define CVK_CHAIN_(N, ...) CVK_CHAIN__(N, __VA_ARGS__)
#define cvkChain(...) CVK_CHAIN_(CVK_NARG(__VA_ARGS__), __VA_ARGS__) |
|
There's also https://github.com/KhronosGroup/Vulkan-Utility-Libraries/blob/main/include/vulkan/utility/vk_struct_helper.hpp Arguably this mixes constants and defaults. |
|
Did a version of my "How to Vulkan in 2026" tutorial with this and really liking this. It removes a lot of verbosity and clearly improves code readability. Esp. with C++ requiring members for designated initializers to be in order, which usually means |
|
Any preferred way of listing possible additional sane defaults that could make sense? E.g. |
Over the next few days I'm going to do a pass on the vk.xml file to add a ton more |
|
To the Vulkan-HPP discussion, I don't think there's any actual competition between this and Vulkan-HPP. I think they serve two different audiences. In fact, I think Vulkan-HPP might be able to offer this as a natural extension point into the rest of Vulkan-HPP should people want to take advantage of more modern C++ practices which many will. That type of architecture might even benefit Vulkan-HPP while we wait for modules to exist to have a more ala carte' style c++ inclusion based upon needs which will drive down compile time. Keeping this focused on a specific need / use case can only serve as a good benefit for the Vulkan community. This is a great way to make Vulkan easier to use for new developers. I haven't commented before now because the most I really have to say is +1. Great effort and I love where this is going well done! |
|
Agreed, this will likely directly benefit Vulkan-Hpp too. Merely gotta be careful with the opinionated defaults imo.
Could you expand on what you mean by that? We are actively developing the module with plans to drop the experimental tag soon, so it would be nice to explore all the options beforehand. An issue over there would be very appreciated! (as to not clog this PR) |
|
I just noticed an issue with the way the header decides if defaults should be used or not. For reference, I'm talking about this: If the default is setting the only valid value, there's nothing wrong with that. But if it's setting something for convenience, then it must be opt-in. Let's take I can have an app today that knowingly does this (contrived, but for the sake of example): If we simply default |
|
This is a great point; I think for non-bitmasks, changing the default is only safe if 0 was an invalid value previously. E.g. Defaulting
|
This PR adds optional Vulkan structure C++11 default member initializations that apps compiling for C++11 and above can use to avoid having to initialize the
sType,pNextand other member values manually. The output of these changes can be seen here:The initial draft PR just supports
sType,pNextandlineWidth. Defaults for other members will be added soon.This PR adds a new C macro
VK_CPP11_DEFAULT(value)and is added to Vulkan structures where default values make sense and add convenience for application. This especially applies to thesTypeandpNextmembers. For example:In this example, if the application declares a VkPhysicalDeviceProperties2 variable like this:
then the
sTypeandpNextmembers will be initialized, making this a correctly initialized structure that can be used directly with the Vulkan API.The application can also do the following to zero-initialize all the other members:
VkPhysicalDeviceProperties2 properties{ };When using C++20, the applications can combine this functionality with designated initializers like this:
VkPhysicalDeviceVulkan14Properties vulkan14Properties{ }; VkPhysicalDeviceProperties2 properties{ .pNext{&vulkan14Properties} }; vkGetPhysicalDeviceProperties2(physicalDevice, &properties);Structure members that do not have a
VK_CPP11_DEFAULTinitializer should be manually set by the application like before because there is no appropriate default.