Theme

alex/volcanic

Automated vulkan bindings

BRANCH main
main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
Download file

root / codegen / src / emit / mod.rs

mod declarations;
mod enums;
mod loader;
#[cfg(test)]
mod tests;
mod types;

use declarations::*;
use enums::*;
use loader::*;
use types::*;

use crate::ctype::Decl;
use crate::registry::{Command, EnumEntry, Registry, TypeDef};
use crate::select;
use indexmap::{IndexMap, IndexSet};

#[derive(Clone)]
struct ResolvedEnum {
    entry: EnumEntry,
    group_name: Option<String>,
    extension_number: Option<u32>,
    condition: select::FeatureExpr,
}

struct IndexedEnum {
    entry: EnumEntry,
    group_name: Option<String>,
    extension_number: Option<u32>,
}

#[derive(Clone)]
struct BitmaskInfo {
    bits_name: Option<String>,
    bits_ty: &'static str,
}

#[derive(Clone)]
struct FlagConstant {
    flags_type: String,
    assoc_name: String,
}

#[derive(Clone)]
struct RustEnumInfo {
    constants: IndexMap<String, EnumConstant>,
}

#[derive(Clone)]
struct EnumConstant {
    value: String,
    condition: select::FeatureExpr,
}

struct EmitModule {
    source_name: String,
    feature_name: String,
    prerequisite: select::FeatureExpr,
    guards: IndexSet<String>,
    types: IndexMap<String, select::FeatureExpr>,
    commands: IndexMap<String, select::FeatureExpr>,
    enums: IndexMap<String, ResolvedEnum>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CommandScope {
    Global,
    Instance,
    Device,
}

#[derive(Default)]
struct Ownership {
    types: IndexMap<String, select::FeatureExpr>,
    consts: IndexMap<String, select::FeatureExpr>,
    enums: IndexMap<String, select::FeatureExpr>,
}

pub struct Generated {
    pub lib_rs: String,
    pub loader_rs: String,
}

pub fn generate(vk: &Registry, video: &Registry) -> Generated {
    let selection = select::select_vulkan(vk, video);
    let enum_index = enum_index(vk, video, &selection);
    let mut modules = emit_modules(&selection, &enum_index);
    expand_module_dependencies(vk, video, &enum_index, &mut modules);
    let mut ctx = Context::new(vk, video, &modules);

    let mut out = generated_header();
    out.push('\n');
    out.push_str("#![allow(non_camel_case_types)]\n");
    out.push_str("#![allow(non_snake_case)]\n");
    out.push_str("#![allow(non_upper_case_globals)]\n");
    out.push_str("#![allow(unused_imports)]\n");
    out.push_str("#![allow(clippy::missing_safety_doc)]\n\n");
    out.push_str("pub use bitflags;\n\n");
    out.push_str("use core::ffi::{c_char, c_int, c_ulong, c_void};\n\n");

    emit_feature_requirement_errors(&mut out, vk, &modules);
    emit_native_types(&mut out, &mut ctx);

    for module in &modules {
        let module_start = out.len();
        section(&mut out, &module.source_name);
        out.push('\n');
        let item_start = out.len();

        emit_feature_extension_guards(&mut out, &mut ctx, &module.guards);
        emit_define_items(&mut out, &mut ctx, vk, &module.types);
        emit_define_items(&mut out, &mut ctx, video, &module.types);
        emit_type_aliases(&mut out, &mut ctx, video, false, &module.types);
        emit_type_aliases(&mut out, &mut ctx, vk, true, &module.types);
        emit_enum_constants(&mut out, &mut ctx, &module.enums);
        emit_records(&mut out, &mut ctx, video, false, &module.types);
        emit_records(&mut out, &mut ctx, vk, true, &module.types);
        emit_func_pointers(&mut out, &mut ctx, video, &module.types);
        emit_func_pointers(&mut out, &mut ctx, vk, &module.types);
        emit_command_pointers(&mut out, &mut ctx, vk, &module.commands);

        if out.len() == item_start {
            out.truncate(module_start);
        }
    }

    out.push_str("#[cfg(feature = \"loader\")]\n");
    out.push_str("pub mod loader;\n");

    let mut loader = generated_header();
    loader.push('\n');
    emit_loader_module(&mut loader, &ctx, vk);

    Generated {
        lib_rs: out,
        loader_rs: loader,
    }
}

struct Context {
    emitted_types: IndexSet<String>,
    emitted_consts: IndexSet<String>,
    emitted_flag_consts: IndexSet<String>,
    const_types: IndexMap<String, String>,
    ownership: Ownership,
    enum_widths: IndexMap<String, &'static str>,
    rust_enums: IndexMap<String, RustEnumInfo>,
    bitmasks: IndexMap<String, BitmaskInfo>,
    bit_enums_to_flags: IndexMap<String, String>,
    flag_constants: IndexMap<String, FlagConstant>,
    bitflag_macro_entries: IndexSet<String>,
    video_type_names: IndexSet<String>,
}

impl Context {
    fn new(vk: &Registry, video: &Registry, modules: &[EmitModule]) -> Self {
        let ownership = item_ownership(vk, video, modules);
        let mut enum_widths = IndexMap::new();
        for group in video.enum_groups.iter().chain(vk.enum_groups.iter()) {
            if let Some(name) = &group.name {
                let ty = match (group.kind.as_str(), group.bitwidth) {
                    ("bitmask", Some(64)) => "u64",
                    ("bitmask", _) => "u32",
                    ("enum", _) => "i32",
                    _ => "u32",
                };
                enum_widths.insert(name.clone(), ty);
            }
        }
        let bitmasks = bitmask_infos(vk);
        let bit_enums_to_flags = bit_enum_to_flags(vk, &bitmasks);
        let rust_enums = rust_enum_infos(vk, video, modules, &enum_widths, &bit_enums_to_flags);
        let (flag_constants, bitflag_macro_entries) =
            flag_constants(vk, modules, &bit_enums_to_flags);
        let video_type_names = video.types.iter().map(|ty| ty.name.clone()).collect();
        Self {
            emitted_types: IndexSet::new(),
            emitted_consts: IndexSet::new(),
            emitted_flag_consts: IndexSet::new(),
            const_types: IndexMap::new(),
            ownership,
            enum_widths,
            rust_enums,
            bitmasks,
            bit_enums_to_flags,
            flag_constants,
            bitflag_macro_entries,
            video_type_names,
        }
    }

