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 / types.rs

use super::*;

pub(super) fn emit_type_aliases(
    out: &mut String,
    ctx: &mut Context,
    registry: &Registry,
    is_vk: bool,
    names: &IndexMap<String, select::FeatureExpr>,
) {
    let section = section(out, if is_vk { "Vulkan types" } else { "Video types" });
    for ty in &registry.types {
        if !names.contains_key(&ty.name)
            || !select::api_matches(ty.api.as_deref())
            || is_native(&ty.name)
        {
            continue;
        }
        if is_vk && ctx.video_type_names.contains(&ty.name) {
            continue;
        }

        if let Some(alias) = &ty.alias {
            emit_type(
                out,
                ctx,
                &ty.name,
                &format!("pub type {} = {alias};\n", ty.name),
            );
            continue;
        }

        match ty.category.as_str() {
            "include" | "define" => {}
            "none" => {
                if !is_primitive(&ty.name)
                    && !ty
                        .requires
                        .as_deref()
                        .is_some_and(|r| r.starts_with("vk_video/"))
                {
                    emit_type(
                        out,
                        ctx,
                        &ty.name,
                        &format!("pub type {} = c_void;\n", ty.name),
                    );
                }
            }
            "basetype" => emit_basetype(out, ctx, ty),
            "bitmask" => {
                if is_vk {
                    emit_bitflags_type(out, ctx, registry, ty);
                } else {
                    let base = ty.underlying_type.as_deref().unwrap_or("VkFlags");
                    emit_type(
                        out,
                        ctx,
                        &ty.name,
                        &format!("pub type {} = {};\n", ty.name, rust_base(base)),
                    );
                }
            }
            "handle" => emit_handle(out, ctx, ty),
            "enum" => {
                if is_vk && let Some(flags_type) = ctx.bit_enums_to_flags.get(&ty.name).cloned() {
                    emit_type(
                        out,
                        ctx,
                        &ty.name,
                        &format!("pub type {} = {flags_type};\n", ty.name),
                    );
                } else if ctx.rust_enums.contains_key(&ty.name) {
                    emit_rust_enum(out, ctx, &ty.name);
                } else {
                    let rust_ty = ctx.enum_widths.get(&ty.name).copied().unwrap_or("i32");
                    emit_type(
                        out,
                        ctx,
                        &ty.name,
                        &format!("pub type {} = {rust_ty};\n", ty.name),
                    );
                }
            }
            "struct" | "union" | "funcpointer" => {}
            other => panic!("unsupported type category {other} for {}", ty.name),
        }
    }
    section.finish(out);
}

pub(super) fn emit_rust_enum(out: &mut String, ctx: &mut Context, name: &str) {
    if !ctx.emitted_types.insert(name.to_string()) {
        return;
    }
    let info = ctx
        .rust_enums
        .get(name)
        .unwrap_or_else(|| panic!("unknown Rust enum {name}"));
    emit_cfg(out, ctx.type_condition(name), "");
    out.push_str("#[repr(transparent)]\n");
    out.push_str("#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\n");
    out.push_str(&format!("pub struct {name}(i32);\n"));
    emit_cfg(out, ctx.type_condition(name), "");
    out.push_str(&format!("impl {name} {{\n"));
    out.push_str("    pub const fn from_raw(value: i32) -> Self { Self(value) }\n");
    out.push_str("    pub const fn as_raw(self) -> i32 { self.0 }\n");
    for (constant_name, constant) in &info.constants {
        emit_nested_cfg(out, &constant.condition, ctx.type_condition(name), "    ");
        out.push_str(&format!(
            "    pub const {}: Self = Self({});\n",
            constant_name, constant.value
        ));
    }
    out.push_str("}\n");
}

pub(super) fn emit_basetype(out: &mut String, ctx: &mut Context, ty: &TypeDef) {
    if let Some(decl) = &ty.underlying_decl {
        let rust_ty = rust_decl_type(decl, false);
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = {rust_ty};\n", ty.name),
        );
    } else if let Some(base) = &ty.underlying_type {
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = {};\n", ty.name, rust_base(base)),
        );
    } else {
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = c_void;\n", ty.name),
        );
    }
}

