vulkano 0.27.0

Safe wrapper for the Vulkan graphics API
Documentation
// Copyright (c) 2021 The Vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use self::spirv_grammar::SpirvGrammar;
use indexmap::IndexMap;
use lazy_static::lazy_static;
use regex::Regex;
use std::{
    collections::HashMap,
    env,
    fmt::Display,
    fs::File,
    io::{BufWriter, Write},
    path::Path,
    process::Command,
};
use vk_parse::{
    EnumSpec, EnumsChild, Extension, ExtensionChild, Feature, InterfaceItem, Registry,
    RegistryChild, SpirvExtOrCap, Type, TypeCodeMarkup, TypeSpec, TypesChild,
};

mod extensions;
mod features;
mod fns;
mod formats;
mod properties;
mod spirv_grammar;
mod spirv_parse;
mod spirv_reqs;

pub fn autogen() {
    let registry = get_vk_registry("vk.xml");
    let vk_data = VkRegistryData::new(&registry);
    let spirv_grammar = get_spirv_grammar("spirv.core.grammar.json");

    extensions::write(&vk_data);
    features::write(&vk_data);
    formats::write(&vk_data);
    fns::write(&vk_data);
    properties::write(&vk_data);
    spirv_parse::write(&spirv_grammar);
    spirv_reqs::write(&vk_data, &spirv_grammar);
}

fn write_file(file: impl AsRef<Path>, source: impl AsRef<str>, content: impl Display) {
    let path = Path::new(&env::var_os("OUT_DIR").unwrap()).join(file.as_ref());
    let mut writer = BufWriter::new(File::create(&path).unwrap());

    write!(
        writer,
        "\
        // This file is auto-generated by vulkano autogen from {}.\n\
        // It should not be edited manually. Changes should be made by editing autogen.\n\
        \n\n{}",
        source.as_ref(),
        content.to_string(),
    )
    .unwrap();

    std::mem::drop(writer); // Ensure that the file is fully written
    Command::new("rustfmt").arg(&path).status().ok();
}

fn get_vk_registry<P: AsRef<Path> + ?Sized>(path: &P) -> Registry {
    let (registry, errors) = vk_parse::parse_file(path.as_ref()).unwrap();

    if !errors.is_empty() {
        eprintln!("The following errors were found while parsing the file:");

        for error in errors {
            eprintln!("{:?}", error);
        }
    }

    registry
}

pub struct VkRegistryData<'r> {
    pub header_version: u16,
    pub extensions: IndexMap<&'r str, &'r Extension>,
    pub features: IndexMap<&'r str, &'r Feature>,
    pub formats: Vec<&'r str>,
    pub spirv_capabilities: Vec<&'r SpirvExtOrCap>,
    pub spirv_extensions: Vec<&'r SpirvExtOrCap>,
    pub types: HashMap<&'r str, (&'r Type, Vec<&'r str>)>,
}

impl<'r> VkRegistryData<'r> {
    fn new(registry: &'r Registry) -> Self {
        let aliases = Self::get_aliases(&registry);
        let extensions = Self::get_extensions(&registry);
        let features = Self::get_features(&registry);
        let formats = Self::get_formats(
            &registry,
            features.values().map(|x| x.children.iter()).flatten(),
            extensions.values().map(|x| x.children.iter()).flatten(),
        );
        let spirv_capabilities = Self::get_spirv_capabilities(registry);
        let spirv_extensions = Self::get_spirv_extensions(registry);
        let types = Self::get_types(&registry, &aliases, &features, &extensions);
        let header_version = Self::get_header_version(&registry);

        VkRegistryData {
            header_version,
            extensions,
            features,
            formats,
            spirv_capabilities,
            spirv_extensions,
            types,
        }
    }

