1 // Copyright (c) 2021 The Vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 use super::{write_file, IndexMap, VkRegistryData};
11 use heck::ToSnakeCase;
12 use proc_macro2::{Ident, Literal, TokenStream};
13 use quote::{format_ident, quote};
14 use std::fmt::Write as _;
15 use vk_parse::Extension;
16 
17 // This is not included in vk.xml, so it's added here manually
required_if_supported(name: &str) -> bool18 fn required_if_supported(name: &str) -> bool {
19     #[allow(clippy::match_like_matches_macro)]
20     match name {
21         "VK_KHR_portability_subset" => true,
22         _ => false,
23     }
24 }
25 
conflicts_extensions(name: &str) -> &'static [&'static str]26 fn conflicts_extensions(name: &str) -> &'static [&'static str] {
27     match name {
28         "VK_KHR_buffer_device_address" => &["VK_EXT_buffer_device_address"],
29         "VK_EXT_buffer_device_address" => &["VK_KHR_buffer_device_address"],
30         _ => &[],
31     }
32 }
33 
write(vk_data: &VkRegistryData)34 pub fn write(vk_data: &VkRegistryData) {
35     write_device_extensions(vk_data);
36     write_instance_extensions(vk_data);
37 }
38 
39 #[derive(Clone, Debug)]
40 struct ExtensionsMember {
41     name: Ident,
42     doc: String,
43     raw: String,
44     required_if_supported: bool,
45     requires: Vec<RequiresOneOf>,
46     conflicts_device_extensions: Vec<Ident>,
47     status: Option<ExtensionStatus>,
48 }
49 
50 #[derive(Clone, Debug, Default, PartialEq, Eq)]
51 pub struct RequiresOneOf {
52     pub api_version: Option<(String, String)>,
53     pub device_extensions: Vec<Ident>,
54     pub instance_extensions: Vec<Ident>,
55 }
56 
57 #[derive(Clone, Debug)]
58 enum Replacement {
59     Core((String, String)),
60     DeviceExtension(Ident),
61     InstanceExtension(Ident),
62 }
63 
64 #[derive(Clone, Debug)]
65 enum ExtensionStatus {
66     Promoted(Replacement),
67     Deprecated(Option<Replacement>),
68 }
69 
write_device_extensions(vk_data: &VkRegistryData)70 fn write_device_extensions(vk_data: &VkRegistryData) {
71     write_file(
72         "device_extensions.rs",
73         format!(
74             "vk.xml header version {}.{}.{}",
75             vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
76         ),
77         device_extensions_output(&extensions_members("device", &vk_data.extensions)),
78     );
79 }
80 
write_instance_extensions(vk_data: &VkRegistryData)81 fn write_instance_extensions(vk_data: &VkRegistryData) {
82     write_file(
83         "instance_extensions.rs",
84         format!(
85             "vk.xml header version {}.{}.{}",
86             vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
87         ),
88         instance_extensions_output(&extensions_members("instance", &vk_data.extensions)),
89     );
90 }
91 
device_extensions_output(members: &[ExtensionsMember]) -> TokenStream92 fn device_extensions_output(members: &[ExtensionsMember]) -> TokenStream {
93     let common = extensions_common_output(format_ident!("DeviceExtensions"), members);
94 
95     let check_requirements_items = members.iter().map(|ExtensionsMember {
96         name,
97         requires,
98         conflicts_device_extensions,
99         required_if_supported,
100         ..
101     }| {
102         let name_string = name.to_string();
103 
104         let requires_items = requires.iter().map(|require| {
105             let require_items = require.api_version.iter().map(|version| {
106                 let version = format_ident!("V{}_{}", version.0, version.1);
107                 quote! { api_version >= crate::Version::#version }
108             }).chain(require.instance_extensions.iter().map(|ext| {
109                 quote! { instance_extensions.#ext }
110             })).chain(require.device_extensions.iter().map(|ext| {
111                 quote! { device_extensions.#ext }
112             }));
113 
114             let api_version_items = require.api_version.as_ref().map(|version| {
115                 let version = format_ident!("V{}_{}", version.0, version.1);
116                 quote! { Some(crate::Version::#version) }
117             }).unwrap_or_else(|| quote!{ None });
118             let device_extensions_items = require.device_extensions.iter().map(|ext| ext.to_string());
119             let instance_extensions_items = require.instance_extensions.iter().map(|ext| ext.to_string());
120 
121             quote! {
122                 if !(#(#require_items)||*) {
123                     return Err(crate::device::ExtensionRestrictionError {
124                         extension: #name_string,
125                         restriction: crate::device::ExtensionRestriction::Requires(crate::RequiresOneOf {
126                             api_version: #api_version_items,
127                             device_extensions: &[#(#device_extensions_items),*],
128                             instance_extensions: &[#(#instance_extensions_items),*],
129                             ..Default::default()
130                         }),
131                     })
132                 }
133             }
134         });
135         let conflicts_device_extensions_items = conflicts_device_extensions.iter().map(|extension| {
136             let string = extension.to_string();
137             quote! {
138                 if self.#extension {
139                     return Err(crate::device::ExtensionRestrictionError {
140                         extension: #name_string,
141                         restriction: crate::device::ExtensionRestriction::ConflictsDeviceExtension(#string),
142                     });
143                 }
144             }
145         });
146         let required_if_supported = if *required_if_supported {
147             quote! {
148                 if supported.#name {
149                     return Err(crate::device::ExtensionRestrictionError {
150                         extension: #name_string,
151                         restriction: crate::device::ExtensionRestriction::RequiredIfSupported,
152                     });
153                 }
154             }
155         } else {
156             quote! {}
157         };
158 
159         quote! {
160             if self.#name {
161                 if !supported.#name {
162                     return Err(crate::device::ExtensionRestrictionError {
163                         extension: #name_string,
164                         restriction: crate::device::ExtensionRestriction::NotSupported,
165                     });
166                 }
167 
168                 #(#requires_items)*
169                 #(#conflicts_device_extensions_items)*
170             } else {
171                 #required_if_supported
172             }
173         }
174     });
175 
176     quote! {
177         #common
178 
179         impl DeviceExtensions {
180             /// Checks enabled extensions against the device version, instance extensions and each other.
181             pub(super) fn check_requirements(
182                 &self,
183                 supported: &DeviceExtensions,
184                 api_version: crate::Version,
185                 instance_extensions: &crate::instance::InstanceExtensions,
186             ) -> Result<(), crate::device::ExtensionRestrictionError> {
187                 let device_extensions = self;
188                 #(#check_requirements_items)*
189                 Ok(())
190             }
191         }
192     }
193 }
194 
instance_extensions_output(members: &[ExtensionsMember]) -> TokenStream195 fn instance_extensions_output(members: &[ExtensionsMember]) -> TokenStream {
196     let common = extensions_common_output(format_ident!("InstanceExtensions"), members);
197 
198     let check_requirements_items =
199         members
200             .iter()
201             .map(|ExtensionsMember { name, requires, .. }| {
202                 let name_string = name.to_string();
203 
204                 let requires_items = requires.iter().map(|require| {
205                     let require_items = require
206                         .api_version
207                         .iter()
208                         .map(|version| {
209                             let version = format_ident!("V{}_{}", version.0, version.1);
210                             quote! { api_version >= crate::Version::#version }
211                         })
212                         .chain(require.instance_extensions.iter().map(|ext| {
213                             quote! { instance_extensions.#ext }
214                         }))
215                         .chain(require.device_extensions.iter().map(|ext| {
216                             quote! { device_extensions.#ext }
217                         }));
218 
219                     let api_version_items = require
220                         .api_version
221                         .as_ref()
222                         .map(|version| {
223                             let version = format_ident!("V{}_{}", version.0, version.1);
224                             quote! { Some(crate::Version::#version) }
225                         })
226                         .unwrap_or_else(|| quote! { None });
227                     let device_extensions_items =
228                         require.device_extensions.iter().map(|ext| ext.to_string());
229                     let instance_extensions_items = require
230                         .instance_extensions
231                         .iter()
232                         .map(|ext| ext.to_string());
233 
234                     quote! {
235                         if !(#(#require_items)||*) {
236                             return Err(crate::instance::ExtensionRestrictionError {
237                                 extension: #name_string,
238                                 restriction: crate::instance::ExtensionRestriction::Requires(crate::RequiresOneOf {
239                                     api_version: #api_version_items,
240                                     device_extensions: &[#(#device_extensions_items),*],
241                                     instance_extensions: &[#(#instance_extensions_items),*],
242                                     ..Default::default()
243                                 }),
244                             })
245                         }
246                     }
247                 });
248 
249                 quote! {
250                     if self.#name {
251                         if !supported.#name {
252                             return Err(crate::instance::ExtensionRestrictionError {
253                                 extension: #name_string,
254                                 restriction: crate::instance::ExtensionRestriction::NotSupported,
255                             });
256                         }
257 
258                         #(#requires_items)*
259                     }
260                 }
261             });
262 
263     quote! {
264         #common
265 
266         impl InstanceExtensions {
267             /// Checks enabled extensions against the instance version and each other.
268             pub(super) fn check_requirements(
269                 &self,
270                 supported: &InstanceExtensions,
271                 api_version: crate::Version,
272             ) -> Result<(), crate::instance::ExtensionRestrictionError> {
273                 let instance_extensions = self;
274                 #(#check_requirements_items)*
275                 Ok(())
276             }
277         }
278     }
279 }
280 
extensions_common_output(struct_name: Ident, members: &[ExtensionsMember]) -> TokenStream281 fn extensions_common_output(struct_name: Ident, members: &[ExtensionsMember]) -> TokenStream {
282     let struct_items = members.iter().map(|ExtensionsMember { name, doc, .. }| {
283         quote! {
284             #[doc = #doc]
285             pub #name: bool,
286         }
287     });
288 
289     let empty_items = members.iter().map(|ExtensionsMember { name, .. }| {
290         quote! {
291             #name: false,
292         }
293     });
294 
295     let intersects_items = members.iter().map(|ExtensionsMember { name, .. }| {
296         quote! {
297             (self.#name && other.#name)
298         }
299     });
300 
301     let contains_items = members.iter().map(|ExtensionsMember { name, .. }| {
302         quote! {
303             (self.#name || !other.#name)
304         }
305     });
306 
307     let union_items = members.iter().map(|ExtensionsMember { name, .. }| {
308         quote! {
309             #name: self.#name || other.#name,
310         }
311     });
312 
313     let intersection_items = members.iter().map(|ExtensionsMember { name, .. }| {
314         quote! {
315             #name: self.#name && other.#name,
316         }
317     });
318 
319     let difference_items = members.iter().map(|ExtensionsMember { name, .. }| {
320         quote! {
321             #name: self.#name && !other.#name,
322         }
323     });
324 
325     let symmetric_difference_items = members.iter().map(|ExtensionsMember { name, .. }| {
326         quote! {
327             #name: self.#name ^ other.#name,
328         }
329     });
330 
331     let debug_items = members.iter().map(|ExtensionsMember { name, raw, .. }| {
332         quote! {
333             if self.#name {
334                 if !first { write!(f, ", ")? }
335                 else { first = false; }
336                 f.write_str(#raw)?;
337             }
338         }
339     });
340 
341     let arr_items = members.iter().map(|ExtensionsMember { name, raw, .. }| {
342         quote! {
343             (#raw, self.#name),
344         }
345     });
346     let arr_len = members.len();
347 
348     let from_str_for_extensions_items =
349         members.iter().map(|ExtensionsMember { name, raw, .. }| {
350             let raw = Literal::string(raw);
351             quote! {
352                 #raw => { extensions.#name = true; }
353             }
354         });
355 
356     let from_extensions_for_vec_cstring_items =
357         members.iter().map(|ExtensionsMember { name, raw, .. }| {
358             quote! {
359                 if x.#name { data.push(std::ffi::CString::new(#raw).unwrap()); }
360             }
361         });
362 
363     quote! {
364         /// List of extensions that are enabled or available.
365         #[derive(Copy, Clone, PartialEq, Eq)]
366         pub struct #struct_name {
367             #(#struct_items)*
368 
369             pub _ne: crate::NonExhaustive,
370         }
371 
372         impl Default for #struct_name {
373             #[inline]
374             fn default() -> Self {
375                 Self::empty()
376             }
377         }
378 
379         impl #struct_name {
380             /// Returns an `Extensions` object with none of the members set.
381             #[inline]
382             pub const fn empty() -> Self {
383                 Self {
384                     #(#empty_items)*
385                     _ne: crate::NonExhaustive(()),
386                 }
387             }
388 
389             /// Returns an `Extensions` object with none of the members set.
390             #[deprecated(since = "0.31.0", note = "Use `empty` instead.")]
391             #[inline]
392             pub const fn none() -> Self {
393                 Self::empty()
394             }
395 
396             /// Returns whether any members are set in both `self` and `other`.
397             #[inline]
398             pub const fn intersects(&self, other: &Self) -> bool {
399                 #(#intersects_items)||*
400             }
401 
402             /// Returns whether all members in `other` are set in `self`.
403             #[inline]
404             pub const fn contains(&self, other: &Self) -> bool {
405                 #(#contains_items)&&*
406             }
407 
408             /// Returns whether all members in `other` are set in `self`.
409             #[deprecated(since = "0.31.0", note = "Use `contains` instead.")]
410             #[inline]
411             pub const fn is_superset_of(&self, other: &Self) -> bool {
412                 self.contains(other)
413             }
414 
415             /// Returns the union of `self` and `other`.
416             #[inline]
417             pub const fn union(&self, other: &Self) -> Self {
418                 Self {
419                     #(#union_items)*
420                     _ne: crate::NonExhaustive(()),
421                 }
422             }
423 
424             /// Returns the intersection of `self` and `other`.
425             #[inline]
426             pub const fn intersection(&self, other: &Self) -> Self {
427                 Self {
428                     #(#intersection_items)*
429                     _ne: crate::NonExhaustive(()),
430                 }
431             }
432 
433             /// Returns `self` without the members set in `other`.
434             #[inline]
435             pub const fn difference(&self, other: &Self) -> Self {
436                 Self {
437                     #(#difference_items)*
438                     _ne: crate::NonExhaustive(()),
439                 }
440             }
441 
442             /// Returns the members set in `self` or `other`, but not both.
443             #[inline]
444             pub const fn symmetric_difference(&self, other: &Self) -> Self {
445                 Self {
446                     #(#symmetric_difference_items)*
447                     _ne: crate::NonExhaustive(()),
448                 }
449             }
450         }
451 
452         impl std::ops::BitAnd for #struct_name {
453             type Output = #struct_name;
454 
455             #[inline]
456             fn bitand(self, rhs: Self) -> Self::Output {
457                 self.union(&rhs)
458             }
459         }
460 
461         impl std::ops::BitAndAssign for #struct_name {
462             #[inline]
463             fn bitand_assign(&mut self, rhs: Self) {
464                 *self = self.union(&rhs);
465             }
466         }
467 
468         impl std::ops::BitOr for #struct_name {
469             type Output = #struct_name;
470 
471             #[inline]
472             fn bitor(self, rhs: Self) -> Self::Output {
473                 self.intersection(&rhs)
474             }
475         }
476 
477         impl std::ops::BitOrAssign for #struct_name {
478             #[inline]
479             fn bitor_assign(&mut self, rhs: Self) {
480                 *self = self.intersection(&rhs);
481             }
482         }
483 
484         impl std::ops::BitXor for #struct_name {
485             type Output = #struct_name;
486 
487             #[inline]
488             fn bitxor(self, rhs: Self) -> Self::Output {
489                 self.symmetric_difference(&rhs)
490             }
491         }
492 
493         impl std::ops::BitXorAssign for #struct_name {
494             #[inline]
495             fn bitxor_assign(&mut self, rhs: Self) {
496                 *self = self.symmetric_difference(&rhs);
497             }
498         }
499 
500         impl std::ops::Sub for #struct_name {
501             type Output = #struct_name;
502 
503             #[inline]
504             fn sub(self, rhs: Self) -> Self::Output {
505                 self.difference(&rhs)
506             }
507         }
508 
509         impl std::ops::SubAssign for #struct_name {
510             #[inline]
511             fn sub_assign(&mut self, rhs: Self) {
512                 *self = self.difference(&rhs);
513             }
514         }
515 
516         impl std::fmt::Debug for #struct_name {
517             #[allow(unused_assignments)]
518             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
519                 write!(f, "[")?;
520 
521                 let mut first = true;
522                 #(#debug_items)*
523 
524                 write!(f, "]")
525             }
526         }
527 
528         impl<'a> FromIterator<&'a str> for #struct_name {
529             fn from_iter<I>(iter: I) -> Self
530                 where I: IntoIterator<Item = &'a str>
531             {
532                 let mut extensions = Self::empty();
533                 for name in iter {
534                     match name {
535                         #(#from_str_for_extensions_items)*
536                         _ => (),
537                     }
538                 }
539                 extensions
540             }
541         }
542 
543         impl<'a> From<&'a #struct_name> for Vec<std::ffi::CString> {
544             fn from(x: &'a #struct_name) -> Self {
545                 let mut data = Self::new();
546                 #(#from_extensions_for_vec_cstring_items)*
547                 data
548             }
549         }
550 
551         impl IntoIterator for #struct_name {
552             type Item = (&'static str, bool);
553             type IntoIter = std::array::IntoIter<Self::Item, #arr_len>;
554 
555             #[inline]
556             fn into_iter(self) -> Self::IntoIter {
557                 [#(#arr_items)*].into_iter()
558             }
559         }
560     }
561 }
562 
extensions_members(ty: &str, extensions: &IndexMap<&str, &Extension>) -> Vec<ExtensionsMember>563 fn extensions_members(ty: &str, extensions: &IndexMap<&str, &Extension>) -> Vec<ExtensionsMember> {
564     extensions
565         .values()
566         .filter(|ext| ext.ext_type.as_ref().unwrap() == ty)
567         .map(|ext| {
568             let raw = ext.name.to_owned();
569             let name = raw.strip_prefix("VK_").unwrap().to_snake_case();
570 
571             let mut requires = Vec::new();
572 
573             if let Some(core) = ext.requires_core.as_ref() {
574                 let (major, minor) = core.split_once('.').unwrap();
575                 requires.push(RequiresOneOf {
576                     api_version: Some((major.to_owned(), minor.to_owned())),
577                     ..Default::default()
578                 });
579             }
580 
581             if let Some(req) = ext.requires.as_ref() {
582                 requires.extend(req.split(',').map(|mut vk_name| {
583                     let mut dependencies = RequiresOneOf::default();
584 
585                     loop {
586                         if let Some(version) = vk_name.strip_prefix("VK_VERSION_") {
587                             let (major, minor) = version.split_once('_').unwrap();
588                             dependencies.api_version = Some((major.to_owned(), minor.to_owned()));
589                             break;
590                         } else {
591                             let ident = format_ident!(
592                                 "{}",
593                                 vk_name.strip_prefix("VK_").unwrap().to_snake_case()
594                             );
595                             let extension = extensions[vk_name];
596 
597                             match extension.ext_type.as_deref() {
598                                 Some("device") => &mut dependencies.device_extensions,
599                                 Some("instance") => &mut dependencies.instance_extensions,
600                                 _ => unreachable!(),
601                             }
602                             .insert(0, ident);
603 
604                             if let Some(promotedto) = extension.promotedto.as_ref() {
605                                 vk_name = promotedto.as_str();
606                             } else {
607                                 break;
608                             }
609                         }
610                     }
611 
612                     dependencies
613                 }));
614             }
615 
616             let conflicts_extensions = conflicts_extensions(&ext.name);
617 
618             let mut member = ExtensionsMember {
619                 name: format_ident!("{}", name),
620                 doc: String::new(),
621                 raw,
622                 required_if_supported: required_if_supported(ext.name.as_str()),
623                 requires,
624                 conflicts_device_extensions: conflicts_extensions
625                     .iter()
626                     .filter(|&&vk_name| extensions[vk_name].ext_type.as_ref().unwrap() == "device")
627                     .map(|vk_name| {
628                         format_ident!("{}", vk_name.strip_prefix("VK_").unwrap().to_snake_case())
629                     })
630                     .collect(),
631                 status: ext
632                     .promotedto
633                     .as_deref()
634                     .and_then(|pr| {
635                         if let Some(version) = pr.strip_prefix("VK_VERSION_") {
636                             let (major, minor) = version.split_once('_').unwrap();
637                             Some(ExtensionStatus::Promoted(Replacement::Core((
638                                 major.to_owned(),
639                                 minor.to_owned(),
640                             ))))
641                         } else {
642                             let member = pr.strip_prefix("VK_").unwrap().to_snake_case();
643                             match extensions[pr].ext_type.as_ref().unwrap().as_str() {
644                                 "device" => Some(ExtensionStatus::Promoted(
645                                     Replacement::DeviceExtension(format_ident!("{}", member)),
646                                 )),
647                                 "instance" => Some(ExtensionStatus::Promoted(
648                                     Replacement::InstanceExtension(format_ident!("{}", member)),
649                                 )),
650                                 _ => unreachable!(),
651                             }
652                         }
653                     })
654                     .or_else(|| {
655                         ext.deprecatedby.as_deref().and_then(|depr| {
656                             if depr.is_empty() {
657                                 Some(ExtensionStatus::Deprecated(None))
658                             } else if let Some(version) = depr.strip_prefix("VK_VERSION_") {
659                                 let (major, minor) = version.split_once('_').unwrap();
660                                 Some(ExtensionStatus::Deprecated(Some(Replacement::Core((
661                                     major.parse().unwrap(),
662                                     minor.parse().unwrap(),
663                                 )))))
664                             } else {
665                                 let member = depr.strip_prefix("VK_").unwrap().to_snake_case();
666                                 match extensions[depr].ext_type.as_ref().unwrap().as_str() {
667                                     "device" => Some(ExtensionStatus::Deprecated(Some(
668                                         Replacement::DeviceExtension(format_ident!("{}", member)),
669                                     ))),
670                                     "instance" => Some(ExtensionStatus::Deprecated(Some(
671                                         Replacement::InstanceExtension(format_ident!("{}", member)),
672                                     ))),
673                                     _ => unreachable!(),
674                                 }
675                             }
676                         })
677                     }),
678             };
679             make_doc(&mut member);
680             member
681         })
682         .collect()
683 }
684 
make_doc(ext: &mut ExtensionsMember)685 fn make_doc(ext: &mut ExtensionsMember) {
686     let writer = &mut ext.doc;
687     write!(writer, "- [Vulkan documentation](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/{}.html)", ext.raw).unwrap();
688 
689     if ext.required_if_supported {
690         write!(
691             writer,
692             "\n- Must be enabled if it is supported by the physical device",
693         )
694         .unwrap();
695     }
696 
697     if let Some(status) = ext.status.as_ref() {
698         match status {
699             ExtensionStatus::Promoted(replacement) => {
700                 write!(writer, "\n- Promoted to ",).unwrap();
701 
702                 match replacement {
703                     Replacement::Core(version) => {
704                         write!(writer, "Vulkan {}.{}", version.0, version.1).unwrap();
705                     }
706                     Replacement::DeviceExtension(ext) => {
707                         write!(writer, "[`{}`](crate::device::DeviceExtensions::{0})", ext)
708                             .unwrap();
709                     }
710                     Replacement::InstanceExtension(ext) => {
711                         write!(
712                             writer,
713                             "[`{}`](crate::instance::InstanceExtensions::{0})",
714                             ext
715                         )
716                         .unwrap();
717                     }
718                 }
719             }
720             ExtensionStatus::Deprecated(replacement) => {
721                 write!(writer, "\n- Deprecated ",).unwrap();
722 
723                 match replacement {
724                     Some(Replacement::Core(version)) => {
725                         write!(writer, "by Vulkan {}.{}", version.0, version.1).unwrap();
726                     }
727                     Some(Replacement::DeviceExtension(ext)) => {
728                         write!(
729                             writer,
730                             "by [`{}`](crate::device::DeviceExtensions::{0})",
731                             ext
732                         )
733                         .unwrap();
734                     }
735                     Some(Replacement::InstanceExtension(ext)) => {
736                         write!(
737                             writer,
738                             "by [`{}`](crate::instance::InstanceExtensions::{0})",
739                             ext
740                         )
741                         .unwrap();
742                     }
743                     None => {
744                         write!(writer, "without a replacement").unwrap();
745                     }
746                 }
747             }
748         }
749     }
750 
751     if !ext.requires.is_empty() {
752         write!(writer, "\n- Requires:").unwrap();
753     }
754 
755     for require in &ext.requires {
756         let mut line = Vec::new();
757 
758         if let Some((major, minor)) = require.api_version.as_ref() {
759             line.push(format!("Vulkan API version {}.{}", major, minor));
760         }
761 
762         line.extend(require.device_extensions.iter().map(|ext| {
763             format!(
764                 "device extension [`{}`](crate::device::DeviceExtensions::{0})",
765                 ext
766             )
767         }));
768         line.extend(require.instance_extensions.iter().map(|ext| {
769             format!(
770                 "instance extension [`{}`](crate::instance::InstanceExtensions::{0})",
771                 ext
772             )
773         }));
774 
775         if line.len() == 1 {
776             write!(writer, "\n  - {}", line[0]).unwrap();
777         } else {
778             write!(writer, "\n  - One of: {}", line.join(", ")).unwrap();
779         }
780     }
781 
782     if !ext.conflicts_device_extensions.is_empty() {
783         let links: Vec<_> = ext
784             .conflicts_device_extensions
785             .iter()
786             .map(|ext| format!("[`{}`](crate::device::DeviceExtensions::{0})", ext))
787             .collect();
788         write!(
789             writer,
790             "\n- Conflicts with device extension{}: {}",
791             if ext.conflicts_device_extensions.len() > 1 {
792                 "s"
793             } else {
794                 ""
795             },
796             links.join(", ")
797         )
798         .unwrap();
799     }
800 }
801