pub(super) fn emit_bitflags_type(
    out: &mut String,
    ctx: &mut Context,
    registry: &Registry,
    ty: &TypeDef,
) {
    let Some(info) = ctx.bitmasks.get(&ty.name).cloned() else {
        let base = ty.underlying_type.as_deref().unwrap_or("VkFlags");
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = {};\n", ty.name, rust_base(base)),
        );
        return;
    };

    if !ctx.emitted_types.insert(ty.name.clone()) {
        return;
    }

    emit_cfg(out, ctx.type_condition(&ty.name), "");
    out.push_str("crate::bitflags::bitflags! {\n");
    out.push_str("    #[repr(transparent)]\n");
    out.push_str("    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\n");
    out.push_str(&format!(
        "    pub struct {}: {} {{\n",
        ty.name, info.bits_ty
    ));

    if let Some(bits_name) = &info.bits_name
        && let Some(group) = registry
            .enum_groups
            .iter()
            .find(|group| group.name.as_deref() == Some(bits_name.as_str()))
    {
        for entry in &group.entries {
            if !select::api_matches(entry.api.as_deref()) {
                continue;
            }
            let assoc_name = ctx
                .flag_constants
                .get(&entry.name)
                .map(|constant| constant.assoc_name.as_str())
                .unwrap_or_else(|| panic!("unknown flag constant {}", entry.name));
            let value = bitflag_bits_value(ctx, entry, Some(bits_name), None, Some(&ty.name));
            emit_nested_cfg(
                out,
                ctx.enum_condition(&entry.name),
                ctx.type_condition(&ty.name),
                "        ",
            );
            out.push_str(&format!("        const {assoc_name} = {value};\n"));
        }
    }

    out.push_str("        const _ = !0;\n");
    out.push_str("    }\n");
    out.push_str("}\n");
}

pub(super) fn emit_handle(out: &mut String, ctx: &mut Context, ty: &TypeDef) {
    if ty
        .handle_macro
        .as_deref()
        .is_some_and(|name| name == "VK_DEFINE_NON_DISPATCHABLE_HANDLE")
    {
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = u64;\n", ty.name),
        );
    } else {
        let tag = format!("{}_T", ty.name);
        emit_type(
            out,
            ctx,
            &tag,
            &format!("#[repr(C)]\npub struct {tag} {{ _private: [u8; 0] }}\n"),
        );
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!("pub type {} = *mut {tag};\n", ty.name),
        );
    }
}

pub(super) fn emit_enum_constants(
    out: &mut String,
    ctx: &mut Context,
    enums: &IndexMap<String, ResolvedEnum>,
) {
    let section = section(out, "Constants");
    for required in enums.values() {
        if ctx.flag_constants.contains_key(&required.entry.name) {
            emit_bitflag_extension_entry(
                out,
                ctx,
                &required.entry,
                required.group_name.as_deref(),
                required.extension_number,
            );
        } else {
            emit_enum_entry(
                out,
                ctx,
                &required.entry,
                required.group_name.as_deref(),
                required.extension_number,
            );
        }
    }
    section.finish(out);
}

pub(super) fn emit_enum_entry(
    out: &mut String,
    ctx: &mut Context,
    entry: &EnumEntry,
    group_name: Option<&str>,
    extension_number: Option<u32>,
) {
    if ctx.emitted_consts.contains(&entry.name) || !select::api_matches(entry.api.as_deref()) {
        return;
    }
    if group_name.is_some_and(|name| ctx.rust_enums.contains_key(name)) {
        return;
    }
    let const_ty = const_type(ctx, entry, group_name);
    let value = const_value(ctx, entry, group_name, extension_number);
    emit_const(out, ctx, &entry.name, &const_ty, &value);
}

pub(super) fn emit_bitflag_extension_entry(
    out: &mut String,
    ctx: &mut Context,
    entry: &EnumEntry,
    group_name: Option<&str>,
    extension_number: Option<u32>,
) {
    if ctx.bitflag_macro_entries.contains(&entry.name) || !select::api_matches(entry.api.as_deref())
    {
        return;
    }
    let constant = ctx
        .flag_constants
        .get(&entry.name)
        .cloned()
        .unwrap_or_else(|| panic!("unknown flag constant {}", entry.name));
    let key = format!("{}::{}", constant.flags_type, constant.assoc_name);
    if !ctx.emitted_flag_consts.insert(key) {
        return;
    }
    let value = bitflag_bits_value(
        ctx,
        entry,
        group_name,
        extension_number,
        Some(&constant.flags_type),
    );
    emit_cfg(out, ctx.enum_condition(&entry.name), "");
    out.push_str(&format!("impl {} {{\n", constant.flags_type));
    out.push_str(&format!(
        "    pub const {}: Self = Self::from_bits_retain({value});\n",
        constant.assoc_name
    ));
    out.push_str("}\n");
}

