#![allow(clippy::from_str_radix_10)]
#![allow(clippy::field_reassign_with_default)]
use core::fmt::Write;
use std::path::PathBuf;
use clap::Parser;
use magnesium::{
skip_comments, skip_empty_text_elements, trim_text, ElementIterator,
TagAttributeIterator,
XmlElement::{self, *},
};
#[derive(Parser, Debug)]
struct Args {
#[arg(long)]
xml: PathBuf,
#[arg(long)]
api: String,
#[arg(long)]
name: String,
#[arg(long)]
number: String,
#[arg(long)]
ext: Vec<String>,
}
macro_rules! list_features_and_return {
($features:expr) => {{
println!("Available features include:");
for f in $features.iter() {
let api = &f.api;
let name = &f.name;
let number = &f.number;
println!("* api:{api}, name:{name}, number:{number}");
}
return;
}};
}
fn main() {
let package_version = env!("CARGO_PKG_VERSION");
let args = Args::parse();
println!("// file generated by `phosphorus-{package_version}`",);
println!("// {args:?}");
println!();
println!("//! Module for interfacing with `{}`.", args.name);
println!("//! ");
println!("//! Before actually calling any GL function, you must first");
println!("//! load the GL function pointers using [`load_gl_functions`].");
println!("//! Always do this *after* your GL context has been created.");
println!("//! Function pointers are loaded into global atomic variables,");
println!("//! so all GL contexts across all threads must use compatible");
println!("//! pointers. In practice, if all contexts are created using the");
println!("//! same context creation parameters they will have compatible");
println!("//! function pointers.");
println!("//! ");
println!("//! On some platforms it's possible to load an extension function");
println!("//! without the extension being supported for your context.");
println!("//! Always check that your context supports an extension before");
println!("//! calling any extension function.");
println!("//! ");
println!("//! Calling any GL function that isn't loaded will cause a panic.");
let gl_xml = std::fs::read_to_string(&args.xml).unwrap();
let mut elem_iter = ElementIterator::new(&gl_xml)
.filter_map(skip_comments)
.map(trim_text)
.filter_map(skip_empty_text_elements);
let registry = match elem_iter.next().unwrap() {
StartTag { name: "registry", attrs: "" } => Registry::new(&mut elem_iter),
_ => panic!("couldn't read the `registry` element"),
};
let api_entries: Vec<&Feature> =
registry.features.iter().filter(|f| f.api == args.api).collect();
if api_entries.is_empty() {
println!("No features found with that API.");
list_features_and_return!(registry.features);
}
let mut output_enumeration_names = Vec::new();
let mut output_command_names = Vec::new();
let mut feature_name_and_number_match = false;
for feature in api_entries.iter() {
for requirement in feature.requirements.iter() {
if let Some(api) = requirement.api.as_ref() {
if api.as_str() != args.api.as_str() {
continue;
}
}
if let Some(profile) = requirement.profile.as_ref() {
if profile.as_str() != "core" {
continue;
}
}
output_enumeration_names.extend_from_slice(&requirement.enumerations);
output_command_names.extend_from_slice(&requirement.commands);
}
for removal in feature.removals.iter() {
if let Some(api) = removal.api.as_ref() {
if api.as_str() != args.api.as_str() {
continue;
}
}
if let Some(profile) = removal.profile.as_ref() {
if profile.as_str() != "core" {
continue;
}
}
for enumeration in removal.enumerations.iter() {
if let Some(index) =
output_enumeration_names.iter().position(|e| e == enumeration)
{
output_enumeration_names.remove(index);
}
}
for command in removal.commands.iter() {
if let Some(index) =
output_command_names.iter().position(|e| e == command)
{
output_command_names.remove(index);
}
}
}
if feature.name == args.name && feature.number == args.number {
feature_name_and_number_match = true;
break;
}
}
if !feature_name_and_number_match {
println!("Could not find an exact feature match!");
list_features_and_return!(registry.features);
}
for ex in args.ext.iter() {
let extension: &Extension =
match registry.extensions.iter().find(|x| &x.name == ex) {
Some(x) => x,
None => {
println!("Requested extension `{ex}` is not available.");
return;
}
};
if !extension.supported.contains(&args.api) {
println!(
"Found extension `{ex}`, but it isn't supported by API `{}`.",
args.api
);
return;
}
for requirement in extension.requirements.iter() {
if let Some(api) = requirement.api.as_ref() {
if api.as_str() != args.api.as_str() {
continue;
}
}
if let Some(profile) = requirement.profile.as_ref() {
if profile.as_str() != "core" {
continue;
}
}
output_enumeration_names.extend_from_slice(&requirement.enumerations);
output_command_names.extend_from_slice(&requirement.commands);
}
}
output_enumeration_names.sort();
output_command_names.sort();
let mut output_enumerations = Vec::new();
let mut output_commands = Vec::new();
for output_enumeration_name in output_enumeration_names {
if let Some(enumeration) = registry.enumerations.iter().find(|e| {
e.name == output_enumeration_name
&& (match e.api.as_ref() {
Some(e_api) => e_api.as_str() == args.api,
None => true,
})
}) {
output_enumerations.push(enumeration.clone());
} else {
println!("Registry error: `{}` is part of the requested feature, but not defined in the registry.",
output_enumeration_name);
return;
}
}
for output_command_name in output_command_names {
if let Some(command) =
registry.commands.iter().find(|c| c.name == output_command_name)
{
output_commands.push(command.clone());
} else {
println!("Registry error: `{}` is part of the requested feature, but not defined in the registry.",
output_command_name);
return;
}
}
output_enumerations.dedup_by_key(|e| e.name.clone());
output_commands.dedup_by_key(|c| c.name.clone());
println!("{}", phosphorus::GL_CORE_TYPES);
output_enumerations.iter().for_each(|e| e.print_rust_const());
output_commands.iter().for_each(|c| c.print_global_wrapper_fn());
print_global_loader(&output_commands);
}
fn burn_until<'a>(
end: &str, elem_iter: &mut impl Iterator<Item = XmlElement<'a>>,
) {
loop {
if let EndTag { name } = elem_iter.next().unwrap() {
if end == name {
return;
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Registry {
pub enumerations: Vec<Enumeration>,
pub commands: Vec<Command>,
pub features: Vec<Feature>,
pub extensions: Vec<Extension>,
}
impl Registry {
pub fn new<'a>(mut elem_iter: impl Iterator<Item = XmlElement<'a>>) -> Self {
let mut registry = Self::default();
loop {
match elem_iter.next().unwrap() {
EndTag { name: "registry" } => return registry,
StartTag { name: "comment", attrs: "" } => {
burn_until("comment", &mut elem_iter)
}
StartTag { name: "types", attrs: "" } => {
burn_until("types", &mut elem_iter)
}
EmptyTag { name: "enums", attrs: _ } => {
}
StartTag { name: "enums", attrs } => 'enums: loop {
let is_bitmask = TagAttributeIterator::new(attrs)
.any(|attr| attr.key == "type" && attr.value == "bitmask");
match elem_iter.next().unwrap() {
EndTag { name: "enums" } => break 'enums,
EmptyTag { name: "unused", attrs: _ } => (),
EmptyTag { name: "enum", attrs } => {
let mut enumeration = Enumeration::default();
enumeration.is_bitmask = is_bitmask;
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"value" => enumeration.value = attr.value.to_string(),
"name" => enumeration.name = attr.value.to_string(),
"group" => enumeration.group = Some(attr.value.to_string()),
"alias" => enumeration.alias = Some(attr.value.to_string()),
"type" => enumeration.type_ = Some(attr.value.to_string()),
"api" => enumeration.api = Some(attr.value.to_string()),
"comment" => (),
other => panic!("{other:?}"),
}
}
registry.enumerations.push(enumeration);
}
other => panic!("{other:?}"),
}
},
StartTag { name: "commands", attrs: r#"namespace="GL""# } => {
'commands: loop {
match elem_iter.next().unwrap() {
EndTag { name: "commands" } => {
break 'commands;
}
StartTag { name: "command", attrs } => {
registry.commands.push(Command::new(&mut elem_iter, attrs))
}
other => panic!("{other:?}"),
}
}
}
StartTag { name: "feature", attrs } => {
registry.features.push(Feature::new(&mut elem_iter, attrs))
}
StartTag { name: "extensions", attrs: "" } => 'extensions: loop {
match elem_iter.next().unwrap() {
EndTag { name: "extensions" } => break 'extensions,
EmptyTag { name: "extension", attrs } => {
let mut extension = Extension::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => extension.name = attr.value.to_string(),
"supported" => extension.supported = attr.value.to_string(),
other => panic!("{other:?}"),
}
}
registry.extensions.push(extension);
}
StartTag { name: "extension", attrs } => {
registry.extensions.push(Extension::new(&mut elem_iter, attrs))
}
other => panic!("{other:?}"),
}
},
other => panic!("{other:?}"),
}
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Enumeration {
pub name: String,
pub value: String,
pub group: Option<String>,
pub alias: Option<String>,
pub type_: Option<String>,
pub api: Option<String>,
pub is_bitmask: bool,
}
impl Enumeration {
pub fn print_rust_const(&self) {
#[cfg(FALSE)]
if let Some(group) = self.group.as_ref() {
let s = if group.contains(',') { "s" } else { "" };
print!("/// * Group{s}: ");
for (i, entry) in group.split(',').enumerate() {
if i != 0 {
print!(", ");
}
print!("`{entry}`");
}
println!();
}
if self.name.chars().any(|c| c.is_ascii_lowercase()) {
println!("#[allow(non_upper_case_globals)]");
}
let name = &self.name;
let type_ = match self.type_.as_deref() {
None => {
if self.value.starts_with('-') {
"i32"
} else {
"u32"
}
}
Some("u") => "u32",
Some("ull") => "u64",
Some(other) => panic!("unknown enumeration type: `{other}`"),
};
let expr = &self.value;
println!("pub const {name}: {type_} = {expr};");
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Command {
pub name: String,
pub return_ty: String,
pub return_group: Option<String>,
pub return_class: Option<String>,
pub params: Vec<Param>,
pub alias: Option<String>,
}
impl Command {
pub fn new<'a>(
elem_iter: &mut impl Iterator<Item = XmlElement<'a>>, attrs: &str,
) -> Self {
let mut command = Command::default();
for attr in TagAttributeIterator::new(attrs) {
if attr.key == "comment" {
continue;
}
println!("command_attr: {attr:?}");
}
loop {
match elem_iter.next().unwrap() {
EndTag { name: "command" } => {
return command;
}
StartTag { name: "proto", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"group" => command.return_group = Some(attr.value.to_string()),
"class" => command.return_class = Some(attr.value.to_string()),
other => panic!("{other:?}"),
}
}
match elem_iter.next().unwrap() {
Text("void") => command.return_ty = "()".to_string(),
Text("void *") => command.return_ty = "*mut void".to_string(),
Text("const") => {
assert_eq!(
elem_iter.next().unwrap(),
StartTag { name: "ptype", attrs: "" }
);
let t = match elem_iter.next().unwrap() {
Text(t) => t.to_string(),
other => panic!("{other:?}"),
};
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "ptype" });
assert_eq!(elem_iter.next().unwrap(), Text("*"));
command.return_ty = format!("*const {t}");
}
StartTag { name: "ptype", attrs: "" } => {
match elem_iter.next().unwrap() {
Text(t) => command.return_ty = t.to_string(),
other => panic!("{other:?}"),
}
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "ptype" });
}
other => panic!("{other:?}"),
}
match elem_iter.next().unwrap() {
StartTag { name: "name", attrs: "" } => {
match elem_iter.next().unwrap() {
Text(t) => command.name = t.to_string(),
other => panic!("{other:?}"),
}
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "name" });
}
other => panic!("{other:?}"),
}
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "proto" });
}
StartTag { name: "param", attrs } => {
let mut param = Param::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"group" => param.group = Some(attr.value.to_string()),
"class" => param.class = Some(attr.value.to_string()),
"len" => param.len = Some(attr.value.to_string()),
other => panic!("{other:?}"),
}
}
'param: loop {
match elem_iter.next().unwrap() {
EndTag { name: "param" } => {
command.params.push(param);
break 'param;
}
StartTag { name: "ptype", attrs: "" } => {
match elem_iter.next().unwrap() {
Text(t) => param.param_ty = t.to_string(),
other => panic!("{other:?}"),
}
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "ptype" });
}
Text("const") => {
assert_eq!(
elem_iter.next().unwrap(),
StartTag { name: "ptype", attrs: "" }
);
let t = match elem_iter.next().unwrap() {
Text(t) => t.to_string(),
other => panic!("{other:?}"),
};
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "ptype" });
match elem_iter.next().unwrap() {
Text("*") => param.param_ty = format!("*const {t}"),
Text("*const*") => {
param.param_ty = format!("*const *const {t}")
}
Text("**") => param.param_ty = format!("*const *mut {t}"),
other => panic!("{other:?}"),
}
}
Text("const void *") => {
param.param_ty = "*const void".to_string();
}
Text("const void **") => {
param.param_ty = "*const *mut void".to_string();
}
Text("void *") => {
param.param_ty = "*mut void".to_string();
}
Text("void **") => {
param.param_ty = "*mut *mut void".to_string();
}
Text("const void *const*") => {
param.param_ty = "*const *const void".to_string();
}
Text("*") => {
param.param_ty = format!("*mut {}", param.param_ty);
}
StartTag { name: "name", attrs: "" } => {
match elem_iter.next().unwrap() {
Text(t) => param.name = t.to_string(),
other => panic!("{other:?}"),
}
assert_eq!(elem_iter.next().unwrap(), EndTag { name: "name" });
}
other => panic!("{other:?}, Command: {command:?}"),
}
}
}
EmptyTag { name: "glx", .. } => (),
EmptyTag { name: "vecequiv", .. } => (),
EmptyTag { name: "alias", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => command.alias = Some(attr.value.to_string()),
other => panic!("{other:?}"),
}
}
}
other => panic!("{other:?}"),
}
}
}
pub(crate) fn print_global_wrapper_fn(&self) {
let name = &self.name;
let static_name = format!("{name}_p");
let ret_ty = &self.return_ty;
let mut arg_name_and_type_list = String::new();
let mut fn_ptr_ty = "unsafe extern \"system\" fn(".to_string();
let mut arg_name_list = String::new();
for param in self.params.iter() {
let param_name = match param.name.as_str() {
"type" => "type_",
"ref" => "ref_",
other => other,
};
let param_ty = ¶m.param_ty;
write!(arg_name_and_type_list, "{param_name}: {param_ty}, ").ok();
write!(fn_ptr_ty, "{},", param.param_ty).ok();
write!(arg_name_list, "{param_name}, ").ok();
}
write!(fn_ptr_ty, ")->{ret_ty}").ok();
println!(
"
static {static_name}: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[inline]
pub unsafe fn {name}({arg_name_and_type_list}) -> {ret_ty} {{
let u: usize = {static_name}.load(core::sync::atomic::Ordering::Acquire);
assert!(u != 0);
let _func_p: {fn_ptr_ty} = unsafe {{ core::mem::transmute(u) }};
_func_p({arg_name_list})
}}"
);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Param {
pub param_ty: String,
pub name: String,
pub group: Option<String>,
pub class: Option<String>,
pub len: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub enum Profile {
Core,
Compatibility,
}
#[derive(Debug, Clone, Default)]
pub struct Requirement {
api: Option<String>,
profile: Option<String>,
enumerations: Vec<String>,
commands: Vec<String>,
}
impl Requirement {
pub fn new<'a>(
elem_iter: &mut impl Iterator<Item = XmlElement<'a>>, attrs: &str,
) -> Self {
let mut requirement = Requirement::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"comment" => (),
"api" => requirement.api = Some(attr.value.to_string()),
"profile" => requirement.profile = Some(attr.value.to_string()),
other => panic!("{other:?}"),
}
}
loop {
match elem_iter.next().unwrap() {
EndTag { name: "require" } => return requirement,
EmptyTag { name: "enum", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => requirement.enumerations.push(attr.value.to_string()),
"comment" => (),
other => panic!("{other:?}"),
}
}
}
EmptyTag { name: "command", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => requirement.commands.push(attr.value.to_string()),
"comment" => (),
other => panic!("{other:?}"),
}
}
}
EmptyTag { name: "type", attrs: _ } => (),
other => panic!("{other:?}"),
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Removal {
api: Option<String>,
profile: Option<String>,
enumerations: Vec<String>,
commands: Vec<String>,
}
impl Removal {
pub fn new<'a>(
elem_iter: &mut impl Iterator<Item = XmlElement<'a>>, attrs: &str,
) -> Self {
let mut removal = Removal::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"comment" => (),
"api" => removal.api = Some(attr.value.to_string()),
"profile" => removal.profile = Some(attr.value.to_string()),
other => panic!("{other:?}"),
}
}
loop {
match elem_iter.next().unwrap() {
EndTag { name: "remove" } => return removal,
EmptyTag { name: "enum", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => removal.enumerations.push(attr.value.to_string()),
"comment" => (),
other => panic!("{other:?}"),
}
}
}
EmptyTag { name: "command", attrs } => {
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => removal.commands.push(attr.value.to_string()),
"comment" => (),
other => panic!("{other:?}"),
}
}
}
EmptyTag { name: "type", attrs: _ } => (),
other => panic!("{other:?}"),
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Feature {
pub api: String,
pub name: String,
pub number: String,
pub requirements: Vec<Requirement>,
pub removals: Vec<Removal>,
}
impl Feature {
pub fn new<'a>(
elem_iter: &mut impl Iterator<Item = XmlElement<'a>>, attrs: &str,
) -> Self {
let mut feature = Feature::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"api" => feature.api = attr.value.to_string(),
"name" => feature.name = attr.value.to_string(),
"number" => feature.number = attr.value.to_string(),
other => panic!("{other:?}"),
}
}
loop {
match elem_iter.next().unwrap() {
EndTag { name: "feature" } => return feature,
EmptyTag { name: "require", .. } => (),
StartTag { name: "require", attrs } => {
feature.requirements.push(Requirement::new(elem_iter, attrs));
}
StartTag { name: "remove", attrs } => {
feature.removals.push(Removal::new(elem_iter, attrs));
}
other => panic!("{other:?}"),
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Extension {
pub name: String,
pub supported: String,
pub requirements: Vec<Requirement>,
}
impl Extension {
pub fn new<'a>(
elem_iter: &mut impl Iterator<Item = XmlElement<'a>>, attrs: &str,
) -> Self {
let mut extension = Extension::default();
for attr in TagAttributeIterator::new(attrs) {
match attr.key {
"name" => extension.name = attr.value.to_string(),
"supported" => extension.supported = attr.value.to_string(),
"comment" => (),
other => panic!("{other:?}"),
}
}
loop {
match elem_iter.next().unwrap() {
EndTag { name: "extension" } => return extension,
StartTag { name: "require", attrs } => {
extension.requirements.push(Requirement::new(elem_iter, attrs))
}
other => panic!("{other:?}"),
}
}
}
}
fn print_global_loader(commands: &[Command]) {
let mut command_info = String::new();
for command in commands.iter() {
let name = &command.name;
writeln!(command_info, "(\"{name}\\0\", &{name}_p),").ok();
}
println!(
"
/// Loads all GL function pointers.
///
/// In general, one cannot statically or dynamically link to GL functions.
/// The functions are often specific to a particular GL context, and so they
/// can only be loaded at runtime, once a GL context has been created and
/// made current.
///
/// This module stores one `static` address value per GL function. When you
/// call a GL function from this module, it loads the address out of the
/// static, and if the address is non-null it calls that address (if the
/// address is null it will panic).
///
/// Calling `load_gl_functions` allows you to initialize all of the static
/// address values. This should generally be done just once, after your GL
/// context is created and made current.
///
/// ## Safety
/// * The `load_fn` you provide is passed the start of a zero-terminated
/// string naming each GL command. It must use this pointer to load that
/// GL command's pointer using the appropriate platform function, then
/// return that value.
/// * When the platform loader fails to load a GL function it will generally
/// return the null address, though error addresses of 1, 2, 3, and -1
/// have also been seen on some systems. All of these addresses are
/// treated as failed loads, and will store null into the `static`.
///
/// ## Failure
/// * Returns `Ok` when all functions load successfully. Otherwise you will
/// get an `Err` with a list of all functions that failed to load.
/// * The loading process does not \"early return\". It will always attempt
/// to load all functions, only returning the list of errors at the end.
pub unsafe fn load_gl_functions(load_fn: &dyn Fn(*const u8) -> *const void) -> Result<(), Vec<&'static str>> {{
let command_info = &[{command_info}];
let mut load_failures = Vec::new();
for (command_name, command_p) in command_info.iter() {{
let addr: usize = match load_fn(command_name.as_ptr()) as usize {{
0 | 1 | 2 | 3 | usize::MAX => {{
load_failures.push(&command_name[..command_name.len()-1]);
0
}}
other => other
}};
command_p.store(addr, core::sync::atomic::Ordering::Release);
}}
if load_failures.is_empty() {{
Ok( () )
}} else {{
Err( load_failures )
}}
}}
"
);
}