    fn type_condition(&self, name: &str) -> &select::FeatureExpr {
        self.ownership
            .types
            .get(name)
            .unwrap_or_else(|| panic!("type {name} has no feature owner"))
    }

    fn type_condition_opt(&self, name: &str) -> Option<&select::FeatureExpr> {
        self.ownership.types.get(name)
    }

    fn const_condition(&self, name: &str) -> &select::FeatureExpr {
        self.ownership
            .consts
            .get(name)
            .unwrap_or_else(|| panic!("constant {name} has no feature owner"))
    }

    fn enum_condition(&self, name: &str) -> &select::FeatureExpr {
        self.ownership
            .enums
            .get(name)
            .unwrap_or_else(|| panic!("enum entry {name} has no feature owner"))
    }
}

fn item_ownership(vk: &Registry, video: &Registry, modules: &[EmitModule]) -> Ownership {
    let mut ownership = Ownership::default();
    let mut types: IndexMap<&str, Vec<&TypeDef>> = IndexMap::new();
    let enum_groups: IndexMap<_, _> = video
        .enum_groups
        .iter()
        .chain(vk.enum_groups.iter())
        .filter_map(|group| group.name.as_deref().map(|name| (name, group)))
        .collect();
    let commands: IndexMap<_, _> = vk
        .commands
        .iter()
        .map(|command| (command.name.as_str(), command))
        .collect();

    for ty in video.types.iter().chain(vk.types.iter()) {
        types.entry(ty.name.as_str()).or_default().push(ty);
    }

    for module in modules {
        let feature = select::FeatureExpr::feature(&module.feature_name);

        for guard in &module.guards {
            add_owner(&mut ownership.consts, guard, feature.clone());
        }

        for (name, condition) in &module.types {
            if is_native(name) {
                add_owner(&mut ownership.types, name, condition.clone());
            }
            if let Some(defs) = types.get(name.as_str()) {
                for ty in defs {
                    add_type_owners(&mut ownership, &enum_groups, ty, condition);
                }
            }
        }

        for (command_name, condition) in &module.commands {
            add_owner(
                &mut ownership.types,
                &format!("PFN_{command_name}"),
                condition.clone(),
            );
            let command = commands
                .get(command_name.as_str())
                .copied()
                .unwrap_or_else(|| panic!("unknown command {command_name}"));
            let signature = resolve_command_signature(command, &commands);
            if let Some(proto) = &signature.proto {
                add_decl_owners(&mut ownership, proto, condition);
            }
            for param in &signature.params {
                add_decl_owners(&mut ownership, param, condition);
            }
        }

        for resolved in module.enums.values() {
            add_owner(
                &mut ownership.enums,
                &resolved.entry.name,
                resolved.condition.clone(),
            );
            add_owner(
                &mut ownership.consts,
                &resolved.entry.name,
                resolved.condition.clone(),
            );
        }
    }

    ownership
}

fn add_type_owners(
    ownership: &mut Ownership,
    enum_groups: &IndexMap<&str, &crate::registry::EnumGroup>,
    ty: &TypeDef,
    condition: &select::FeatureExpr,
) {
    if !select::api_matches(ty.api.as_deref()) {
        return;
    }

    if ty.category == "define" {
        add_owner(&mut ownership.consts, &ty.name, condition.clone());
        return;
    }

    add_owner(&mut ownership.types, &ty.name, condition.clone());

    if ty.category == "handle"
        && ty.alias.is_none()
        && !ty
            .handle_macro
            .as_deref()
            .is_some_and(|name| name == "VK_DEFINE_NON_DISPATCHABLE_HANDLE")
    {
        add_owner(
            &mut ownership.types,
            &format!("{}_T", ty.name),
            condition.clone(),
        );
    }

    if ty.category == "enum" {
        add_enum_group_owners(ownership, enum_groups, &ty.name, condition);
    }

    if ty.category == "bitmask"
        && let Some(bits_name) = ty.bitvalues.as_deref().or(ty.requires.as_deref())
    {
        add_enum_group_owners(ownership, enum_groups, bits_name, condition);
    }

    if let Some(decl) = &ty.underlying_decl {
        add_decl_owners(ownership, decl, condition);
    }
    for member in &ty.members {
        add_decl_owners(ownership, member, condition);
    }
    if let Some(func) = &ty.func {
        add_decl_owners(ownership, &func.proto, condition);
        for param in &func.params {
            add_decl_owners(ownership, param, condition);
        }
    }
}

fn add_enum_group_owners(
    ownership: &mut Ownership,
    enum_groups: &IndexMap<&str, &crate::registry::EnumGroup>,
    group_name: &str,
    condition: &select::FeatureExpr,
) {
    let Some(group) = enum_groups.get(group_name).copied() else {
        return;
    };
    for entry in &group.entries {
        if select::api_matches(entry.api.as_deref()) {
            add_owner(&mut ownership.enums, &entry.name, condition.clone());
            add_owner(&mut ownership.consts, &entry.name, condition.clone());
        }
    }
}

fn add_decl_owners(ownership: &mut Ownership, decl: &Decl, condition: &select::FeatureExpr) {
    if select::api_matches(decl.api.as_deref()) && is_native(&decl.base) {
        add_owner(&mut ownership.types, &decl.base, condition.clone());
    }
}

fn add_owner(
    map: &mut IndexMap<String, select::FeatureExpr>,
    name: &str,
    condition: select::FeatureExpr,
) {
    select::insert_condition(map, name, condition);
}

fn bitmask_infos(vk: &Registry) -> IndexMap<String, BitmaskInfo> {
    let mut infos = IndexMap::new();
    for ty in &vk.types {
        if ty.category == "bitmask" && ty.alias.is_none() && select::api_matches(ty.api.as_deref())
        {
            infos.insert(
                ty.name.clone(),
                BitmaskInfo {
                    bits_name: ty.bitvalues.clone().or_else(|| ty.requires.clone()),
                    bits_ty: bitmask_bits_ty(ty.underlying_type.as_deref()),
                },
            );
        }
    }
    infos
}

fn bit_enum_to_flags(
    vk: &Registry,
    bitmasks: &IndexMap<String, BitmaskInfo>,
) -> IndexMap<String, String> {
    let mut map = IndexMap::new();
    for (flags_name, info) in bitmasks {
        if let Some(bits_name) = &info.bits_name {
            map.entry(bits_name.clone())
                .or_insert_with(|| flags_name.clone());
        }
    }

    let enum_aliases: IndexMap<_, _> = vk
        .types
        .iter()
        .filter(|ty| ty.category == "enum")
        .filter_map(|ty| {
            ty.alias
                .as_ref()
                .map(|alias| (ty.name.clone(), alias.clone()))
        })
        .collect();

    loop {
        let mut changed = false;
        for (name, alias) in &enum_aliases {
            if map.contains_key(name) {
                continue;
            }
            if let Some(flags_name) = map.get(alias).cloned() {
                map.insert(name.clone(), flags_name);
                changed = true;
            }
        }
        if !changed {
            break;
        }
    }

    map
}

fn flag_constants(
    vk: &Registry,
    modules: &[EmitModule],
    bit_enums_to_flags: &IndexMap<String, String>,
) -> (IndexMap<String, FlagConstant>, IndexSet<String>) {
    let mut constants = IndexMap::new();
    let mut macro_entries = IndexSet::new();
    let mut used_names: IndexMap<String, IndexSet<String>> = IndexMap::new();

    for group in &vk.enum_groups {
        let Some(group_name) = &group.name else {
            continue;
        };
        let Some(flags_type) = bit_enums_to_flags.get(group_name) else {
            continue;
        };
        if group.kind != "bitmask" {
            continue;
        }
        for entry in &group.entries {
            if !select::api_matches(entry.api.as_deref()) {
                continue;
            }
            insert_flag_constant(
                &mut constants,
                &mut used_names,
                flags_type,
                group_name,
                &entry.name,
            );
            macro_entries.insert(entry.name.clone());
        }
    }

    for module in modules {
        for resolved in module.enums.values() {
            let Some(group_name) = &resolved.group_name else {
                continue;
            };
            let Some(flags_type) = bit_enums_to_flags.get(group_name) else {
                continue;
            };
            if !select::api_matches(resolved.entry.api.as_deref()) {
                continue;
            }
            insert_flag_constant(
                &mut constants,
                &mut used_names,
                flags_type,
                group_name,
                &resolved.entry.name,
            );
        }
    }

    (constants, macro_entries)
}

fn insert_flag_constant(
    constants: &mut IndexMap<String, FlagConstant>,
    used_names: &mut IndexMap<String, IndexSet<String>>,
    flags_type: &str,
    group_name: &str,
    enum_name: &str,
) {
    if constants.contains_key(enum_name) {
        return;
    }

    let proposed = flag_assoc_name(group_name, enum_name);
    let used = used_names.entry(flags_type.to_string()).or_default();
    let assoc_name = if used.insert(proposed.clone()) {
        proposed
    } else {
        let fallback = full_flag_assoc_name(enum_name);
        if used.insert(fallback.clone()) {
            fallback
        } else {
            let mut idx = 2usize;
            loop {
                let name = format!("{fallback}_{idx}");
                if used.insert(name.clone()) {
                    break name;
                }
                idx += 1;
            }
        }
    };

    constants.insert(
        enum_name.to_string(),
        FlagConstant {
            flags_type: flags_type.to_string(),
            assoc_name,
        },
    );
}

fn bitmask_bits_ty(base: Option<&str>) -> &'static str {
    match base.unwrap_or("VkFlags") {
        "VkFlags64" | "uint64_t" => "u64",
        _ => "u32",
    }
}

