alex/volcanic
Automated vulkan bindings
BRANCH main
SNAPSHOT Improved loading api and made ready for publish
DOWNLOAD
root codegen src select.rs
use crate::registry::{EnumEntry, Registry};
use indexmap::{IndexMap, IndexSet};
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum FeatureExpr {
Feature(String),
All(Vec<FeatureExpr>),
Any(Vec<FeatureExpr>),
Not(Box<FeatureExpr>),
True,
False,
}
impl FeatureExpr {
pub fn feature(name: impl Into<String>) -> Self {
Self::Feature(name.into())
}
pub fn all(exprs: impl IntoIterator<Item = Self>) -> Self {
let mut out = Vec::new();
for expr in exprs {
match expr {
Self::True => {}
Self::False => return Self::False,
Self::All(children) => out.extend(children),
other => out.push(other),
}
}
normalize_operands(&mut out);
let snapshot = out.clone();
out.retain(|expr| {
!snapshot
.iter()
.any(|other| other != expr && implies(other, expr))
});
finish_operands(out, Self::All, Self::True)
}
pub fn any(exprs: impl IntoIterator<Item = Self>) -> Self {
let mut out = Vec::new();
for expr in exprs {
match expr {
Self::True => return Self::True,
Self::False => {}
Self::Any(children) => out.extend(children),
other => out.push(other),
}
}
normalize_operands(&mut out);
let snapshot = out.clone();
out.retain(|expr| {
!snapshot
.iter()
.any(|other| other != expr && implies(expr, other))
});
finish_operands(out, Self::Any, Self::False)
}
pub fn not(expr: Self) -> Self {
match expr {
Self::True => Self::False,
Self::False => Self::True,
Self::Not(inner) => *inner,
other => Self::Not(Box::new(other)),
}
}
pub fn cfg_expr(&self) -> String {
match self {
Self::Feature(feature) => format!("feature = \"{feature}\""),
Self::All(exprs) => format!(
"all({})",
exprs
.iter()
.map(Self::cfg_expr)
.collect::<Vec<_>>()
.join(", ")
),
Self::Any(exprs) => format!(
"any({})",
exprs
.iter()
.map(Self::cfg_expr)
.collect::<Vec<_>>()
.join(", ")
),
Self::Not(expr) => format!("not({})", expr.cfg_expr()),
Self::True => "all()".to_string(),
Self::False => "any()".to_string(),
}
}
pub fn description(&self) -> String {
match self {
Self::Feature(feature) => format!("feature `{feature}`"),
Self::All(exprs) => exprs
.iter()
.map(description_part)
.collect::<Vec<_>>()
.join(" and "),
Self::Any(exprs) => exprs
.iter()
.map(description_part)
.collect::<Vec<_>>()
.join(" or "),
Self::Not(expr) => format!("not {}", description_part(expr)),
Self::True => "no additional features".to_string(),
Self::False => "an unavailable feature requirement".to_string(),
}
}
pub fn is_false(&self) -> bool {
*self == Self::False
}
pub fn implies(&self, other: &Self) -> bool {
implies(self, other)
}
}
fn normalize_operands(exprs: &mut Vec<FeatureExpr>) {
exprs.sort();
exprs.dedup();
}
fn finish_operands(
mut exprs: Vec<FeatureExpr>,
combine: impl FnOnce(Vec<FeatureExpr>) -> FeatureExpr,
identity: FeatureExpr,
) -> FeatureExpr {
match exprs.len() {
0 => identity,
1 => exprs.pop().unwrap(),
_ => combine(exprs),
}
}
fn implies(left: &FeatureExpr, right: &FeatureExpr) -> bool {
if left == right || *right == FeatureExpr::True || *left == FeatureExpr::False {
return true;
}
match (left, right) {
(FeatureExpr::Feature(left), FeatureExpr::Feature(right)) => feature_implies(left, right),
(FeatureExpr::All(left), _) => left.iter().any(|expr| implies(expr, right)),
(FeatureExpr::Any(left), _) => left.iter().all(|expr| implies(expr, right)),
(_, FeatureExpr::All(right)) => right.iter().all(|expr| implies(left, expr)),
(_, FeatureExpr::Any(right)) => right.iter().any(|expr| implies(left, expr)),
(FeatureExpr::Not(left), FeatureExpr::Not(right)) => implies(right, left),
_ => false,
}
}
fn feature_implies(left: &str, right: &str) -> bool {
left == right
|| match (core_feature_level(left), core_feature_level(right)) {
(Some(left), Some(right)) => left >= right,
_ => false,
}
}
fn core_feature_level(feature: &str) -> Option<u8> {
feature.strip_prefix("core_1_")?.parse().ok()
}
fn description_part(expr: &FeatureExpr) -> String {
let description = expr.description();
if matches!(
expr,
FeatureExpr::Feature(_) | FeatureExpr::Not(_) | FeatureExpr::True | FeatureExpr::False
) {
description
} else {
format!("({description})")
}
}
#[derive(Debug, Clone)]
pub struct RequiredEnum {
pub entry: EnumEntry,
pub extension_number: Option<u32>,
pub condition: FeatureExpr,
}
#[derive(Debug)]
pub struct Selection {
pub modules: Vec<SelectedModule>,
}
#[derive(Debug, Clone)]
pub struct SelectedModule {
pub source_name: String,
pub rust_name: String,
pub prerequisite: FeatureExpr,
pub guards: IndexSet<String>,
pub types: IndexMap<String, FeatureExpr>,
pub commands: IndexMap<String, FeatureExpr>,
pub enums: Vec<RequiredEnum>,
}
pub fn select_vulkan(vk: &Registry, video: &Registry) -> Selection {
let mut known_features = IndexSet::new();
for feature in &vk.features {
if api_matches(Some(&feature.api))
&& let Some(rust_name) = core_module_for_feature(&feature.name)
{
known_features.insert(rust_name.to_string());
}
}
for extension in &vk.extensions {
if supported_vulkan(&extension.supported) {
known_features.insert(module_name(&extension.name));
}
}
for extension in &video.extensions {
if supported_vulkan(&extension.supported) {
known_features.insert(module_name(&extension.name));
}
}
let mut modules = Vec::new();
for feature in &vk.features {
if !api_matches(Some(&feature.api)) {
continue;
}
let prerequisite = dependency_expr(feature.depends.as_deref(), &known_features);
if prerequisite.is_false() {
continue;
}
let module_idx = core_module_for_feature(&feature.name)
.map(|rust_name| ensure_module(&mut modules, &feature.name, rust_name));
if feature.apitype.as_deref() != Some("internal")
&& let Some(module_idx) = module_idx
{
modules[module_idx].guards.insert(feature.name.clone());
}
for require in &feature.requires {
if !api_matches(require.api.as_deref()) {
continue;
}
if let Some(module_idx) = module_idx {
let activation = FeatureExpr::feature(modules[module_idx].rust_name.clone());
add_require(
&mut modules[module_idx],
require,
None,
&activation,
&known_features,
);
}
}
}
for extension in &vk.extensions {
if !supported_vulkan(&extension.supported) {
continue;
}
let prerequisite =
extension_requirement_expr(extension.depends.as_deref(), &known_features);
if prerequisite.is_false() {
continue;
}
let rust_name = module_name(&extension.name);
let module_idx = ensure_module(&mut modules, &extension.name, &rust_name);
modules[module_idx].prerequisite = prerequisite;
modules[module_idx].guards.insert(extension.name.clone());
let activation = FeatureExpr::feature(rust_name);
for require in &extension.requires {
if !api_matches(require.api.as_deref()) {
continue;
}
add_require(
&mut modules[module_idx],
require,
Some(extension.number),
&activation,
&known_features,
);
}
}
for extension in &video.extensions {
if !supported_vulkan(&extension.supported) {
continue;
}
let prerequisite = dependency_expr(extension.depends.as_deref(), &known_features);
if prerequisite.is_false() {
continue;
}
let rust_name = module_name(&extension.name);
let module_idx = ensure_module(&mut modules, &extension.name, &rust_name);
modules[module_idx].prerequisite = prerequisite;
modules[module_idx].guards.insert(extension.name.clone());
let activation = FeatureExpr::feature(rust_name);
for require in &extension.requires {
if !api_matches(require.api.as_deref()) {
continue;
}
add_require(
&mut modules[module_idx],
require,
Some(extension.number),
&activation,
&known_features,
);
}
}
Selection { modules }
}
pub fn feature_names(vk: &Registry, video: &Registry) -> Vec<String> {
select_vulkan(vk, video)
.modules
.into_iter()
.map(|module| module.rust_name)
.collect()
}
pub fn api_matches(api: Option<&str>) -> bool {
api.map(|api| {
api.split(',')
.any(|part| matches!(part.trim(), "vulkan" | "vulkanbase"))
})
.unwrap_or(true)
}
fn supported_vulkan(supported: &str) -> bool {
supported
.split(',')
.any(|part| matches!(part.trim(), "vulkan" | "vulkanbase"))
}
pub fn module_name(source: &str) -> String {
source.to_ascii_lowercase()
}
fn core_module_for_feature(name: &str) -> Option<&'static str> {
match name {
"VK_BASE_VERSION_1_0"
| "VK_COMPUTE_VERSION_1_0"
| "VK_GRAPHICS_VERSION_1_0"
| "VK_VERSION_1_0" => Some("core_1_0"),
"VK_BASE_VERSION_1_1"
| "VK_COMPUTE_VERSION_1_1"
| "VK_GRAPHICS_VERSION_1_1"
| "VK_VERSION_1_1" => Some("core_1_1"),
"VK_BASE_VERSION_1_2"
| "VK_COMPUTE_VERSION_1_2"
| "VK_GRAPHICS_VERSION_1_2"
| "VK_VERSION_1_2" => Some("core_1_2"),
"VK_BASE_VERSION_1_3"
| "VK_COMPUTE_VERSION_1_3"
| "VK_GRAPHICS_VERSION_1_3"
| "VK_VERSION_1_3" => Some("core_1_3"),
"VK_BASE_VERSION_1_4"
| "VK_COMPUTE_VERSION_1_4"
| "VK_GRAPHICS_VERSION_1_4"
| "VK_VERSION_1_4" => Some("core_1_4"),
_ => None,
}
}
fn ensure_module(modules: &mut Vec<SelectedModule>, source_name: &str, rust_name: &str) -> usize {
if let Some((idx, _)) = modules
.iter()
.enumerate()
.find(|(_, module)| module.rust_name == rust_name)
{
return idx;
}
modules.push(SelectedModule {
source_name: source_name.to_string(),
rust_name: rust_name.to_string(),
prerequisite: FeatureExpr::True,
guards: IndexSet::new(),
types: IndexMap::new(),
commands: IndexMap::new(),
enums: Vec::new(),
});
modules.len() - 1
}
fn add_require(
module: &mut SelectedModule,
require: &crate::registry::Require,
extension_number: Option<u32>,
activation: &FeatureExpr,
known_features: &IndexSet<String>,
) {
let condition = FeatureExpr::all([
activation.clone(),
dependency_expr(require.depends.as_deref(), known_features),
]);
if condition.is_false() {
return;
}
for name in &require.types {
insert_condition(&mut module.types, name, condition.clone());
}
for name in &require.commands {
insert_condition(&mut module.commands, name, condition.clone());
}
module
.enums
.extend(require.enums.iter().cloned().map(|entry| RequiredEnum {
entry,
extension_number,
condition: condition.clone(),
}));
}
pub fn insert_condition(
map: &mut IndexMap<String, FeatureExpr>,
name: &str,
condition: FeatureExpr,
) -> bool {
match map.get(name) {
Some(existing) => {
let merged = FeatureExpr::any([existing.clone(), condition]);
if *existing == merged {
false
} else {
map.insert(name.to_string(), merged);
true
}
}
None => {
map.insert(name.to_string(), condition);
true
}
}
}
pub fn dependency_expr(depends: Option<&str>, known_features: &IndexSet<String>) -> FeatureExpr {
depends
.map(|depends| Parser::new(depends, known_features).parse())
.unwrap_or(FeatureExpr::True)
}
pub fn extension_requirement_expr(
depends: Option<&str>,
known_features: &IndexSet<String>,
) -> FeatureExpr {
FeatureExpr::all([
FeatureExpr::feature("core_1_0"),
dependency_expr(depends, known_features),
])
}
struct Parser<'a> {
input: &'a [u8],
pos: usize,
known_features: &'a IndexSet<String>,
}
impl<'a> Parser<'a> {
fn new(input: &'a str, known_features: &'a IndexSet<String>) -> Self {
Self {
input: input.as_bytes(),
pos: 0,
known_features,
}
}
fn parse(mut self) -> FeatureExpr {
let value = self.expr();
self.skip_ws();
assert_eq!(
self.pos,
self.input.len(),
"unexpected dependency token at byte {}",
self.pos
);
value
}
fn expr(&mut self) -> FeatureExpr {
let mut value = self.term();
loop {
self.skip_ws();
match self.peek() {
Some(b'+') => {
self.pos += 1;
value = FeatureExpr::all([value, self.term()]);
}
Some(b',') => {
self.pos += 1;
value = FeatureExpr::any([value, self.term()]);
}
_ => return value,
}
}
}
fn term(&mut self) -> FeatureExpr {
self.skip_ws();
match self.peek() {
Some(b'(') => {
self.pos += 1;
let value = self.expr();
self.skip_ws();
assert_eq!(self.peek(), Some(b')'), "unclosed dependency group");
self.pos += 1;
value
}
Some(b'!') => {
self.pos += 1;
FeatureExpr::not(self.term())
}
_ => {
let token = self.token().to_string();
symbol_expr(&token, self.known_features)
}
}
}
fn token(&mut self) -> &str {
self.skip_ws();
let start = self.pos;
while let Some(ch) = self.peek() {
if ch.is_ascii_alphanumeric() || matches!(ch, b'_' | b':') {
self.pos += 1;
} else {
break;
}
}
assert!(self.pos > start, "expected dependency name at byte {start}");
std::str::from_utf8(&self.input[start..self.pos]).unwrap()
}
fn skip_ws(&mut self) {
while self.peek().is_some_and(|ch| ch.is_ascii_whitespace()) {
self.pos += 1;
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
}
fn symbol_expr(symbol: &str, known_features: &IndexSet<String>) -> FeatureExpr {
if symbol.is_empty() || symbol.contains("::") {
return FeatureExpr::True;
}
if let Some(feature) = core_module_for_feature(symbol) {
return FeatureExpr::feature(feature);
}
let feature = module_name(symbol);
if known_features.contains(&feature) {
FeatureExpr::feature(feature)
} else {
FeatureExpr::False
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_malformed_dependencies() {
let known = IndexSet::from(["a".into(), "b".into()]);
for input in ["", "A+", "(A", "A)", "A B", "A|B", "A,,B"] {
assert!(
std::panic::catch_unwind(|| dependency_expr(Some(input), &known)).is_err(),
"accepted {input:?}"
);
}
}
#[test]
fn parses_depends_left_to_right() {
let known_features = IndexSet::from(["a".to_string(), "c".to_string()]);
assert_eq!(
dependency_expr(Some("A+B,C"), &known_features),
FeatureExpr::feature("c")
);
assert_eq!(
dependency_expr(Some("A+(B,C)+D"), &known_features),
FeatureExpr::False
);
assert_eq!(
dependency_expr(Some("A+VkFoo::bar"), &known_features),
FeatureExpr::feature("a")
);
}
#[test]
fn renders_dependency_cfgs() {
let known_features = IndexSet::from([
"vk_ext_a".to_string(),
"vk_ext_b".to_string(),
"vk_ext_c".to_string(),
]);
let expr = dependency_expr(Some("VK_EXT_a+(VK_EXT_b,VK_EXT_c)"), &known_features);
assert_eq!(
expr.cfg_expr(),
"all(feature = \"vk_ext_a\", any(feature = \"vk_ext_b\", feature = \"vk_ext_c\"))"
);
}
#[test]
fn keeps_conditions_on_extension_require_blocks() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let vk = Registry::load(manifest_dir.join("../spec/vk.xml")).unwrap();
let video = Registry::load(manifest_dir.join("../spec/video.xml")).unwrap();
let selection = select_vulkan(&vk, &video);
let maintenance11 = selection
.modules
.iter()
.find(|module| module.rust_name == "vk_khr_maintenance11")
.unwrap();
let condition = |name: &str| {
&maintenance11
.enums
.iter()
.find(|required| required.entry.name == name)
.unwrap()
.condition
};
assert_eq!(
condition("VK_SHADER_CREATE_INDEPENDENT_SETS_BIT_KHR"),
&FeatureExpr::all([
FeatureExpr::feature("vk_khr_maintenance11"),
FeatureExpr::feature("vk_ext_shader_object"),
])
);
assert_eq!(
condition("VK_IMAGE_CREATE_2_ALIAS_SINGLE_LAYER_DESCRIPTOR_BIT_KHR"),
&FeatureExpr::all([
FeatureExpr::feature("vk_khr_maintenance11"),
FeatureExpr::feature("vk_khr_extended_flags"),
])
);
}
#[test]
fn simplifies_implied_feature_conditions() {
let core = FeatureExpr::feature("core_1_1");
let extension = FeatureExpr::feature("vk_ext_example");
assert_eq!(
FeatureExpr::any([
core.clone(),
FeatureExpr::all([core.clone(), extension.clone()]),
]),
core
);
assert_eq!(
FeatureExpr::all([
FeatureExpr::feature("core_1_0"),
FeatureExpr::feature("core_1_4"),
]),
FeatureExpr::feature("core_1_4")
);
}
#[test]
fn derives_module_names() {
assert_eq!(module_name("VK_KHR_surface"), "vk_khr_surface");
assert_eq!(
module_name("vulkan_video_codec_h264std_decode"),
"vulkan_video_codec_h264std_decode"
);
}
}