-
Notifications
You must be signed in to change notification settings - Fork 213
Description
Vulkan specifically defines a VkBaseInStructure with a constant pNext pointer for read-only inputs to APIs like info in vkGetPhysicalDeviceImageFormatProperties2() (in contrast to the writable properties as outputs).
We do not currently make use of this distinction between VkBaseOutStructure, requiring callers to unconditionally declare their "read-only" input structures as mut when using .push_next(&mut T) because of the way we build up a pointer chain.
While this might not directly be a problem, it would have been nice to have a way to construct chains "more linearly" (like in C++) while keeping these structs defined as constants locally, to signify their
Example
let mut drm_format_modifier_info = vk::PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
.drm_format_modifier(1337);
let mut external_image_format_info = vk::PhysicalDeviceExternalImageFormatInfo::default()
.handle_type(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
let info = vk::PhysicalDeviceImageFormatInfo2::default()
.push_next(&mut drm_format_modifier_info)
.push_next(&mut external_image_format_info);
let mut external_image_format_properties = vk::ExternalImageFormatProperties::default();
let mut props = vk::ImageFormatProperties2::default()
.push_next(&mut external_image_format_properties);
instance.get_physical_device_image_format_properties2(pdevice, &info, &mut props).unwrap();Unfortunately external_image_format_info and drm_format_modifier_info in this example are only defined mut because of .push_next(&mut T). The second call to .push_next() has to mutate external_image_format_info.p_next = drm_format_modifier_info.p_next; to insert it in the chain.
Not to mention, by default this means structs are inserted in the chain in reverse order, i.e. the last struct to go ends up being the first struct in the chain. This is usually not a problem in practice.
Unfortunately I do not immediately have a solution to this "problem" (that isn't introducing multiple wrapper structs and macros to abstract chains...) aside from defining a .next() directly on these "extending structures, so that you would instead write:
let drm_format_modifier_info = vk::PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
.drm_format_modifier(1337);
let external_image_format_info = vk::PhysicalDeviceExternalImageFormatInfo::default()
.handle_type(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD)
.next(&drm_format_modifier_info);
let info = vk::PhysicalDeviceImageFormatInfo2::default()
.next(&external_image_format_info);This is much closer to what one would write in C++, making sample/code translation easier. Noting that next() here is a builder function that overwrites p_next - and probably needs an assert in case !self.p_next.is_null()?
However, it easily breaks our predicate of guarding push_next() to only take extending structs, and only implementing it on the root struct via trait ExtendsT. Moreover, we'd have to disambiguate where a struct extends multiple root structs, such as:
unsafe impl ExtendsBufferViewCreateInfo for BufferUsageFlags2CreateInfoKHR<'_> {}
unsafe impl ExtendsBufferCreateInfo for BufferUsageFlags2CreateInfoKHR<'_> {}
unsafe impl ExtendsPhysicalDeviceExternalBufferInfo for BufferUsageFlags2CreateInfoKHR<'_> {}
unsafe impl ExtendsDescriptorBufferBindingInfoEXT for BufferUsageFlags2CreateInfoKHR<'_> {}Besides not being able to create an OR trait bound on this hypothetical fn next<T: ExtendsX | ExtendsY | ExtendsZ>, users could mix up pointer chains from different "incompatible" hierarchies at places.
Perhaps that validation is not something that is very important for Ash. It's definitely a nicety for callers and why I'd like to keep it, but not something that's in place to prevent UB.