summaryrefslogtreecommitdiff
path: root/content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md')
-rw-r--r--content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md724
1 files changed, 724 insertions, 0 deletions
diff --git a/content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md b/content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md
new file mode 100644
index 0000000..07f5740
--- /dev/null
+++ b/content/blog/2019-05-04-handmade-rust-4-vulkan-bindings.md
@@ -0,0 +1,724 @@
++++
+title = "Handmade Rust Part 4: Generating Vulkan bindings"
+[taxonomies]
+tags = ["Rust", "Programming", "Vulkan"]
++++
+
+Vulkan is a C API so we'll need some kind of bindings to be able to use it in Rust. The API is defined in a XML file distributed by Khronos. This file describes the structs, enums, constants, and functions for each version of the API, and all published extensions. The functions can then be loaded from the Vulkan dynamic library and other functions from the API.
+
+<!-- more -->
+
+However using a raw C API isn't easy in Rust because it requires using a lot of unsafe code. This is why we'll also generate builders for all structs so we can for instance fill in pointer/size pairs using slices, but we'll also generate methods that return Rust's `Result`s and take in Rust-friendly types like references instead of raw C types. Finally we'll also generate loaders so we don't have to manually load the function we need.
+
+Parsing the spec
+=======================
+
+The XML spec for Vulkan is notoriously annoying to use outside of the C/C++ ecosystem because it's basically C code with tags around it. For instance, good luck parsing this:
+
+```xml
+<member>const <type>void</type>* <name>pNext</name></member>
+```
+
+For my purpose, I wanted to transform this awkward representation into some kind of AST. For example, given the code above, this is what the parser outputs:
+
+```xml
+<member name="pNext" optional="False">
+ <pointer const="False">
+ <type const="True">void</type>
+ </pointer>
+</member>
+```
+
+This is much easier to use for generating code in any other language than C.
+
+On a higher level, the parser is also able to resolve type dependencies. The goal is to tell it what version of the API and what extensions to use. With that, the parser visits all necessary types and commands, possibly extends some enums, and this is what is then written to the output file.
+
+With that in mind, I won't go into too much details into how the parser works. Just know that it's written in Python and that it was both incredibly boring and annoying to do, I never want to do it again! I'll now show a few examples of the generated XML file.
+
+The source code for the parser and generators is available on [GitHub](https://github.com/stevenlr/VkXml).
+
+Type aliases
+---------------
+
+```xml
+<alias name="VkInstanceCreateFlags" type="VkFlags"/>
+<base name="VkDeviceSize" type="uint64_t"/>
+<handle name="VkBufferView" type="uint64_t"/>
+```
+
+The `alias` tag is a Vulkan-to-Vulkan type alias while `base` is a Vulkan-to-native alias. In this instance `VkInstanceCreateFlags` is an alias for `VkFlags` because it is an empty enum.
+
+Commands
+-------------
+
+```xml
+<command name="vkGetPhysicalDeviceSurfacePresentModesKHR" success-codes="VK_SUCCESS,VK_INCOMPLETE" type="instance">
+ <return-type>
+ <type const="False">VkResult</type>
+ </return-type>
+ <arg name="physicalDevice" optional="False">
+ <type const="False">VkPhysicalDevice</type>
+ </arg>
+ <arg name="surface" optional="False">
+ <type const="False">VkSurfaceKHR</type>
+ </arg>
+ <arg name="pPresentModeCount" optional="True">
+ <pointer const="False">
+ <type const="False">uint32_t</type>
+ </pointer>
+ </arg>
+ <arg length="pPresentModeCount" name="pPresentModes" optional="True">
+ <pointer const="False">
+ <type const="False">VkPresentModeKHR</type>
+ </pointer>
+ </arg>
+</command>
+```
+
+`command` define their name of course, but also their success codes for functions that return a `VkResult` (this will be used for error checking) and their type, which defines how the associated function pointer must be loaded.
+
+The command has one `return-type` and many `arg`s with a type AST inside.
+
+Each argument defines whether it is optional or not (this is useful for pointers) and pointer arguments can indicate what argument defines their length.
+
+- For instance if `length` is `pPresentModeCount`, it means that the `pPresentModeCount` argument defines the length of the array pointed to by the pointer.
+- If the length is `pCreateInfo::somethingCount`, it means that the lenth of the array is the `somethingCount` field from the struct in the `pCreateInfo` argument.
+
+These info are useful to generate functions that take in slices instead of pointer/length pairs, which is much more idiomatic in Rust. Also, whether the pointer type is const or not indicates whether it is an input argument or not.
+
+Enums
+-----------
+
+```xml
+<enum name="VkPresentModeKHR" type="enum">
+ <entry name="VK_PRESENT_MODE_IMMEDIATE_KHR">0</entry>
+ <entry name="VK_PRESENT_MODE_MAILBOX_KHR">1</entry>
+ <entry name="VK_PRESENT_MODE_FIFO_KHR">2</entry>
+ <entry name="VK_PRESENT_MODE_FIFO_RELAXED_KHR">3</entry>
+</enum>
+```
+
+Not much to say here, everything is pretty obvious. :)
+
+Bit flags
+--------------
+
+```xml
+<bitmask flags="VkShaderStageFlagBits" name="VkShaderStageFlags"/>
+<enum name="VkShaderStageFlagBits" type="bitmask">
+ <entry name="VK_SHADER_STAGE_VERTEX_BIT">1</entry>
+ <entry name="VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT">2</entry>
+ <entry name="VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT">4</entry>
+ <entry name="VK_SHADER_STAGE_GEOMETRY_BIT">8</entry>
+ <entry name="VK_SHADER_STAGE_FRAGMENT_BIT">16</entry>
+ <entry name="VK_SHADER_STAGE_ALL_GRAPHICS">31</entry>
+ <entry name="VK_SHADER_STAGE_COMPUTE_BIT">32</entry>
+ <entry name="VK_SHADER_STAGE_ALL">2147483647</entry>
+</enum>
+```
+
+For bit flags, we associate a top-level `bitmask` tag which indicates the association between the enum that defines the flags and the type that combines those flags.
+
+In Vulkan, the flags enum are always called `XxxFlagBits` and the combined flags `XxxFlags`.
+
+Function pointers
+---------------------
+
+```xml
+<function-pointer name="PFN_vkFreeFunction">
+ <return-type>
+ <type const="False">void</type>
+ </return-type>
+ <arg name="pUserData" optional="False">
+ <pointer const="False">
+ <type const="False">void</type>
+ </pointer>
+ </arg>
+ <arg name="pMemory" optional="False">
+ <pointer const="False">
+ <type const="False">void</type>
+ </pointer>
+ </arg>
+</function-pointer>
+```
+
+Function pointers follow the exact same syntax as commands.
+
+Constants
+--------------
+
+```xml
+<integer-constant name="VK_SUBPASS_EXTERNAL" size="32">4294967295</integer-constant>
+<real-constant name="VK_LOD_CLAMP_NONE" size="32">1000.0</real-constant>
+<string-constant name="VK_KHR_WIN32_SURFACE_EXTENSION_NAME">VK_KHR_win32_surface</string-constant>
+```
+
+Structures and unions
+-----------
+
+```xml
+<struct name="VkInstanceCreateInfo">
+ <member default_value="VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO" name="sType" optional="False">
+ <type const="False">VkStructureType</type>
+ </member>
+ <member name="pNext" optional="False">
+ <pointer const="False">
+ <type const="True">void</type>
+ </pointer>
+ </member>
+ <member name="flags" optional="True">
+ <type const="False">VkInstanceCreateFlags</type>
+ </member>
+ <member name="pApplicationInfo" optional="True">
+ <pointer const="False">
+ <type const="True">VkApplicationInfo</type>
+ </pointer>
+ </member>
+ <member name="enabledLayerCount" optional="True">
+ <type const="False">uint32_t</type>
+ </member>
+ <member length="enabledLayerCount" name="ppEnabledLayerNames" optional="False">
+ <pointer const="False">
+ <pointer const="True">
+ <type const="True">char</type>
+ </pointer>
+ </pointer>
+ </member>
+ <member name="enabledExtensionCount" optional="True">
+ <type const="False">uint32_t</type>
+ </member>
+ <member length="enabledExtensionCount" name="ppEnabledExtensionNames" optional="False">
+ <pointer const="False">
+ <pointer const="True">
+ <type const="True">char</type>
+ </pointer>
+ </pointer>
+ </member>
+</struct>
+```
+
+On their top-level tag, structs can define an `extends` attribute which indicates what structures can receive it in the `pNext` chain.
+
+Another interesting note here is that pointer fields can have a `length` attribute, which serves the exacts same purpose as on function arguments.
+
+Unions are the exact same thing as structures, so I won't detail them here.
+
+Generating Rust types
+========================
+
+Handles
+--------------
+
+```rust
+#[repr(transparent)]
+#[derive(Default, Copy, Clone, PartialEq, Eq)]
+pub struct VkPhysicalDevice(usize);
+impl VkPhysicalDevice
+{
+ #[inline]
+ pub fn null() -> Self
+ {
+ Self(0)
+ }
+
+ #[inline]
+ pub fn from_raw(r: usize) -> Self
+ {
+ Self(r)
+ }
+
+ #[inline]
+ pub fn as_raw(&self) -> usize
+ {
+ self.0
+ }
+}
+```
+
+Handles are generated as a newtypes with the `transparent` attribute, which guarantees that it has the exact same layout as the underlying type, allowing us to pass it to the API directly. We also generate a few helper methods.
+
+Enums
+-----------
+
+```rust
+#[repr(transparent)]
+#[derive(Default, PartialOrd, Copy, Clone, Ord, PartialEq, Eq, Hash)]
+pub struct VkSystemAllocationScope(u32);
+impl VkSystemAllocationScope
+{
+ pub const COMMAND: VkSystemAllocationScope = VkSystemAllocationScope(0);
+ pub const OBJECT: VkSystemAllocationScope = VkSystemAllocationScope(1);
+ pub const CACHE: VkSystemAllocationScope = VkSystemAllocationScope(2);
+ pub const DEVICE: VkSystemAllocationScope = VkSystemAllocationScope(3);
+ pub const INSTANCE: VkSystemAllocationScope = VkSystemAllocationScope(4);
+}
+```
+
+Enums are also generated as newtypes with `transparent`. Each value is an associated constant. It was not possible to use an `enum` here because there can be multiple symbols with the same value, which is not allowed in Rust enums.
+
+Bitflags
+------------
+
+```rust
+#[repr(transparent)]
+#[derive(Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct VkCompositeAlphaFlagBitsKHR(VkFlags);
+impl VkCompositeAlphaFlagBitsKHR
+{
+ pub const OPAQUE_BIT_KHR: VkCompositeAlphaFlagBitsKHR = VkCompositeAlphaFlagBitsKHR(1);
+ pub const PRE_MULTIPLIED_BIT_KHR: VkCompositeAlphaFlagBitsKHR = VkCompositeAlphaFlagBitsKHR(2);
+ pub const POST_MULTIPLIED_BIT_KHR: VkCompositeAlphaFlagBitsKHR = VkCompositeAlphaFlagBitsKHR(4);
+ pub const INHERIT_BIT_KHR: VkCompositeAlphaFlagBitsKHR = VkCompositeAlphaFlagBitsKHR(8);
+
+ #[inline]
+ pub fn contains(&self, other: Self) -> bool
+ {
+ return (self.0 & other.0) == other.0;
+ }
+}
+```
+
+Bitflags are implemented in a very similar way to enums, but they contains a very useful `contains` method for checking that a mask is contained in another one. They also implement additional traits like `BitOr`, `BitAnd`, `BitXor`, and their `-Assign` variants.
+
+Structures
+---------------
+
+```rust
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct VkInstanceCreateInfo
+{
+ pub s_type: VkStructureType,
+ pub p_next: *const core::ffi::c_void,
+ pub flags: VkInstanceCreateFlags,
+ pub p_application_info: *const VkApplicationInfo,
+ pub enabled_layer_count: u32,
+ pub pp_enabled_layer_names: *const *const u8,
+ pub enabled_extension_count: u32,
+ pub pp_enabled_extension_names: *const *const u8,
+}
+
+pub trait ExtendsInstanceCreateInfo
+{
+}
+
+impl Default for VkInstanceCreateInfo
+{
+ fn default() -> Self
+ {
+ Self {
+ s_type: VkStructureType::INSTANCE_CREATE_INFO,
+ p_next: core::ptr::null(),
+ flags: VkInstanceCreateFlags::default(),
+ p_application_info: core::ptr::null(),
+ enabled_layer_count: 0,
+ pp_enabled_layer_names: core::ptr::null(),
+ enabled_extension_count: 0,
+ pp_enabled_extension_names: core::ptr::null(),
+ }
+ }
+}
+```
+
+Structures use the `repr(C)` attribute for ABI compatibility. They implement the `Default` trait, and we also define a `Extends-` trait that must be implemented by all structures that can be added to the `pNext` chain of this structure. For instance we have:
+
+```rust
+impl ExtendsInstanceCreateInfo for VkDebugUtilsMessengerCreateInfoEXT {}
+```
+
+Function pointers and commands
+-----------
+
+```rust
+pub type PfnVkEnumeratePhysicalDevices = extern "system" fn(
+ instance: VkInstance,
+ p_physical_device_count: *mut u32,
+ p_physical_devices: *mut VkPhysicalDevice,
+) -> VkResult;
+```
+
+Both function pointers and commands define associated function pointers that will be used in the loader. They use raw types, but will not be used directly by the user.
+
+Structure builders
+===================
+
+```rust
+pub struct VkInstanceCreateInfoBuilder<'a>
+{
+ s: VkInstanceCreateInfo,
+ _p: core::marker::PhantomData<&'a ()>,
+}
+
+impl<'a> VkInstanceCreateInfoBuilder<'a>
+{
+ pub fn new() -> Self
+ {
+ Self {
+ s: VkInstanceCreateInfo::default(),
+ _p: core::marker::PhantomData,
+ }
+ }
+
+ pub fn build(&self) -> VkInstanceCreateInfo
+ {
+ self.s.clone()
+ }
+
+ pub fn s_type(mut self, value: VkStructureType) -> VkInstanceCreateInfoBuilder<'a>
+ {
+ self.s.s_type = value;
+ self
+ }
+
+ pub fn push_next<T: ExtendsInstanceCreateInfo>(
+ mut self,
+ next: &'a mut T,
+ ) -> VkInstanceCreateInfoBuilder<'a>
+ {
+ unsafe {
+ let last = get_last_base_out_struct_chain(next as *mut T as *mut VkBaseOutStructure);
+ (*last).p_next = self.s.p_next as _;
+ self.s.p_next = core::mem::transmute(next);
+ }
+ self
+ }
+
+ pub fn p_application_info(
+ mut self,
+ value: Option<&'a VkApplicationInfo>,
+ ) -> VkInstanceCreateInfoBuilder<'a>
+ {
+ self.s.p_application_info = match value
+ {
+ Some(r) => r,
+ None => core::ptr::null(),
+ };
+ self
+ }
+
+ pub fn enabled_layer_count(mut self, value: u32) -> VkInstanceCreateInfoBuilder<'a>
+ {
+ self.s.enabled_layer_count = value;
+ self
+ }
+
+ pub fn pp_enabled_layer_names(
+ mut self,
+ values: &'a [*const u8],
+ ) -> VkInstanceCreateInfoBuilder<'a>
+ {
+ self.s.enabled_layer_count = values.len() as _;
+ self.s.pp_enabled_layer_names = values.as_ptr();
+ self
+ }
+
+ // ...
+}
+
+impl<'a> core::ops::Deref for VkInstanceCreateInfoBuilder<'a>
+{
+ type Target = VkInstanceCreateInfo;
+
+ fn deref(&self) -> &Self::Target
+ {
+ &self.s
+ }
+}
+```
+
+Raw structures can be annoying to fill in because they use pointers, amongst other reasons. To prevent this pain, we define a builder for each structure.
+
+Each field from the struct has an associated method to fill it in the raw struct. These methods use Rust-friendly types:
+
+ - References instead of pointers.
+ - `Option<&[mut] T>` instead of optional pointers.
+ - Fields with associated lengths take in a slice and fill in the associated length field (see `pp_enabled_layer_names` above).
+ - When taking in a reference, its lifetime is bound to the builder, allowing some sweet borrow-checking goodness.
+
+For structs that include a `pNext` chain, the associated `push_next` method checks the compatibility of the structure we want to push using the `Extends-` trait at compile time (oh yeah!), and then automagically pushes it to the front of the chain.
+
+In addition to all this, the builder also has a `build` method that returns the underlying struct. The only caveat is that all lifetime information is then lost.
+
+`Deref` and `DerefMut` are also implemented in order to be able to pass a builder as its underlying struct without having to call `build`.
+
+Note that most of this was inspired by [Ash](https://github.com/MaikKlein/ash).
+
+Generating a loader
+==================
+
+Now that we have the types sorted out, we need to load the functions pointers to all commands. This is done in several steps:
+
+ - Retrieve the `vkGetInstanceProcAddr` function from the Vulkan library (`vulkan-1.dll` on Windows). This is the "static" category.
+ - Retrieve the commands that don't depend on a Vulkan instance using `vkGetInstanceProcAddr` with `VK_NULL_HANDLE` as first parameter. This is the "entry" category.
+ - After an instance has been created, retrieve its commands using `vkGetInstanceProcAddr` with the instance handle as first parameter. This is the "instance" category.
+ - After a device has been created, retrieve its commands using `vkGetDeviceProcAddr` with the device handle as first parameter. This is the "device" category.
+
+Each category has a struct containing the function pointers to all its commands. It has a `new` method that takes in a closure used to load a command given its name as a null-terminated byte string. Each command also has a method to call it.
+
+For example, to create the `StaticCommands` struct, the user just needs to pass to it a closure that calls `GetProcAddress` with the handle to the DLL previously loaded.
+
+```rust
+#[derive(Clone)]
+pub struct EntryCommands
+{
+ pfn_enumerate_instance_layer_properties: PfnVkEnumerateInstanceLayerProperties,
+ pfn_enumerate_instance_extension_properties: PfnVkEnumerateInstanceExtensionProperties,
+ pfn_create_instance: PfnVkCreateInstance,
+}
+
+impl EntryCommands
+{
+ pub fn load(load_fn: impl Fn(&[u8]) -> PfnVkVoidFunction) -> Self
+ {
+ EntryCommands {
+ pfn_enumerate_instance_layer_properties: unsafe {
+ core::mem::transmute(load_fn(b"vkEnumerateInstanceLayerProperties\0"))
+ },
+ pfn_enumerate_instance_extension_properties: unsafe {
+ core::mem::transmute(load_fn(b"vkEnumerateInstanceExtensionProperties\0"))
+ },
+ pfn_create_instance: unsafe { core::mem::transmute(load_fn(b"vkCreateInstance\0")) },
+ }
+ }
+
+ #[inline]
+ pub unsafe fn enumerate_instance_layer_properties(
+ &self,
+ p_property_count: *mut u32,
+ p_properties: *mut VkLayerProperties,
+ ) -> VkResult
+ {
+ (self.pfn_enumerate_instance_layer_properties)(p_property_count, p_properties)
+ }
+}
+```
+
+The high level entry point, instance, and device wrappers
+========================
+
+The API is divided in three parts:
+
+ - The `EntryPoint` stores static and entry commands. It is created by giving it a closure, just like in the loader. This one is responsible for loading `vkGetInstanceProcAddr` from the OS. After loading the static commands, it also loads the entry commands.
+ - `Instance` wraps a `VkInstance` and the instance commands. It is created by giving it a `VkInstance` handle and an `EntryPoint` struct.
+ - `Device` wraps a `VkDevice` and the device commands. It is created by giving it a `VkDevice` handle and an `Instance` struct.
+
+Those structs contain a method for each of their command. Those methods are high level: they translate from idiomatic Rust to raw API calls. The next part will detail how they are generated.
+
+```rust
+#[derive(Clone)]
+pub struct Device
+{
+ handle: VkDevice,
+ d: DeviceCommands,
+}
+
+impl Device
+{
+ pub fn new(device: VkDevice, instance: &Instance) -> Self
+ {
+ let commands = DeviceCommands::load(|fn_name| unsafe {
+ instance.i.get_device_proc_addr(device, fn_name.as_ptr())
+ });
+ Self {
+ handle: device,
+ d: commands,
+ }
+ }
+}
+```
+
+Generating idiomatic Rust commands
+=====================================
+
+The first step to making the Vulkan API more ergonomic in this context is not requiring to pass in the device or instance handle as first argument of the commands. For this purpose, the first `VkInstance` argument of all instance commands is ommited and the one contained in the `Instance` is given to the raw call. Same thing for `VkDevice` in `Device`.
+
+Next, commands that return a `VkResult` now return a `Result<VkResult, VkResult>`. Whether it is `Ok` or `Err` depend on the success codes listed in the specs.
+
+Commands that return a single value via a pointer now return it naturally. If such a command returns a `VkResult`, the high level method returns a `Result<(VkResult, T), VkResult>` instead, where T is the returned value.
+
+```rust
+impl Device
+{
+ pub fn create_fence(
+ &self,
+ p_create_info: &VkFenceCreateInfo,
+ p_allocator: Option<&VkAllocationCallbacks>,
+ ) -> Result<(VkResult, VkFence), VkResult>
+ {
+ // VkResult vkCreateFence(
+ // VkDevice device,
+ // const VkFenceCreateInfo* pCreateInfo,
+ // const VkAllocationCallbacks* pAllocator,
+ // VkFence* pFence);
+
+ // ...
+ }
+}
+```
+
+The argument type ergonomics are the same used in structure builders so I won't list them again. This includes optional pointers, slices, etc.
+
+Finally commands are categorized to generate the most ergonomic version possible.
+
+Commands that return a single value
+--------------------
+
+This is detected by finding a non-const pointer argument that doesn't have a `length` attribute. See the above example.
+
+Commands that return an array or take in arrays of known length
+----------------
+
+This is detected by finding pointer arguments that have a `length` attribute pointing to an integer value.
+
+- If the length is an argument we replace the length and pointer arguments by a slice. The length is that of the slice.
+
+```rust
+pub fn wait_for_fences(
+ &self,
+ p_fences: &[VkFence],
+ wait_all: bool,
+ timeout: u64,
+) -> Result<VkResult, VkResult>
+{
+ let fence_count = p_fences.len() as _;
+
+ // ...
+
+ // VkResult vkWaitForFences(
+ // VkDevice device,
+ // uint32_t fenceCount,
+ // const VkFence* pFences,
+ // VkBool32 waitAll,
+ // uint64_t timeout);
+}
+```
+
+- If the length is a field in a structure given as argument, we replace the pointer argument by a slice and assert that its length is correct.
+
+```rust
+pub fn allocate_command_buffers(
+ &self,
+ p_allocate_info: &VkCommandBufferAllocateInfo,
+ p_command_buffers: &mut [VkCommandBuffer],
+) -> Result<VkResult, VkResult>
+{
+ assert!(p_allocate_info.command_buffer_count as usize == p_command_buffers.len());
+
+ // ...
+
+ // VkResult vkAllocateCommandBuffers(
+ // VkDevice device,
+ // const VkCommandBufferAllocateInfo* pAllocateInfo,
+ // VkCommandBuffer* pCommandBuffers);
+}
+```
+
+- If multiple pointer arguments point to the same length argument, one of the slices set the length and we assert the length of the following ones.
+
+```rust
+pub fn cmd_bind_vertex_buffers(
+ &self,
+ command_buffer: VkCommandBuffer,
+ first_binding: u32,
+ p_buffers: &[VkBuffer],
+ p_offsets: &[VkDeviceSize],
+)
+{
+ let binding_count = p_buffers.len() as _;
+ assert!(binding_count as usize == p_offsets.len());
+
+ // ...
+
+ // void vkCmdBindVertexBuffers(
+ // VkCommandBuffer commandBuffer,
+ // uint32_t firstBinding,
+ // uint32_t bindingCount,
+ // const VkBuffer* pBuffers,
+ // const VkDeviceSize* pOffsets);
+}
+```
+
+Commands that return an array of unknown length
+--------------
+
+This is detected by finding a non-const pointer argument with a `length` attribute pointing to a non-const integer pointer. In this case we generate two methods: one that sets the data pointer to null and return only the returned length, and one that take in a slice and return values through it.
+
+```rust
+// VkResult vkEnumerateDeviceLayerProperties(
+// VkPhysicalDevice physicalDevice,
+// uint32_t* pPropertyCount,
+// VkLayerProperties* pProperties);
+
+pub fn enumerate_device_layer_properties_count(
+ &self,
+ physical_device: VkPhysicalDevice,
+) -> Result<(VkResult, usize), VkResult>
+{
+ // Here we set pProperties to null and return the value of pPropertyCount
+
+ // ...
+}
+
+pub fn enumerate_device_layer_properties(
+ &self,
+ physical_device: VkPhysicalDevice,
+ p_properties: &mut [VkLayerProperties],
+) -> Result<VkResult, VkResult>
+{
+ let mut p_property_count = p_properties.len() as _;
+
+ // ...
+}
+```
+
+Any command that doesn't fall into any of those categories is generated without any special ergonomic apart from the one listed at the beginning of this part.
+
+Wrap up
+==============
+
+And that's it for the bindings generator. It was definitely a challenge to be able to handle everything the Vulkan API throws at us but in the end it makes is possible to write Vulkan code in a somewhat Rusty way. However I can't really say that the generator code is elegant... Below is an example of initializing Vulkan.
+
+The code for all of this is available in the [VkXml repository](https://github.com/stevenlr/VkXml). You can contact me on [Twitter](https://twitter.com/steven_lr) for any question or remark.
+
+```rust
+let hinstance = unsafe { GetModuleHandleA(0 as _) as HINSTANCE };
+
+let vk_module = unsafe { LoadLibraryA(b"vulkan-1.dll\0".as_ptr()) };
+let vk_entry = vk::EntryPoint::new(|fn_name| unsafe {
+ transmute(GetProcAddress(vk_module, fn_name.as_ptr()))
+});
+
+let instance_extensions = &[
+ VK_EXT_DEBUG_UTILS_EXTENSION_NAME__C.as_ptr(),
+ VK_KHR_SURFACE_EXTENSION_NAME__C.as_ptr(),
+ VK_KHR_WIN32_SURFACE_EXTENSION_NAME__C.as_ptr(),
+];
+
+let layers = &[b"VK_LAYER_LUNARG_standard_validation\0".as_ptr()];
+
+let create_info = VkInstanceCreateInfoBuilder::new()
+ .pp_enabled_extension_names(instance_extensions)
+ .pp_enabled_layer_names(layers);
+
+let vk_instance = vk_entry.create_instance(&create_info, None).unwrap().1;
+let vk_instance = vk::Instance::new(vk_instance, &vk_entry);
+
+let gpu_count = vk_instance.enumerate_physical_devices_count().unwrap().1;
+println!("{} GPU(s)", gpu_count);
+
+let gpus = {
+ let mut gpus = Array::new();
+ gpus.resize(gpu_count, VkPhysicalDevice::null());
+ vk_instance.enumerate_physical_devices(&mut gpus).unwrap();
+ gpus
+};
+
+for (index, gpu) in gpus.iter().enumerate()
+{
+ let prps = vk_instance.get_physical_device_properties(*gpu);
+ let name = unsafe { CStr::from_bytes_null_terminated_unchecked(prps.device_name.as_ptr()) };
+ println!(" {}: {}", index, name.as_str().unwrap());
+}
+
+vk_instance.destroy_instance(None);
+```