fn flag_assoc_name(group_name: &str, enum_name: &str) -> String {
    let group_prefix = bitflag_group_prefix(group_name);
    let mut name = enum_name
        .strip_prefix("VK_")
        .unwrap_or(enum_name)
        .to_string();
    if let Some(rest) = name.strip_prefix(&format!("{group_prefix}_")) {
        name = rest.to_string();
    }
    trim_bit_suffix(&name)
}

fn full_flag_assoc_name(enum_name: &str) -> String {
    trim_bit_suffix(enum_name.strip_prefix("VK_").unwrap_or(enum_name))
}

fn enum_variant_name(group_name: &str, enum_name: &str) -> String {
    let mut name = enum_name
        .strip_prefix("VK_")
        .unwrap_or(enum_name)
        .to_string();
    let group = group_name.strip_prefix("Vk").unwrap_or(group_name);
    let mut group_words: Vec<String> = camel_to_upper_snake(group)
        .split('_')
        .filter(|word| !word.is_empty())
        .map(ToOwned::to_owned)
        .collect();
    let mut suffix_words = Vec::new();
    while group_words
        .last()
        .is_some_and(|word| is_vendor_suffix(word))
    {
        suffix_words.push(group_words.pop().unwrap());
    }

    if !group_words.is_empty() {
        let prefix = group_words.join("_");
        if let Some(rest) = name.strip_prefix(&format!("{prefix}_")) {
            name = rest.to_string();
        }
    }

    for suffix in suffix_words.iter().rev() {
        if let Some(rest) = name.strip_suffix(&format!("_{suffix}")) {
            name = rest.to_string();
        }
    }

    upper_snake_to_upper_camel(&name)
}

fn is_vendor_suffix(word: &str) -> bool {
    matches!(
        word,
        "AMD"
            | "AMDX"
            | "ANDROID"
            | "ARM"
            | "EXT"
            | "FUCHSIA"
            | "GGP"
            | "GOOGLE"
            | "HUAWEI"
            | "IMG"
            | "INTEL"
            | "KHR"
            | "LUNARG"
            | "MESA"
            | "MSFT"
            | "MVK"
            | "NN"
            | "NV"
            | "NVX"
            | "OHOS"
            | "QCOM"
            | "QNX"
            | "SEC"
            | "VALVE"
    )
}