    fn get_header_version(registry: &Registry) -> u16 {
        registry.0.iter()
            .find_map(|child| -> Option<u16> {
                if let RegistryChild::Types(types) = child {
                    return types.children.iter().find_map(|ty| -> Option<u16> {
                        if let TypesChild::Type(ty) = ty {
                            if let TypeSpec::Code(code) = &ty.spec {
                                if code.markup.iter().any(|mkup| matches!(mkup, TypeCodeMarkup::Name(name) if name == "VK_HEADER_VERSION")) {
                                    return Some(code.code.rsplit_once(' ').unwrap().1.parse().unwrap());
                                }
                            }
                        }
                        None
                    });
                }
                None
            })
            .unwrap()
    }

    fn get_aliases(registry: &Registry) -> HashMap<&str, &str> {
        registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::Types(types) = child {
                    return Some(types.children.iter().filter_map(|ty| {
                        if let TypesChild::Type(ty) = ty {
                            if let Some(alias) = ty.alias.as_ref().map(|s| s.as_str()) {
                                return Some((ty.name.as_ref().unwrap().as_str(), alias));
                            }
                        }
                        None
                    }));
                }
                None
            })
            .flatten()
            .collect()
    }

    fn get_extensions(registry: &Registry) -> IndexMap<&str, &Extension> {
        let iter = registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::Extensions(ext) = child {
                    return Some(ext.children.iter().filter_map(|ext| {
                        if ext.supported.as_ref().map(|s| s.as_str()) == Some("vulkan")
                            && ext.obsoletedby.is_none()
                        {
                            return Some(ext);
                        }
                        None
                    }));
                }
                None
            })
            .flatten();

        let extensions: HashMap<&str, &Extension> =
            iter.clone().map(|ext| (ext.name.as_str(), ext)).collect();
        let mut names: Vec<_> = iter.map(|ext| ext.name.as_str()).collect();
        names.sort_unstable_by_key(|name| {
            if name.starts_with("VK_KHR_") {
                (0, name.to_owned())
            } else if name.starts_with("VK_EXT_") {
                (1, name.to_owned())
            } else {
                (2, name.to_owned())
            }
        });

        names.iter().map(|&name| (name, extensions[name])).collect()
    }

    fn get_features(registry: &Registry) -> IndexMap<&str, &Feature> {
        registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::Feature(feat) = child {
                    return Some((feat.name.as_str(), feat));
                }

                None
            })
            .collect()
    }

    fn get_formats<'a>(
        registry: &'a Registry,
        features: impl IntoIterator<Item = &'a ExtensionChild>,
        extensions: impl IntoIterator<Item = &'a ExtensionChild>,
    ) -> Vec<&'a str> {
        registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::Enums(enums) = child {
                    if enums.name.as_ref().map(|s| s.as_str()) == Some("VkFormat") {
                        return Some(enums.children.iter().filter_map(|en| {
                            if let EnumsChild::Enum(en) = en {
                                if en.name != "VK_FORMAT_UNDEFINED" {
                                    return Some(en.name.as_str());
                                }
                            }
                            None
                        }));
                    }
                }
                None
            })
            .flatten()
            .chain(
                features
                    .into_iter()
                    .chain(extensions.into_iter())
                    .filter_map(|child| {
                        if let ExtensionChild::Require { items, .. } = child {
                            return Some(items.iter().filter_map(|item| {
                                if let InterfaceItem::Enum(en) = item {
                                    if let EnumSpec::Offset { extends, .. } = &en.spec {
                                        if extends == "VkFormat" {
                                            return Some(en.name.as_str());
                                        }
                                    }
                                }
                                None
                            }));
                        }
                        None
                    })
                    .flatten(),
            )
            .collect()
    }

    fn get_spirv_capabilities<'a>(registry: &'a Registry) -> Vec<&'a SpirvExtOrCap> {
        registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::SpirvCapabilities(capabilities) = child {
                    return Some(capabilities.children.iter());
                }
                None
            })
            .flatten()
            .collect()
    }

    fn get_spirv_extensions<'a>(registry: &'a Registry) -> Vec<&'a SpirvExtOrCap> {
        registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::SpirvExtensions(extensions) = child {
                    return Some(extensions.children.iter());
                }
                None
            })
            .flatten()
            .collect()
    }

    fn get_types<'a>(
        registry: &'a Registry,
        aliases: &HashMap<&'a str, &'a str>,
        features: &IndexMap<&'a str, &'a Feature>,
        extensions: &IndexMap<&'a str, &'a Extension>,
    ) -> HashMap<&'a str, (&'a Type, Vec<&'a str>)> {
        let mut types: HashMap<&str, (&Type, Vec<&str>)> = registry
            .0
            .iter()
            .filter_map(|child| {
                if let RegistryChild::Types(types) = child {
                    return Some(types.children.iter().filter_map(|ty| {
                        if let TypesChild::Type(ty) = ty {
                            if ty.alias.is_none() {
                                return ty.name.as_ref().map(|name| (name.as_str(), (ty, vec![])));
                            }
                        }
                        None
                    }));
                }
                None
            })
            .flatten()
            .collect();

        features
            .iter()
            .map(|(name, feature)| (name, &feature.children))
            .chain(extensions.iter().map(|(name, ext)| (name, &ext.children)))
            .for_each(|(provided_by, children)| {
                children
                    .iter()
                    .filter_map(|child| {
                        if let ExtensionChild::Require { items, .. } = child {
                            return Some(items.iter());
                        }
                        None
                    })
                    .flatten()
                    .filter_map(|item| {
                        if let InterfaceItem::Type { name, .. } = item {
                            return Some(name.as_str());
                        }
                        None
                    })
                    .for_each(|item_name| {
                        let item_name = aliases.get(item_name).unwrap_or(&item_name);
                        if let Some(ty) = types.get_mut(item_name) {
                            if !ty.1.contains(provided_by) {
                                ty.1.push(provided_by);
                            }
                        }
                    });
            });

        types
            .into_iter()
            .filter(|(_key, val)| !val.1.is_empty())
            .collect()
    }
}

