alex/volcanic
Automated vulkan bindings
BRANCH main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
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 ®istry.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)
}