fn upper_snake_to_upper_camel(name: &str) -> String {
    let mut out = String::new();
    for word in name.split('_').filter(|word| !word.is_empty()) {
        out.push_str(&upper_snake_word_to_upper_camel(word));
    }
    if out.is_empty() {
        out.push_str("Empty");
    }
    if out.as_bytes().first().is_some_and(u8::is_ascii_digit) {
        out.insert(0, '_');
    }
    out
}

fn upper_snake_word_to_upper_camel(word: &str) -> String {
    if word.bytes().any(|b| b.is_ascii_digit()) {
        return word.to_ascii_uppercase();
    }

    let mut chars = word.chars();
    let Some(first) = chars.next() else {
        return String::new();
    };
    let mut out = String::new();
    out.push(first.to_ascii_uppercase());
    for ch in chars {
        out.push(ch.to_ascii_lowercase());
    }
    out
}

fn trim_bit_suffix(name: &str) -> String {
    let mut name = if let Some(rest) = name.strip_suffix("_BIT") {
        rest.to_string()
    } else if let Some((left, right)) = name.split_once("_BIT_") {
        format!("{left}_{right}")
    } else {
        name.to_string()
    };
    if name.is_empty() {
        name.push_str("EMPTY");
    }
    if name.as_bytes().first().is_some_and(u8::is_ascii_digit) {
        name.insert(0, '_');
    }
    name
}

fn bitflag_group_prefix(group_name: &str) -> String {
    let name = group_name.strip_prefix("Vk").unwrap_or(group_name);
    let Some(idx) = name.find("FlagBits") else {
        return camel_to_upper_snake(name);
    };
    let before = &name[..idx];
    let after = &name[idx + "FlagBits".len()..];
    let digits: String = after.chars().take_while(|ch| ch.is_ascii_digit()).collect();
    let mut prefix = camel_to_upper_snake(before);
    if !digits.is_empty() {
        prefix.push('_');
        prefix.push_str(&digits);
    }
    prefix
}

fn camel_to_upper_snake(name: &str) -> String {
    let chars: Vec<char> = name.chars().collect();
    let mut out = String::new();
    for (idx, ch) in chars.iter().copied().enumerate() {
        if idx > 0 && ch.is_ascii_uppercase() {
            let prev = chars[idx - 1];
            let next = chars.get(idx + 1).copied();
            if prev.is_ascii_lowercase()
                || prev.is_ascii_digit()
                || (prev.is_ascii_uppercase() && next.is_some_and(|n| n.is_ascii_lowercase()))
            {
                out.push('_');
            }
        }
        out.push(ch.to_ascii_uppercase());
    }
    out
}

fn generated_header() -> String {
    let mut out = String::new();
    out.push_str("// This file is @generated by the volcanic codegen crate.\n");
    out.push_str("// Run `cargo run -p codegen` from the repository root to update it.\n");
    out
}

fn emit_feature_requirement_errors(out: &mut String, vk: &Registry, modules: &[EmitModule]) {
    let extensions: IndexMap<_, _> = vk
        .extensions
        .iter()
        .map(|extension| (extension.name.as_str(), extension))
        .collect();
    let known_features: IndexSet<_> = modules
        .iter()
        .map(|module| module.feature_name.clone())
        .collect();

    let section = section(out, "Feature requirements");
    for module in modules {
        let Some(extension) = extensions.get(module.source_name.as_str()).copied() else {
            continue;
        };
        let requirement =
            select::extension_requirement_expr(extension.depends.as_deref(), &known_features);
        if requirement == select::FeatureExpr::True {
            continue;
        }

        let cfg = requirement.cfg_expr();
        let description = requirement.description();
        out.push_str(&format!(
            "#[cfg(all(feature = \"{}\", not({cfg})))]\n",
            module.feature_name
        ));
        out.push_str(&format!(
            "compile_error!(\"feature `{}` requires {description}\");\n",
            module.feature_name
        ));
    }

    section.finish(out);
}

fn enum_index(
    vk: &Registry,
    video: &Registry,
    selection: &select::Selection,
) -> IndexMap<String, IndexedEnum> {
    let mut index = IndexMap::new();
    for group in video.enum_groups.iter().chain(vk.enum_groups.iter()) {
        for entry in &group.entries {
            index
                .entry(entry.name.clone())
                .or_insert_with(|| IndexedEnum {
                    entry: entry.clone(),
                    group_name: group
                        .name
                        .as_deref()
                        .filter(|name| *name != "API Constants")
                        .map(ToOwned::to_owned),
                    extension_number: None,
                });
        }
    }
    for module in &selection.modules {
        for required in &module.enums {
            if required.entry.value.is_some()
                || required.entry.bitpos.is_some()
                || required.entry.offset.is_some()
                || required.entry.alias.is_some()
            {
                index
                    .entry(required.entry.name.clone())
                    .or_insert_with(|| IndexedEnum {
                        group_name: required.entry.extends.clone(),
                        entry: required.entry.clone(),
                        extension_number: required.extension_number,
                    });
            }
        }
    }
    index
}

fn resolve_required_enums(
    required: &[select::RequiredEnum],
    enum_index: &IndexMap<String, IndexedEnum>,
) -> IndexMap<String, ResolvedEnum> {
    let mut enums = IndexMap::new();
    for required in required {
        let resolved = if required.entry.value.is_some()
            || required.entry.bitpos.is_some()
            || required.entry.offset.is_some()
            || required.entry.alias.is_some()
        {
            ResolvedEnum {
                group_name: required.entry.extends.clone(),
                entry: required.entry.clone(),
                extension_number: required.extension_number,
                condition: required.condition.clone(),
            }
        } else {
            let indexed = enum_index
                .get(&required.entry.name)
                .unwrap_or_else(|| panic!("unknown required enum {}", required.entry.name));
            ResolvedEnum {
                entry: indexed.entry.clone(),
                group_name: indexed.group_name.clone(),
                extension_number: indexed.extension_number.or(required.extension_number),
                condition: required.condition.clone(),
            }
        };
        insert_resolved_enum(&mut enums, resolved);
    }
    enums
}

