1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
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);
```
|