pub(super) fn emit_records(
    out: &mut String,
    ctx: &mut Context,
    registry: &Registry,
    is_vk: bool,
    names: &IndexMap<String, select::FeatureExpr>,
) {
    let section = section(
        out,
        if is_vk {
            "Vulkan records"
        } else {
            "Video records"
        },
    );
    for ty in &registry.types {
        if !names.contains_key(&ty.name)
            || !select::api_matches(ty.api.as_deref())
            || ty.alias.is_some()
        {
            continue;
        }
        if !matches!(ty.category.as_str(), "struct" | "union") {
            continue;
        }
        if is_vk && ctx.video_type_names.contains(&ty.name) {
            continue;
        }
        if ctx.emitted_types.contains(&ty.name) {
            continue;
        }

        emit_cfg(out, ctx.type_condition(&ty.name), "");
        out.push_str("#[repr(C)]\n#[derive(Copy, Clone)]\n");
        out.push_str(&format!("pub {} {} {{\n", ty.category, ty.name));
        emit_fields(out, ty);
        out.push_str("}\n");
        ctx.emitted_types.insert(ty.name.clone());
    }
    section.finish(out);
}

pub(super) fn emit_fields(out: &mut String, ty: &TypeDef) {
    let mut bitfield_index = 0usize;
    let mut bitfield_width = 0u32;

    for member in &ty.members {
        if !select::api_matches(member.api.as_deref()) {
            continue;
        }
        if let Some(width) = member.bit_width {
            if bitfield_width + width > 32 {
                bitfield_index += 1;
                out.push_str(&format!("    pub _bitfield_{bitfield_index}: u32,\n"));
                bitfield_width = 0;
            }
            bitfield_width += width;
            continue;
        }

        if bitfield_width > 0 {
            bitfield_index += 1;
            out.push_str(&format!("    pub _bitfield_{bitfield_index}: u32,\n"));
            bitfield_width = 0;
        }

        out.push_str(&format!(
            "    pub {}: {},\n",
            rust_ident(&member.name),
            rust_decl_type(member, false)
        ));
    }

    if bitfield_width > 0 {
        bitfield_index += 1;
        out.push_str(&format!("    pub _bitfield_{bitfield_index}: u32,\n"));
    }
}

pub(super) fn emit_func_pointers(
    out: &mut String,
    ctx: &mut Context,
    registry: &Registry,
    names: &IndexMap<String, select::FeatureExpr>,
) {
    let section = section(out, "Callback function pointers");
    for ty in &registry.types {
        if !names.contains_key(&ty.name)
            || ty.category != "funcpointer"
            || !select::api_matches(ty.api.as_deref())
        {
            continue;
        }
        if ty.name == "PFN_vkVoidFunction" {
            emit_type(
                out,
                ctx,
                &ty.name,
                "pub type PFN_vkVoidFunction = Option<unsafe extern \"system\" fn() -> ()>;\n",
            );
            continue;
        }
        let func = ty.func.as_ref().unwrap();
        let ret = rust_decl_type(&func.proto, true);
        let params = params(&func.params);
        emit_type(
            out,
            ctx,
            &ty.name,
            &format!(
                "pub type {} = Option<unsafe extern \"system\" fn({params}) -> {ret}>;\n",
                ty.name
            ),
        );
    }
    section.finish(out);
}

pub(super) fn emit_command_pointers(
    out: &mut String,
    ctx: &mut Context,
    vk: &Registry,
    selected_commands: &IndexMap<String, select::FeatureExpr>,
) {
    let section = section(out, "Vulkan command function pointers");
    let commands: IndexMap<_, _> = vk
        .commands
        .iter()
        .map(|command| (command.name.as_str(), command))
        .collect();
    for command in &vk.commands {
        if !selected_commands.contains_key(&command.name)
            || !select::api_matches(command.api.as_deref())
        {
            continue;
        }
        let signature = resolve_command_signature(command, &commands);
        let ret = rust_decl_type(signature.proto.as_ref().unwrap(), true);
        let params = params(&signature.params);
        let pfn = format!("PFN_{}", command.name);
        emit_type(
            out,
            ctx,
            &pfn,
            &format!("pub type {pfn} = unsafe extern \"system\" fn({params}) -> {ret};\n"),
        );
    }
    section.finish(out);
}

pub(super) fn resolve_command_signature<'a>(
    command: &'a Command,
    commands: &'a IndexMap<&str, &'a Command>,
) -> &'a Command {
    if let Some(alias) = &command.alias {
        commands
            .get(alias.as_str())
            .copied()
            .unwrap_or_else(|| panic!("unknown command alias target {alias}"))
    } else {
        command
    }
}