fn emit_modules(
    selection: &select::Selection,
    enum_index: &IndexMap<String, IndexedEnum>,
) -> Vec<EmitModule> {
    selection
        .modules
        .iter()
        .map(|module| EmitModule {
            source_name: module.source_name.clone(),
            feature_name: module.rust_name.clone(),
            prerequisite: module.prerequisite.clone(),
            guards: module.guards.clone(),
            types: module.types.clone(),
            commands: module.commands.clone(),
            enums: resolve_required_enums(&module.enums, enum_index),
        })
        .collect()
}

fn insert_resolved_enum(
    enums: &mut IndexMap<String, ResolvedEnum>,
    resolved: ResolvedEnum,
) -> bool {
    if let Some(existing) = enums.get_mut(&resolved.entry.name) {
        let condition = select::FeatureExpr::any([existing.condition.clone(), resolved.condition]);
        let changed = existing.condition != condition;
        existing.condition = condition;
        if existing.group_name.is_none() {
            existing.group_name = resolved.group_name;
        }
        if existing.extension_number.is_none() {
            existing.extension_number = resolved.extension_number;
        }
        changed
    } else {
        enums.insert(resolved.entry.name.clone(), resolved);
        true
    }
}

fn expand_module_dependencies(
    vk: &Registry,
    video: &Registry,
    enum_index: &IndexMap<String, IndexedEnum>,
    modules: &mut [EmitModule],
) {
    let mut type_defs: IndexMap<&str, Vec<&TypeDef>> = IndexMap::new();
    for ty in video.types.iter().chain(vk.types.iter()) {
        type_defs.entry(&ty.name).or_default().push(ty);
    }
    let enum_groups: IndexMap<_, _> = video
        .enum_groups
        .iter()
        .chain(vk.enum_groups.iter())
        .filter_map(|group| group.name.as_deref().map(|name| (name, group)))
        .collect();
    let commands: IndexMap<_, _> = vk
        .commands
        .iter()
        .map(|command| (command.name.as_str(), command))
        .collect();
    let bitmasks = bitmask_infos(vk);
    let bit_enums_to_flags = bit_enum_to_flags(vk, &bitmasks);
    let mut available_types = IndexMap::new();
    let mut available_enums = IndexMap::new();
    for module in modules.iter() {
        for (name, condition) in &module.types {
            select::insert_condition(&mut available_types, name, condition.clone());
        }
        for resolved in module.enums.values() {
            select::insert_condition(
                &mut available_enums,
                &resolved.entry.name,
                resolved.condition.clone(),
            );
        }
    }

    for module in modules {
        loop {
            let mut type_additions = Vec::new();
            let mut enum_additions = Vec::new();

            for (name, condition) in module.types.clone() {
                let Some(defs) = type_defs.get(name.as_str()) else {
                    continue;
                };
                for ty in defs {
                    if !select::api_matches(ty.api.as_deref()) {
                        continue;
                    }
                    if let Some(alias) = &ty.alias {
                        add_type_dependency(&mut type_additions, &type_defs, alias, &condition);
                    }
                    if let Some(base) = &ty.underlying_type {
                        add_type_dependency(&mut type_additions, &type_defs, base, &condition);
                    }
                    if let Some(decl) = &ty.underlying_decl {
                        add_decl_dependencies(
                            &mut type_additions,
                            &mut enum_additions,
                            &type_defs,
                            enum_index,
                            decl,
                            &condition,
                        );
                    }
                    for member in &ty.members {
                        add_decl_dependencies(
                            &mut type_additions,
                            &mut enum_additions,
                            &type_defs,
                            enum_index,
                            member,
                            &condition,
                        );
                    }
                    if let Some(func) = &ty.func {
                        add_decl_dependencies(
                            &mut type_additions,
                            &mut enum_additions,
                            &type_defs,
                            enum_index,
                            &func.proto,
                            &condition,
                        );
                        for param in &func.params {
                            add_decl_dependencies(
                                &mut type_additions,
                                &mut enum_additions,
                                &type_defs,
                                enum_index,
                                param,
                                &condition,
                            );
                        }
                    }
                    if let Some(required) = ty.requires.as_deref() {
                        add_type_dependency(&mut type_additions, &type_defs, required, &condition);
                    }
                    if ty.category == "enum" {
                        if let Some(flags_type) = bit_enums_to_flags.get(&ty.name) {
                            add_type_dependency(
                                &mut type_additions,
                                &type_defs,
                                flags_type,
                                &condition,
                            );
                        }
                        add_enum_group_dependencies(
                            &mut enum_additions,
                            &enum_groups,
                            &ty.name,
                            &condition,
                        );
                    }
                    if ty.category == "bitmask"
                        && let Some(bits_name) = ty.bitvalues.as_deref().or(ty.requires.as_deref())
                    {
                        add_enum_group_dependencies(
                            &mut enum_additions,
                            &enum_groups,
                            bits_name,
                            &condition,
                        );
                    }
                }
            }

            for (name, condition) in module.commands.clone() {
                let command = commands
                    .get(name.as_str())
                    .copied()
                    .unwrap_or_else(|| panic!("unknown command {name}"));
                let signature = resolve_command_signature(command, &commands);
                if let Some(proto) = &signature.proto {
                    add_decl_dependencies(
                        &mut type_additions,
                        &mut enum_additions,
                        &type_defs,
                        enum_index,
                        proto,
                        &condition,
                    );
                }
                for param in &signature.params {
                    add_decl_dependencies(
                        &mut type_additions,
                        &mut enum_additions,
                        &type_defs,
                        enum_index,
                        param,
                        &condition,
                    );
                }
            }

            for resolved in module.enums.values() {
                if let Some(group_name) = &resolved.group_name {
                    add_type_dependency(
                        &mut type_additions,
                        &type_defs,
                        group_name,
                        &resolved.condition,
                    );
                    if let Some(flags_type) = bit_enums_to_flags.get(group_name) {
                        add_type_dependency(
                            &mut type_additions,
                            &type_defs,
                            flags_type,
                            &resolved.condition,
                        );
                    }
                }
                if let Some(alias) = &resolved.entry.alias
                    && let Some(indexed) = enum_index.get(alias)
                {
                    enum_additions.push(ResolvedEnum {
                        entry: indexed.entry.clone(),
                        group_name: indexed.group_name.clone(),
                        extension_number: indexed.extension_number,
                        condition: resolved.condition.clone(),
                    });
                }
            }

            let mut changed = false;
            for (name, condition) in type_additions {
                let valid_condition =
                    select::FeatureExpr::all([condition.clone(), module.prerequisite.clone()]);
                if available_types
                    .get(&name)
                    .is_some_and(|available| valid_condition.implies(available))
                {
                    continue;
                }
                changed |= select::insert_condition(&mut module.types, &name, condition.clone());
                select::insert_condition(&mut available_types, &name, condition);
            }
            for resolved in enum_additions {
                let valid_condition = select::FeatureExpr::all([
                    resolved.condition.clone(),
                    module.prerequisite.clone(),
                ]);
                if available_enums
                    .get(&resolved.entry.name)
                    .is_some_and(|available| valid_condition.implies(available))
                {
                    continue;
                }
                select::insert_condition(
                    &mut available_enums,
                    &resolved.entry.name,
                    resolved.condition.clone(),
                );
                changed |= insert_resolved_enum(&mut module.enums, resolved);
            }
            if !changed {
                break;
            }
        }
    }
}