pub fn get_spirv_grammar<P: AsRef<Path> + ?Sized>(path: &P) -> SpirvGrammar {
    let mut grammar = SpirvGrammar::new(path);

    // Remove duplicate opcodes and enum values, preferring "more official" suffixes
    grammar
        .instructions
        .sort_by_key(|instruction| (instruction.opcode, suffix_key(&instruction.opname)));
    grammar
        .instructions
        .dedup_by_key(|instruction| instruction.opcode);

    grammar
        .operand_kinds
        .iter_mut()
        .filter(|operand_kind| operand_kind.category == "BitEnum")
        .for_each(|operand_kind| {
            operand_kind.enumerants.sort_by_key(|enumerant| {
                let value = enumerant
                    .value
                    .as_str()
                    .unwrap()
                    .strip_prefix("0x")
                    .unwrap();
                (
                    u32::from_str_radix(value, 16).unwrap(),
                    suffix_key(&enumerant.enumerant),
                )
            });
        });

    grammar
        .operand_kinds
        .iter_mut()
        .filter(|operand_kind| operand_kind.category == "ValueEnum")
        .for_each(|operand_kind| {
            operand_kind.enumerants.sort_by_key(|enumerant| {
                (enumerant.value.as_u64(), suffix_key(&enumerant.enumerant))
            });
        });

    grammar
}

lazy_static! {
    static ref VENDOR_SUFFIXES: Regex = Regex::new(r"(?:AMD|GOOGLE|INTEL|NV)$").unwrap();
}

fn suffix_key(name: &str) -> u32 {
    if VENDOR_SUFFIXES.is_match(name) {
        3
    } else if name.ends_with("EXT") {
        2
    } else if name.ends_with("KHR") {
        1
    } else {
        0
    }
}