fn add_type_dependency(
    additions: &mut Vec<(String, select::FeatureExpr)>,
    type_defs: &IndexMap<&str, Vec<&TypeDef>>,
    name: &str,
    condition: &select::FeatureExpr,
) {
    if type_defs.contains_key(name) || is_native(name) {
        additions.push((name.to_string(), condition.clone()));
    }
}

fn add_decl_dependencies(
    type_additions: &mut Vec<(String, select::FeatureExpr)>,
    enum_additions: &mut Vec<ResolvedEnum>,
    type_defs: &IndexMap<&str, Vec<&TypeDef>>,
    enum_index: &IndexMap<String, IndexedEnum>,
    decl: &Decl,
    condition: &select::FeatureExpr,
) {
    if !select::api_matches(decl.api.as_deref()) {
        return;
    }
    add_type_dependency(type_additions, type_defs, &decl.base, condition);
    for dimension in &decl.arrays {
        for name in dimension.split(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_') {
            if let Some(indexed) = enum_index.get(name) {
                enum_additions.push(ResolvedEnum {
                    entry: indexed.entry.clone(),
                    group_name: indexed.group_name.clone(),
                    extension_number: indexed.extension_number,
                    condition: condition.clone(),
                });
            }
        }
    }
}

fn add_enum_group_dependencies(
    additions: &mut Vec<ResolvedEnum>,
    enum_groups: &IndexMap<&str, &crate::registry::EnumGroup>,
    group_name: &str,
    condition: &select::FeatureExpr,
) {
    let Some(group) = enum_groups.get(group_name).copied() else {
        return;
    };
    for entry in &group.entries {
        if select::api_matches(entry.api.as_deref()) {
            additions.push(ResolvedEnum {
                entry: entry.clone(),
                group_name: Some(group_name.to_string()),
                extension_number: None,
                condition: condition.clone(),
            });
        }
    }
}

fn emit_native_types(out: &mut String, ctx: &mut Context) {
    section(out, "Native platform types");
    for (name, ty) in native_types() {
        if ctx.type_condition_opt(name).is_none() {
            continue;
        }
        emit_type(out, ctx, name, &format!("pub type {name} = {ty};\n"));
    }
    out.push('\n');
}

fn emit_define_items(
    out: &mut String,
    ctx: &mut Context,
    registry: &Registry,
    names: &IndexMap<String, select::FeatureExpr>,
) {
    let section = section(out, "Define helpers and constants");
    for ty in &registry.types {
        if ty.category != "define" || !names.contains_key(&ty.name) {
            continue;
        }
        match ty.name.as_str() {
            "VK_MAKE_VERSION" => emit_define_fn(
                out,
                ctx,
                "VK_MAKE_VERSION",
                "pub const fn VK_MAKE_VERSION(major: u32, minor: u32, patch: u32) -> u32 {\n    (major << 22) | (minor << 12) | patch\n}\n",
            ),
            "VK_VERSION_MAJOR" => emit_define_fn(
                out,
                ctx,
                "VK_VERSION_MAJOR",
                "pub const fn VK_VERSION_MAJOR(version: u32) -> u32 { version >> 22 }\n",
            ),
            "VK_VERSION_MINOR" => emit_define_fn(
                out,
                ctx,
                "VK_VERSION_MINOR",
                "pub const fn VK_VERSION_MINOR(version: u32) -> u32 { (version >> 12) & 0x3ff }\n",
            ),
            "VK_VERSION_PATCH" => emit_define_fn(
                out,
                ctx,
                "VK_VERSION_PATCH",
                "pub const fn VK_VERSION_PATCH(version: u32) -> u32 { version & 0xfff }\n",
            ),
            "VK_MAKE_API_VERSION" => emit_define_fn(
                out,
                ctx,
                "VK_MAKE_API_VERSION",
                "pub const fn VK_MAKE_API_VERSION(variant: u32, major: u32, minor: u32, patch: u32) -> u32 {\n    (variant << 29) | (major << 22) | (minor << 12) | patch\n}\n",
            ),
            "VK_API_VERSION_VARIANT" => emit_define_fn(
                out,
                ctx,
                "VK_API_VERSION_VARIANT",
                "pub const fn VK_API_VERSION_VARIANT(version: u32) -> u32 { version >> 29 }\n",
            ),
            "VK_API_VERSION_MAJOR" => emit_define_fn(
                out,
                ctx,
                "VK_API_VERSION_MAJOR",
                "pub const fn VK_API_VERSION_MAJOR(version: u32) -> u32 { (version >> 22) & 0x7f }\n",
            ),
            "VK_API_VERSION_MINOR" => emit_define_fn(
                out,
                ctx,
                "VK_API_VERSION_MINOR",
                "pub const fn VK_API_VERSION_MINOR(version: u32) -> u32 { (version >> 12) & 0x3ff }\n",
            ),
            "VK_API_VERSION_PATCH" => emit_define_fn(
                out,
                ctx,
                "VK_API_VERSION_PATCH",
                "pub const fn VK_API_VERSION_PATCH(version: u32) -> u32 { version & 0xfff }\n",
            ),
            "VK_MAKE_VIDEO_STD_VERSION" => emit_define_fn(
                out,
                ctx,
                "VK_MAKE_VIDEO_STD_VERSION",
                "pub const fn VK_MAKE_VIDEO_STD_VERSION(major: u32, minor: u32, patch: u32) -> u32 {\n    (major << 22) | (minor << 12) | patch\n}\n",
            ),
            "VK_API_VERSION_1_0" => emit_const(
                out,
                ctx,
                "VK_API_VERSION_1_0",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 0, 0)",
            ),
            "VK_API_VERSION_1_1" => emit_const(
                out,
                ctx,
                "VK_API_VERSION_1_1",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 1, 0)",
            ),
            "VK_API_VERSION_1_2" => emit_const(
                out,
                ctx,
                "VK_API_VERSION_1_2",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 2, 0)",
            ),
            "VK_API_VERSION_1_3" => emit_const(
                out,
                ctx,
                "VK_API_VERSION_1_3",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 3, 0)",
            ),
            "VK_API_VERSION_1_4" => emit_const(
                out,
                ctx,
                "VK_API_VERSION_1_4",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 4, 0)",
            ),
            "VK_HEADER_VERSION" => {
                let header = find_define_number(registry, "VK_HEADER_VERSION");
                emit_const(out, ctx, "VK_HEADER_VERSION", "u32", &header);
            }
            "VK_HEADER_VERSION_COMPLETE" => emit_const(
                out,
                ctx,
                "VK_HEADER_VERSION_COMPLETE",
                "u32",
                "VK_MAKE_API_VERSION(0, 1, 4, VK_HEADER_VERSION)",
            ),
            "VK_NULL_HANDLE" => emit_const(out, ctx, "VK_NULL_HANDLE", "u64", "0"),
            name if name.starts_with("VK_STD_VULKAN_VIDEO_CODEC_")
                && name.ends_with("_API_VERSION_1_0_0") =>
            {
                emit_const(out, ctx, name, "u32", "VK_MAKE_VIDEO_STD_VERSION(1, 0, 0)");
            }
            _ => {}
        }
    }
    section.finish(out);
}

fn emit_feature_extension_guards(out: &mut String, ctx: &mut Context, guards: &IndexSet<String>) {
    let section = section(out, "Feature and extension guards");
    for name in guards {
        emit_const(out, ctx, name, "u32", "1");
    }
    section.finish(out);
}

fn const_type(ctx: &Context, entry: &EnumEntry, group_name: Option<&str>) -> String {
    if let Some(alias) = &entry.alias
        && let Some(ty) = ctx.const_types.get(alias)
    {
        return ty.clone();
    }
    if entry
        .value
        .as_deref()
        .is_some_and(|value| value.starts_with('"'))
    {
        return "&core::ffi::CStr".to_string();
    }
    if let Some(group_name) = group_name
        && group_name != "API Constants"
    {
        return group_name.to_string();
    }
    if let Some(extends) = &entry.extends {
        return extends.clone();
    }
    if let Some(ty) = &entry.ty {
        return rust_base(ty);
    }
    if let Some(value) = &entry.value {
        if value.contains("ULL") {
            return "u64".to_string();
        }
        if value.contains('.') || value.ends_with('f') || value.ends_with('F') {
            return "f32".to_string();
        }
    }
    if let Some(group_name) = group_name {
        ctx.enum_widths
            .get(group_name)
            .copied()
            .unwrap_or("u32")
            .to_string()
    } else {
        "u32".to_string()
    }
}

fn const_value(
    ctx: &Context,
    entry: &EnumEntry,
    group_name: Option<&str>,
    extension_number: Option<u32>,
) -> String {
    const_value_from_widths(&ctx.enum_widths, entry, group_name, extension_number)
}

fn const_value_from_widths(
    enum_widths: &IndexMap<String, &'static str>,
    entry: &EnumEntry,
    group_name: Option<&str>,
    extension_number: Option<u32>,
) -> String {
    if let Some(bitpos) = entry.bitpos {
        let width = entry
            .extends
            .as_deref()
            .or(group_name)
            .and_then(|name| enum_widths.get(name).copied())
            .unwrap_or("u32");
        return if width == "u64" {
            format!("1u64 << {bitpos}")
        } else {
            format!("1u32 << {bitpos}")
        };
    }
    if let Some(offset) = entry.offset {
        let ext_number = entry
            .extnumber
            .or(extension_number)
            .expect("extension enum offset without extension number");
        let value = 1_000_000_000i64 + (ext_number as i64 - 1) * 1000 + offset as i64;
        return if entry.dir.as_deref() == Some("-") {
            format!("{}", -value)
        } else {
            format!("{value}")
        };
    }
    if let Some(value) = &entry.value {
        return rust_const_expr(value);
    }
    if let Some(alias) = &entry.alias {
        return alias.clone();
    }
    panic!("enum {} has no value, bitpos, offset, or alias", entry.name);
}

fn bitflag_bits_value(
    ctx: &Context,
    entry: &EnumEntry,
    group_name: Option<&str>,
    extension_number: Option<u32>,
    self_flags_type: Option<&str>,
) -> String {
    if let Some(alias) = &entry.alias
        && let Some(alias_constant) = ctx.flag_constants.get(alias)
    {
        return if self_flags_type == Some(alias_constant.flags_type.as_str()) {
            format!("Self::{}.bits()", alias_constant.assoc_name)
        } else {
            format!(
                "{}::{}.bits()",
                alias_constant.flags_type, alias_constant.assoc_name
            )
        };
    }

    const_value(ctx, entry, group_name, extension_number)
}

fn rust_const_expr(value: &str) -> String {
    let value = value.trim();
    if value.starts_with('"') && value.ends_with('"') {
        return format!("c\"{}\"", &value[1..value.len() - 1]);
    }
    if let Some(expr) = match value {
        "(~0U)" => Some("u32::MAX"),
        "(~1U)" => Some("!1u32"),
        "(~2U)" => Some("!2u32"),
        "(~0ULL)" => Some("u64::MAX"),
        _ => None,
    } {
        return expr.to_string();
    }
    if is_float_literal(value) && (value.ends_with('F') || value.ends_with('f')) {
        return format!("{}f32", &value[..value.len() - 1]);
    }
    if let Some(stripped) = value.strip_suffix("ULL") {
        return format!("{stripped}u64");
    }
    if let Some(stripped) = value.strip_suffix('U') {
        return format!("{stripped}u32");
    }
    value.to_string()
}

fn is_float_literal(value: &str) -> bool {
    let lower = value.to_ascii_lowercase();
    !lower.starts_with("0x") && (value.contains('.') || value.contains('e') || value.contains('E'))
}

fn emit_cfg(out: &mut String, condition: &select::FeatureExpr, indent: &str) {
    assert!(
        !condition.is_false(),
        "attempted to emit item with an impossible feature condition"
    );
    if *condition != select::FeatureExpr::True {
        out.push_str(&format!("{indent}#[cfg({})]\n", condition.cfg_expr()));
    }
}

fn emit_nested_cfg(
    out: &mut String,
    condition: &select::FeatureExpr,
    parent: &select::FeatureExpr,
    indent: &str,
) {
    if !parent.implies(condition) {
        emit_cfg(out, condition, indent);
    }
}

fn emit_const(out: &mut String, ctx: &mut Context, name: &str, ty: &str, value: &str) {
    if ctx.emitted_consts.insert(name.to_string()) {
        ctx.const_types.insert(name.to_string(), ty.to_string());
        emit_cfg(out, ctx.const_condition(name), "");
        out.push_str(&format!("pub const {name}: {ty} = {value};\n"));
    }
}

fn emit_define_fn(out: &mut String, ctx: &mut Context, name: &str, text: &str) {
    if ctx.emitted_consts.insert(name.to_string()) {
        emit_cfg(out, ctx.const_condition(name), "");
        out.push_str(text);
    }
}

fn emit_type(out: &mut String, ctx: &mut Context, name: &str, text: &str) {
    if ctx.emitted_types.insert(name.to_string()) {
        emit_cfg(out, ctx.type_condition(name), "");
        out.push_str(text);
    }
}

struct Section {
    start: usize,
    body: usize,
}

impl Section {
    fn finish(self, out: &mut String) {
        if out.len() == self.body {
            out.truncate(self.start);
        } else {
            out.push('\n');
        }
    }
}

fn section(out: &mut String, name: &str) -> Section {
    let start = out.len();
    let heading = format!("// ---- {name:.54} ");
    out.push_str(&heading);
    out.extend(std::iter::repeat_n(
        '-',
        64usize.saturating_sub(heading.len()),
    ));
    out.push('\n');
    Section {
        start,
        body: out.len(),
    }
}

fn find_define_number(registry: &Registry, name: &str) -> String {
    registry
        .types
        .iter()
        .find(|ty| {
            ty.category == "define" && ty.name == name && select::api_matches(ty.api.as_deref())
        })
        .and_then(|ty| ty.define_text.as_deref())
        .and_then(|text| numeric_define_value(text, name))
        .unwrap_or_else(|| panic!("unknown numeric define {name}"))
}

fn numeric_define_value(text: &str, name: &str) -> Option<String> {
    let mut words = text.split_whitespace();
    while let Some(word) = words.next() {
        if word == name {
            let value = words.next()?.trim_end_matches(['u', 'U', 'l', 'L']);
            return (!value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()))
                .then(|| value.to_string());
        }
    }
    None
}

fn rust_ident(name: &str) -> String {
    match name {
        "type" => "r#type".to_string(),
        _ => name.to_string(),
    }
}

fn is_primitive(name: &str) -> bool {
    matches!(
        name,
        "void"
            | "char"
            | "float"
            | "double"
            | "int"
            | "int8_t"
            | "uint8_t"
            | "int16_t"
            | "uint16_t"
            | "int32_t"
            | "uint32_t"
            | "int64_t"
            | "uint64_t"
            | "size_t"
    )
}

fn native_types() -> &'static [(&'static str, &'static str)] {
    &[
        ("Display", "c_void"),
        ("VisualID", "c_ulong"),
        ("Window", "c_ulong"),
        ("RROutput", "c_ulong"),
        ("wl_display", "c_void"),
        ("wl_surface", "c_void"),
        ("ubm_device", "c_void"),
        ("ubm_surface", "c_void"),
        ("HINSTANCE", "*mut c_void"),
        ("HWND", "*mut c_void"),
        ("HMONITOR", "*mut c_void"),
        ("HANDLE", "*mut c_void"),
        ("SECURITY_ATTRIBUTES", "c_void"),
        ("DWORD", "u32"),
        ("LPCWSTR", "*const u16"),
        ("xcb_connection_t", "c_void"),
        ("xcb_visualid_t", "u32"),
        ("xcb_window_t", "u32"),
        ("IDirectFB", "c_void"),
        ("IDirectFBSurface", "c_void"),
        ("zx_handle_t", "u32"),
        ("GgpStreamDescriptor", "i32"),
        ("GgpFrameToken", "i32"),
        ("_screen_context", "c_void"),
        ("_screen_window", "c_void"),
        ("_screen_buffer", "c_void"),
        ("NvSciSyncAttrList", "*mut c_void"),
        ("NvSciSyncObj", "*mut c_void"),
        ("NvSciSyncFence", "c_void"),
        ("NvSciBufAttrList", "*mut c_void"),
        ("NvSciBufObj", "*mut c_void"),
        ("ANativeWindow", "c_void"),
        ("AHardwareBuffer", "c_void"),
        ("CAMetalLayer", "c_void"),
        ("MTLDevice_id", "*mut c_void"),
        ("MTLCommandQueue_id", "*mut c_void"),
        ("MTLBuffer_id", "*mut c_void"),
        ("MTLTexture_id", "*mut c_void"),
        ("MTLSharedEvent_id", "*mut c_void"),
        ("IOSurfaceRef", "*mut c_void"),
        ("OHNativeWindow", "c_void"),
        ("OHBufferHandle", "c_void"),
        ("OH_NativeBuffer", "c_void"),
    ]
}

fn is_native(name: &str) -> bool {
    native_types().iter().any(|(native, _)| *native == name)
}