#![warn(missing_docs)]
use magnesium::{XmlElement::*, *};
use std::{
collections::{HashMap, HashSet},
fmt::Write,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlProfile {
Core,
Compatibility,
}
macro_rules! show {
($dst:expr) => { writeln!($dst)? };
($dst:expr,) => { writeln!($dst)? };
($dst:expr, $($arg:tt)*) => { writeln!($dst, $($arg)*)? };
}
const GENERATED_BY: &str = concat!(
r#"//!
//! Generated by [phosphorus](https://docs.rs/phosphorus/"#,
env!("CARGO_PKG_VERSION"),
r#"/phosphorus/).
//!"#
);
const STANDARD_DOCS: &str = r#"//!
//! Supported Features:
//! * `global_loader`: Include all mechanisms necessary for calling GL using
//! global functions.
//! * `struct_loader`: Include all mechanisms necessary for calling GL as
//! methods on a struct.
//! * `debug_trace_calls`: if cfg!(debug_assertions), any call to a GL function
//! will `trace!` what was called and with what args.
//! * `debug_automatic_glGetError`: If cfg!(debug_assertions), this will
//! automatically call `glGetError` after every call to any *other* GL
//! function. If an error code occurs it's shown via `error!` along with the
//! name of the function that had the error.
//! * `log`: imports `trace!` and `error!` macros from the `log` crate.
//! Otherwise they just call `println!` and `eprintln!` respectively.
//! * `chlorine`: gets all C types from the `chlorine` crate (which is `no_std`
//! friendly). Otherwise they will be imported from `std::os::raw`.
//! * `bytemuck`: Adds support for the `bytemuck` crate, mostly in the form of
//! `bytemuck::Zeroable` on `GlFns`.
//! * `inline`: Tags all GL calls as `#[inline]`.
//! * `inline_always`: Tags all GL calls as `#[inline(always)]`. This will
//! effectively override the `inline` feature.
//!
//! The crate is `no_std` friendly by default, but features above can end up
//! requiring `std` to be available.
//!
//! # GL Loaders
//! The docs for this crate hosted on docs.rs generate **both** the
//! `global_loader` and `struct_loader` documentation for sake of completeness.
//!
//! However, you are generally expected to use **only one** loader style in any
//! particular project.
//!
//! Each loader style has its own small advantages:
//! * The `global_loader` stores the GL function pointers in static `AtomicPtr`
//! values.
//! * Call [`load_global_gl_with`] to initialize the pointers.
//! * Each GL function is available as a global function under its standard
//! name, eg `glGetError()`.
//! * This lets you call GL functions from anywhere at all, and it's how you
//! might expect to use GL if you have a C background.
//! * Being able to call GL from anywhere makes it easy to write Drop impls,
//! among other things.
//! * The `struct_loader` stores all the function pointers in the fields of a
//! [`GlFns`] struct.
//! * Call [`GlFns::load_with`] to make a `GlFns` value.
//! * Each GL function is available as a method on the struct with the `gl`
//! prefix removed. It's presumed that you'll call the struct itself `gl`,
//! so calls will look something like `gl.GetError()`.
//! * This is closer to how WebGL works on WASM targets, and so this is how
//! the [`glow`](https://docs.rs/glow) crate works to maintain consistency
//! across desktop and web.
//! * Also, if you want to do any sort of "live code reloading" you'll have to
//! use the struct loader. DLLs don't share their static values with the
//! main program, so if the DLL uses the global loader functions the
//! pointers won't be loaded and calling any GL function from the DLL will
//! panic. Instead, if you just pass a `&GlFns` to your DLL it can call the
//! GL methods just fine.
//!
//! In both styles, if you call a function that isn't loaded you will get a
//! panic. This generally only happens if the context doesn't fully support
//! the GL version. You can check if a GL command is loaded or not before
//! actually calling it by adding `_is_loaded` to the name of the command. In
//! other words, `glGetError_is_loaded` to check if `glGetError` is globally
//! loaded, and `gl.GetError_is_loaded` to check if it's loaded in a `GlFns`.
//! All of the "`_is_loaded`" functions are hidden in the generated docs just
//! to keep things tidy, but they're there.
//!
//! # Safety
//! In general, there's many ways that GL can go wrong.
//!
//! For the purposes of this library, it's important to focus on the fact that:
//! * Initially all functions are null pointers. If a function is called when it's in a null state then you'll get a panic (reminder: a panic is safe).
//! * You can load pointers from the current GL context (described above).
//! * These pointers are technically context specific, though in practice different contexts for the same graphics driver often all share the same function pointers.
//! * The loader has no way to verify that pointers it gets are actually pointers to the correct functions, it just trusts what you tell it.
//! * Since loading a function pointer transitions the world from "it will definitely (safely) panic to call that GL command" to "it might be UB to call that GL command (even with the correct arguments)", the act of simply loading a function pointer is itself considered to be `unsafe`.
//! * Individual GL commands are generally safe to use once they've been properly loaded for the current context, but this crate doesn't attempt to sort out what is safe and what's not. All GL commands are blanket marked as being `unsafe`.
//! It's up to you to try and manage this unsafety! Sorry, but this crate just does what you tell it to.
"#;
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct GlApiSelection {
pub gl_types: Vec<GlType>,
pub gl_enums: HashMap<String, GlEnum>,
pub gl_commands: HashMap<String, GlCommand>,
pub api: ApiGroup,
pub version: (i32, i32),
pub extensions: Vec<String>,
}
impl core::fmt::Display for GlApiSelection {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let api = self.api;
let major_version_number = self.version.0;
const EXAMPLE_MODE: bool = false;
if !EXAMPLE_MODE {
show!(f, "#![no_std]");
}
show!(f, "#![allow(bad_style)]");
show!(f, "#![deny(missing_docs)]");
show!(f, "#![deny(missing_debug_implementations)]");
show!(f);
show!(
f,
"//! Bindings to {:?} {}.{}",
self.api,
self.version.0,
self.version.1
);
show!(f, "{}", GENERATED_BY);
show!(f, "//! Included Extensions (activate via cargo feature):");
for ext in self.extensions.iter() {
show!(f, "//! * `{ext}`", ext = ext);
}
show!(f, "{}", STANDARD_DOCS);
if EXAMPLE_MODE {
show!(f, "fn main() {{ }} // TODO: disable EXAMPLE_MODE.");
}
show!(
f,
"
#[cfg(any(
all(
not(feature = \"log\"),
any(
feature = \"debug_trace_calls\",
feature = \"debug_automatic_glGetError\"
)
),
not(feature = \"chlorine\"),
))]
extern crate std;
#[cfg(feature=\"chlorine\")]use chlorine::*;
#[cfg(not(feature=\"chlorine\"))]use std::os::raw::*;
#[cfg(feature = \"log\")] #[allow(unused)]
use log::{{error, trace}};
#[cfg(all(
not(feature = \"log\"),
feature = \"debug_trace_calls\"
))]
macro_rules! trace {{ ($($arg:tt)*) => {{ std::println!($($arg)*) }} }}
#[cfg(all(
not(feature = \"log\"),
feature = \"debug_automatic_glGetError\"
))]
macro_rules! error {{ ($($arg:tt)*) => {{ std::eprintln!($($arg)*) }} }}"
);
show!(
f,
"
use core::{{
sync::atomic::{{AtomicPtr, Ordering}},
mem::transmute,
ptr::null_mut,
}};
#[allow(dead_code)]const RELAX: Ordering = Ordering::Relaxed;
#[allow(dead_code)]type APcv = AtomicPtr<c_void>;
#[cfg(feature=\"global_loader\")]const fn ap_null() -> APcv {{ AtomicPtr::new(null_mut()) }}"
);
show!(f);
show!(f, "pub use types::*;");
show!(f, "#[allow(missing_docs)] pub mod types {{");
show!(f, "//! Contains all the GL types.");
show!(f, " use super::*;");
for gl_type in self.gl_types.iter() {
show!(f, " {}", gl_type);
}
show!(f, "}}");
show!(f);
show!(f, "pub use enums::*;");
show!(f, "pub mod enums {{");
show!(f, "//! Contains all the GL enumerated values.");
show!(f, "//! ");
show!(f, "//! In C these are called 'enums', but in Rust we call them a 'const'. Whatever.");
show!(f, " use super::*;");
let mut enum_list: Vec<GlEnum> = self.gl_enums.values().cloned().collect();
enum_list.sort_by_key(|gl_enum| gl_enum.name.clone());
for gl_enum in enum_list.iter() {
show!(f, " {}", GlEnumDisplayer { gl_enum, api });
}
show!(f, "}}");
let mut command_list: Vec<GlCommand> =
self.gl_commands.values().cloned().collect();
command_list.sort_by_key(|gl_command| gl_command.name.clone());
show!(
f,
"
/// This is called to panic when a not-loaded function is attempted.
///
/// Placing the panic mechanism in this cold function generally helps code generation for the hot path.
/// Or so the sages say, at least.
#[cold]
#[inline(never)]#[allow(dead_code)]
fn go_panic_because_fn_not_loaded(name: &str) -> ! {{
panic!(\"called {{name}} but it was not loaded.\", name = name)
}}
/// Loads a function pointer.
/// Rejects suggested pointer addresses which are likely to be lies.
/// This function is used by both the global loader and struct loader.
/// We mark it as `inline(never)` to favor a small binary over initialization speed.
/// Returns if there's now a non-null value in the atomic pointer.
#[inline(never)]#[allow(dead_code)]
fn load_dyn_name_atomic_ptr(
get_proc_address: &mut dyn FnMut(*const c_char) -> *mut c_void,
fn_name: &[u8],
ptr: &APcv,
) -> bool {{
// if this fails the code generator itself royally screwed up somehow,
// and so it's only a debug assert.
debug_assert_eq!(*fn_name.last().unwrap(), 0);
let p: *mut c_void = get_proc_address(fn_name.as_ptr() as *const c_char);
let p_usize = p as usize;
// You *should* get null for failed lookups, but some systems have been
// reported to give \"error code\" values such as -1 or small non-null values.
// To help guard against this silliness, we consider these values to also
// just be a result of null.
if p_usize == core::usize::MAX || p_usize < 8 {{
ptr.store(null_mut(), RELAX);
false
}} else {{
ptr.store(p, RELAX);
true
}}
}}
/// Returns if an error was printed.
#[cfg(feature = \"debug_automatic_glGetError\")]
#[inline(never)]
fn report_error_code_from(name: &str, err: GLenum) {{
match err {{
GL_NO_ERROR => return,
GL_INVALID_ENUM => error!(\"Invalid Enum to {{name}}.\", name = name),
GL_INVALID_VALUE => error!(\"Invalid Value to {{name}}.\", name = name),
GL_INVALID_OPERATION => error!(\"Invalid Operation to {{name}}.\", name = name),
GL_INVALID_FRAMEBUFFER_OPERATION => error!(\"Invalid Framebuffer Operation to {{name}}.\", name = name),
GL_OUT_OF_MEMORY => error!(\"Out of Memory in {{name}}.\", name = name),
GL_STACK_UNDERFLOW => error!(\"Stack Underflow in {{name}}.\", name = name),
GL_STACK_OVERFLOW => error!(\"Stack Overflow in {{name}}.\", name = name),
unknown => error!(\"Unknown error code {{unknown}} to {{name}}.\", name = name, unknown = unknown),
}}
}}"
);
let arity_set: HashSet<_> =
command_list.iter().map(|glc| glc.params.len()).collect();
let mut arity_list: Vec<_> = arity_set.iter().copied().collect();
arity_list.sort();
for arity in arity_list.iter().copied() {
let mut param_generics = String::new();
let mut param_names_and_types = String::new();
let mut param_names = String::new();
for n in 0..arity {
let n8 = n as u8;
if !param_generics.is_empty() {
param_generics.push(',');
}
let _cant_fail = write!(param_generics, "{}", (b'A' + n8) as char);
if !param_names_and_types.is_empty() {
param_names_and_types.push(',');
}
let _cant_fail = write!(
param_names_and_types,
"{}:{}",
(b'a' + n8) as char,
(b'A' + n8) as char
);
if !param_names.is_empty() {
param_names.push(',');
}
let _cant_fail = write!(param_names, "{}", (b'a' + n8) as char);
}
show!(
f,
"
#[inline(always)]#[allow(dead_code)]
unsafe fn call_atomic_ptr_{arity}arg<Ret{ret_comma}{param_generics}>(name: &str, ptr: &APcv, {param_names_and_types}) -> Ret {{
let p = ptr.load(RELAX);
match transmute::<*mut c_void, Option<extern \"system\" fn({param_generics})->Ret>>(p) {{
Some(fn_p) => fn_p({param_names}),
None => go_panic_because_fn_not_loaded(name),
}}
}}",
arity = arity,
param_generics = param_generics,
param_names_and_types = param_names_and_types,
param_names = param_names,
ret_comma = if arity > 0 { "," } else { "" },
);
}
show!(f);
show!(
f,
"#[cfg(feature=\"global_loader\")] pub use global_commands::*;"
);
show!(
f,
"#[cfg(feature=\"global_loader\")] pub mod global_commands {{"
);
show!(f, "//! Contains functions for using the global GL loader.");
show!(f, " use super::*;");
show!(
f,
"#[cfg(feature = \"debug_automatic_glGetError\")]#[inline(never)]
unsafe fn global_automatic_glGetError(name: &str) {{
let mut err = glGetError();
while err != GL_NO_ERROR {{
report_error_code_from(name, err);
err = glGetError();
}}
}}
/// Loads all global functions using the `get_proc_address` given.
///
/// The closure should, when given a null-terminated name of a function,
/// return a pointer to that function. If the function isn't available, then
/// a null pointer should be returned instead.
///
/// This allows you to call [SDL_GL_GetProcAddress](https://wiki.libsdl.org/SDL_GL_GetProcAddress),
/// [wglGetProcAddress](https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-wglgetprocaddress),
/// or some similar function, depending on your OS.
pub unsafe fn load_global_gl_with<F>(
mut get_proc_address: F,
)
where
F: FnMut(*const c_char) -> *mut c_void
{{"
);
for gl_command in command_list.iter() {
match gl_command.extensions.as_ref() {
None => show!(
f,
" {name}_load_with_dyn(&mut get_proc_address) as usize;",
name = gl_command.name
),
Some(_) => {
let InfoForGlCommandPrinting { extensions, .. } =
InfoForGlCommandPrinting::from_command_and_api(
gl_command,
self.api,
self.version.0,
false,
);
show!(
f,
" {extensions}{{{name}_load_with_dyn(&mut get_proc_address) as usize;}}",
name = gl_command.name,
extensions = extensions,
)
}
}
}
show!(f, "}}");
for gl_command in command_list.iter() {
show!(f);
show!(
f,
"{}",
GlobalGlCommand {
gl_command,
api,
major_version_number
}
);
}
show!(f, "}}");
show!(f);
show!(
f,
"#[cfg(feature=\"struct_loader\")] pub use struct_commands::*;"
);
show!(
f,
"#[cfg(feature=\"struct_loader\")] pub mod struct_commands {{"
);
show!(
f,
"//! Contains the [`GlFns`] type for using the struct GL loader."
);
show!(f, " use super::*;");
show!(
f,
" {}",
StructLoaderDisplayer {
gl_commands: &command_list,
api,
major_version_number
}
);
show!(f, "}}");
show!(f, "// end of module");
Ok(())
}
}
impl GlApiSelection {
pub fn new_from_registry_api_extensions(
reg: &GlRegistry, api: ApiGroup, level: (i32, i32),
target_profile: GlProfile, extensions: &[&str],
) -> Self {
let gl_types: Vec<GlType> = reg.gl_types.clone();
let mut gl_enums: HashMap<String, GlEnum> = HashMap::new();
let mut gl_commands: HashMap<String, GlCommand> = HashMap::new();
let target_number = format!("{}.{}", level.0, level.1);
for gl_feature in reg.gl_features.iter() {
if gl_feature.api != api || gl_feature.number > target_number {
continue;
}
for GlRequirement {
profile,
api,
adjustment,
} in gl_feature.required.iter()
{
if let Some(p) = profile {
match p.as_str() {
"core" => {
if target_profile != GlProfile::Core {
continue;
}
}
"compatibility" => {
if target_profile != GlProfile::Compatibility {
continue;
}
}
unknown => panic!("unknown: {}", unknown),
}
}
assert!(api.is_none());
match adjustment {
ReqRem::Type(_req_type) => (),
ReqRem::Command(req_command) => drop(
gl_commands.insert(
req_command.clone(),
reg
.gl_commands
.iter()
.find(|glc| glc.name.as_str() == req_command)
.unwrap()
.clone(),
),
),
ReqRem::Enum(req_enum) => drop(
gl_enums.insert(
req_enum.clone(),
reg
.gl_enums
.iter()
.find(|gle| gle.name.as_str() == req_enum)
.unwrap()
.clone(),
),
),
}
}
for GlRemoval {
profile,
adjustment,
} in gl_feature.remove.iter()
{
if let Some(p) = profile {
match p.as_str() {
"core" => {
if target_profile != GlProfile::Core {
continue;
}
}
"compatibility" => {
if target_profile != GlProfile::Compatibility {
continue;
}
}
unknown => panic!("unknown: {}", unknown),
}
}
match adjustment {
ReqRem::Type(_rem_type) => (),
ReqRem::Command(rem_command) => drop(gl_commands.remove(rem_command)),
ReqRem::Enum(rem_enum) => drop(gl_enums.remove(rem_enum)),
}
}
}
let mut extensions: Vec<String> =
extensions.iter().copied().map(str::to_string).collect();
extensions.sort();
for extension_name in extensions.iter() {
let the_extension = reg
.gl_extensions
.iter()
.find(|gl_ext| gl_ext.name.as_str() == extension_name.as_str())
.unwrap();
assert!(the_extension.supported.contains(api.supported()), "Requested {extension_name} with api {api:?}, but it is not supported by that API.", extension_name = extension_name, api = api);
for GlRequirement {
profile,
api: req_api,
adjustment,
} in the_extension.required.iter()
{
if let Some(p) = profile {
match p.as_str() {
"core" => {
if target_profile != GlProfile::Core {
continue;
}
}
"compatibility" => {
if target_profile != GlProfile::Compatibility {
continue;
}
}
unknown => panic!("unknown: {}", unknown),
}
}
if let Some(a) = req_api {
if a != &api {
continue;
}
}
match adjustment {
ReqRem::Type(_req_type) => (),
ReqRem::Command(req_command) => {
if gl_commands.contains_key(req_command) {
if let Some(ext) = gl_commands
.get_mut(req_command)
.unwrap()
.extensions
.as_mut()
{
ext.push(extension_name.clone())
}
} else {
let mut new_command = reg
.gl_commands
.iter()
.find(|glc| glc.name.as_str() == req_command)
.unwrap()
.clone();
new_command.extensions = Some(vec![extension_name.clone()]);
gl_commands.insert(req_command.clone(), new_command);
}
}
ReqRem::Enum(req_enum) => {
if gl_enums.contains_key(req_enum) {
if let Some(ext) =
gl_enums.get_mut(req_enum).unwrap().extensions.as_mut()
{
ext.push(extension_name.clone())
}
} else {
let mut new_enum = reg
.gl_enums
.iter()
.find(|glc| glc.name.as_str() == req_enum)
.unwrap()
.clone();
new_enum.extensions = Some(vec![extension_name.clone()]);
gl_enums.insert(req_enum.clone(), new_enum);
}
}
}
}
}
for error_enum_name in [
"GL_NO_ERROR",
"GL_INVALID_ENUM",
"GL_INVALID_VALUE",
"GL_INVALID_OPERATION",
"GL_INVALID_FRAMEBUFFER_OPERATION",
"GL_OUT_OF_MEMORY",
"GL_STACK_UNDERFLOW",
"GL_STACK_OVERFLOW",
]
.iter()
.copied()
{
gl_enums.insert(
error_enum_name.to_string(),
reg
.gl_enums
.iter()
.find(|gle| gle.name.as_str() == error_enum_name)
.unwrap()
.clone(),
);
}
Self {
gl_types,
gl_enums,
gl_commands,
api,
version: level,
extensions,
}
}
}
fn revert_xml_encoding(text: String) -> String {
let mut out = String::with_capacity(text.as_bytes().len());
let mut chars = text.chars();
while let Some(c) = chars.next() {
if c != '&' {
out.push(c);
} else {
match chars.next().unwrap() {
'l' => {
assert_eq!(chars.next().unwrap(), 't');
assert_eq!(chars.next().unwrap(), ';');
out.push('<');
}
'g' => {
assert_eq!(chars.next().unwrap(), 't');
assert_eq!(chars.next().unwrap(), ';');
out.push('>');
}
'a' => {
assert_eq!(chars.next().unwrap(), 'm');
assert_eq!(chars.next().unwrap(), 'p');
assert_eq!(chars.next().unwrap(), ';');
out.push('&');
}
other => panic!("{}", other),
}
}
}
out
}
fn eat_to_comment_close<'s>(iter: &mut impl Iterator<Item = XmlElement<'s>>) {
loop {
match iter.next().unwrap() {
EndTag { name: "comment" } => return,
_ => continue,
}
}
}
fn eat_to_groups_close<'s>(iter: &mut impl Iterator<Item = XmlElement<'s>>) {
loop {
match iter.next().unwrap() {
EndTag { name: "groups" } => return,
_ => continue,
}
}
}
fn grab_out_name_text<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>,
) -> &'s str {
let t = match iter.next().unwrap() {
Text(t) => t,
unknown => panic!("grab_out_name_text err:{:?}", unknown),
};
assert!(matches!(iter.next().unwrap(), EndTag { name: "name" }));
t
}
fn grab_out_ptype_text<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>,
) -> &'s str {
let t = match iter.next().unwrap() {
Text(t) => t,
unknown => panic!("grab_out_ptype_text err:{:?}", unknown),
};
assert!(matches!(iter.next().unwrap(), EndTag { name: "ptype" }));
t
}
#[derive(Debug, Default, Clone)]
pub struct GlRegistry {
pub gl_types: Vec<GlType>,
pub gl_enums: Vec<GlEnum>,
pub gl_commands: Vec<GlCommand>,
pub gl_features: Vec<GlFeature>,
pub gl_extensions: Vec<GlExtension>,
}
impl GlRegistry {
pub fn from_gl_xml_str(mut gl_xml: &str) -> Self {
if gl_xml.chars().nth(0).unwrap() == '\u{feff}' {
gl_xml = &gl_xml['\u{feff}'.len_utf8()..];
}
let iter = &mut ElementIterator::new(&gl_xml)
.filter_map(skip_comments)
.filter_map(skip_empty_text_elements);
assert!(matches!(
iter.next().unwrap(),
StartTag {
name: "registry",
attrs: ""
}
));
Self::from_iter(iter)
}
#[doc(hidden)]
#[allow(clippy::should_implement_trait)]
pub fn from_iter<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>,
) -> Self {
let mut registry = Self::default();
loop {
match iter.next().unwrap() {
EndTag { name: "registry" } => return registry,
StartTag {
name: "comment",
attrs: "",
} => eat_to_comment_close(iter),
StartTag {
name: "groups",
attrs: "",
} => eat_to_groups_close(iter),
StartTag {
name: "types",
attrs: "",
} => loop {
match iter.next().unwrap() {
EndTag { name: "types" } => break,
StartTag {
name: "type",
attrs,
} => {
if let Some(t) = GlType::try_from_iter_and_attrs(iter, attrs) {
registry.gl_types.push(t)
}
}
unknown => panic!("unexpected 'type' tag content:{:?}", unknown),
}
},
StartTag {
name: "enums",
attrs,
} => gather_enum_entries_to(&mut registry.gl_enums, iter, attrs),
EmptyTag {
name: "enums",
attrs: _,
} => {
}
StartTag {
name: "commands",
attrs: r#"namespace="GL""#,
} => loop {
match iter.next().unwrap() {
EndTag { name: "commands" } => break,
StartTag {
name: "command",
attrs,
} => registry
.gl_commands
.push(GlCommand::from_iter_and_attrs(iter, attrs)),
unknown => panic!("unknown 'commands' content:{:?}", unknown),
}
},
StartTag {
name: "feature",
attrs,
} => registry
.gl_features
.push(GlFeature::from_iter_and_attrs(iter, attrs)),
StartTag {
name: "extensions",
attrs: "",
} => loop {
match iter.next().unwrap() {
EndTag { name: "extensions" } => break,
StartTag {
name: "extension",
attrs,
} => registry
.gl_extensions
.push(GlExtension::from_iter_and_attrs(iter, attrs)),
EmptyTag {
name: "extension",
attrs,
} => {
let mut extension = GlExtension::default();
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => extension.name.push_str(value),
"supported" => extension.supported.push_str(value),
unknown => panic!("unknown: {:?}", unknown),
}
}
registry.gl_extensions.push(extension);
}
unknown => panic!("{:?}", unknown),
}
},
unknown => panic!("GlRegistry::from_iter:{:?}", unknown),
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GlType {
Typedef(String),
Struct(String),
IfDef(String),
}
impl core::fmt::Display for GlType {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
GlType::Typedef(s) => {
assert!(s.as_bytes().last().unwrap() == &b';');
let mut words_iter = s[..s.as_bytes().len() - 1].split_whitespace();
assert_eq!(words_iter.next().unwrap(), "typedef");
let mut new = words_iter.next_back().unwrap();
let old: &'static str = match words_iter.next().unwrap() {
"unsigned" => match words_iter.next().unwrap() {
"int" => "c_uint",
"char" => "c_uchar",
"short" => "c_ushort",
unknown => panic!("unknown unsigned:{}", unknown),
},
"void" => match words_iter.next() {
None => "c_void",
Some("*") => "*mut c_void",
Some("(*") => match s.as_str() {
"typedef void (* GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);" => {
new = "GLDEBUGPROC";
r#"Option<unsafe extern "system" fn(source: GLenum, gltype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: *const GLchar, userParam: *mut c_void)>"#
}
"typedef void (* GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);" => {
new = "GLDEBUGPROCARB";
r#"Option<extern "system" fn(source: GLenum, gltype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: *const GLchar, userParam: *mut c_void)>"#
}
"typedef void (* GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam);" => {
new = "GLDEBUGPROCKHR";
r#"Option<extern "system" fn(source: GLenum, gltype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: *const GLchar, userParam: *mut c_void)>"#
}
"typedef void (* GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam);" => {
new = "GLDEBUGPROCAMD";
r#"Option<extern "system" fn(id: GLuint, category: GLenum, severity: GLenum, length: GLsizei, message: *const GLchar, userParam: *mut c_void)>"#
}
"typedef void (* GLVULKANPROCNV)(void);" => {
new = "GLVULKANPROCNV";
r#"Option<extern "system" fn()>"#
}
unknown => panic!("unknown fn ptr:{:?}", unknown),
},
unknown => panic!("unknown void:{:?}", unknown),
},
"struct" => match words_iter.next().unwrap() {
"__GLsync" => {
write!(f, "#[doc(hidden)]pub struct __GLsync{{ _priv: u8 }} impl core::fmt::Debug for __GLsync {{ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {{ write!(f, \"__GLsync\") }} }}")?;
assert_eq!(words_iter.next().unwrap(), "*");
"*mut __GLsync"
}
unknown => panic!("unknown struct:{}", unknown),
},
"khronos_int8_t" => "i8",
"khronos_uint8_t" => "u8",
"khronos_int16_t" => "i16",
"khronos_uint16_t" => "u16",
"khronos_int32_t" => "i32",
"khronos_uint32_t" => "u32",
"khronos_int64_t" => "i64",
"khronos_uint64_t" => "u64",
"khronos_float_t" => "c_float",
"khronos_intptr_t" => "isize",
"khronos_ssize_t" => "isize",
"GLintptr" => "GLintptr",
"double" => "c_double",
"int" => "c_int",
"char" => "c_char",
unknown => panic!("unknown:{}", unknown),
};
write!(f, "pub type {new} = {old};", new = new, old = old)
}
GlType::Struct(s) => {
let mut words_iter = s[..s.as_bytes().len() - 1].split_whitespace();
assert_eq!(words_iter.next().unwrap(), "struct");
let name = words_iter.next().unwrap();
write!(f, "pub struct {name}{{ _priv: u8 }} impl core::fmt::Debug for {name} {{ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {{ write!(f, \"{name}\") }} }}", name = name)
}
GlType::IfDef(s) => {
const GL_HANDLE_ARB_RN: &str = "#ifdef __APPLE__\r\ntypedef void *GLhandleARB;\r\n#else\r\ntypedef unsigned int GLhandleARB;\r\n#endif";
const GL_HANDLE_ARB_N: &str = "#ifdef __APPLE__\ntypedef void *GLhandleARB;\n#else\ntypedef unsigned int GLhandleARB;\n#endif";
match s.as_str() {
GL_HANDLE_ARB_RN | GL_HANDLE_ARB_N => write!(
f,
r#"#[cfg(any(target_os="macos", target_os="ios"))]pub type GLhandleARB = *mut c_void;#[cfg(not(any(target_os="macos", target_os="ios")))]pub type GLhandleARB = c_uint;"#
),
unknown => panic!("unknown ifdef: {}", unknown),
}
}
}
}
}
impl GlType {
fn try_from_iter_and_attrs<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>, _attrs: &str,
) -> Option<Self> {
let mut out = String::new();
loop {
match iter.next().unwrap() {
EndTag { name: "type" } => break,
StartTag {
name: "name",
attrs: "",
} => {
if !out.is_empty() {
out.push(' ');
}
out.push_str(grab_out_name_text(iter))
}
Text(t) => out.push_str(t.trim()),
EmptyTag {
name: "apientry",
attrs: "",
} => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
out = revert_xml_encoding(out);
if out.starts_with("#include") {
None
} else if out.starts_with("typedef") {
Some(GlType::Typedef(out))
} else if out.starts_with("struct") {
Some(GlType::Struct(out))
} else if out.starts_with("#ifdef") {
Some(GlType::IfDef(out))
} else {
panic!("unknown GlType variant: {}", out);
}
}
}
fn gather_enum_entries_to<'s>(
list: &mut Vec<GlEnum>, iter: &mut impl Iterator<Item = XmlElement<'s>>,
attrs: &str,
) {
let mut is_bitmask = false;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"namespace" => assert_eq!(value, "GL"),
"group" | "comment" | "vendor" | "start" | "end" => (),
"type" if value == "bitmask" => is_bitmask = true,
unknown => panic!("unknown enum attr: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "enums" } => break,
EmptyTag {
name: "unused",
attrs: _,
} => (),
EmptyTag {
name: "enum",
attrs,
} => {
list.push(GlEnum::from_attrs(attrs, is_bitmask));
}
unknown => panic!("unknown: {:?}", unknown),
}
}
}
#[derive(Debug, Clone)]
pub struct GlEnum {
pub name: String,
pub value: String,
pub group: Option<String>,
pub alias_of: Option<String>,
pub api: Option<ApiGroup>,
pub is_bitmask: bool,
pub extensions: Option<Vec<String>>,
}
impl GlEnum {
fn from_attrs(attrs: &str, is_bitmask: bool) -> Self {
let mut name = String::new();
let mut the_value = String::new();
let mut group = None;
let mut alias_of = None;
let mut api = None;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"name" => name.push_str(value),
"value" => the_value.push_str(value),
"group" => group = Some(String::from(value)),
"alias" => alias_of = Some(String::from(value)),
"api" => api = Some(ApiGroup::from(value)),
"comment" => (),
"type" => (),
unknown => panic!("unknown enum attr: {:?}", unknown),
}
}
let value = the_value;
assert!(!name.is_empty());
assert!(!value.is_empty());
GlEnum {
name,
value,
group,
alias_of,
api,
is_bitmask,
extensions: None,
}
}
}
#[derive(Debug)]
pub struct GlEnumDisplayer<'e> {
pub gl_enum: &'e GlEnum,
pub api: ApiGroup,
}
impl<'e> core::fmt::Display for GlEnumDisplayer<'e> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if let Some(a) = self.gl_enum.api {
if self.api != a {
panic!("illegal {:?}", self);
}
}
let name = &self.gl_enum.name;
let ty = if self.gl_enum.is_bitmask {
"GLbitfield"
} else if self.gl_enum.value == "0xFFFFFFFFFFFFFFFF" {
"u64"
} else {
"GLenum"
};
let val = if self.gl_enum.value.starts_with('-') {
format!("{} as GLenum", self.gl_enum.value)
} else {
self.gl_enum.value.clone()
};
let mut doc = format!(
"`{name}: {ty} = {value_text}`",
ty = ty,
name = name,
value_text = self.gl_enum.value,
);
if let Some(g) = self.gl_enum.group.as_ref() {
doc.push_str(&format!(
"\\n* **Group{}:** ",
if g.split(',').count() > 1 { "s" } else { "" }
));
for (i, group) in g.split(',').enumerate() {
if i != 0 {
doc.push_str(", ");
}
doc.push_str(group);
}
}
if let Some(a) = self.gl_enum.alias_of.as_ref() {
doc.push_str("\\n* **Alias Of:** `");
doc.push_str(a);
doc.push('`');
}
let mut extensions = String::from("");
if let Some(list) = self.gl_enum.extensions.as_ref() {
extensions.push_str("#[cfg(any(");
for (i, l) in list.iter().enumerate() {
if i != 0 {
extensions.push(',');
}
show!(extensions, "feature=\"{l}\"", l = l);
}
extensions.push_str("))]");
extensions.push_str("#[cfg_attr(docs_rs, doc(cfg(any(");
for (i, l) in list.iter().enumerate() {
if i != 0 {
extensions.push(',');
}
show!(extensions, "feature=\"{l}\"", l = l);
}
extensions.push_str("))))]");
};
write!(
f,
"#[doc = \"{doc}\"]{extensions}pub const {name}: {ty} = {val};",
name = name,
ty = ty,
val = val,
doc = doc,
extensions = extensions
)
}
}
#[derive(Debug, Default, Clone)]
#[allow(missing_docs)]
pub struct GlCommand {
pub name: String,
pub proto: String,
pub proto_group: Option<String>,
pub params: Vec<GlCommandParam>,
pub glx_attrs: Option<String>,
pub alias_of: Option<String>,
pub vec_equivalent: Option<String>,
pub extensions: Option<Vec<String>>,
}
impl GlCommand {
fn from_iter_and_attrs<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>, attrs: &str,
) -> Self {
for TagAttribute { key, value: _ } in TagAttributeIterator::new(attrs) {
match key {
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
let mut command = GlCommand::default();
loop {
match iter.next().unwrap() {
EndTag { name: "command" } => break,
StartTag {
name: "proto",
attrs,
} => {
if !attrs.is_empty() {
for TagAttribute { key, value } in TagAttributeIterator::new(attrs)
{
match key {
"group" => command.proto_group = Some(String::from(value)),
unknown => panic!("unknown proto attr: {:?}", unknown),
}
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "proto" } => break,
Text(t) => command.proto.push_str(t),
StartTag {
name: "name",
attrs: "",
} => {
let n = grab_out_name_text(iter);
command.name.push_str(n);
command.proto.push_str(n);
}
StartTag {
name: "ptype",
attrs: "",
} => {
let n = grab_out_ptype_text(iter);
command.proto.push_str(n);
}
unknown => panic!("unknown: {:?}", unknown),
}
}
}
StartTag {
name: "param",
attrs,
} => command
.params
.push(GlCommandParam::from_iter_and_attrs(iter, attrs)),
EmptyTag { name: "glx", attrs } => {
command.glx_attrs = Some(String::from(attrs));
}
EmptyTag {
name: "alias",
attrs,
} => {
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"name" => command.alias_of = Some(String::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "vecequiv",
attrs,
} => {
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"name" => command.vec_equivalent = Some(String::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
unknown => panic!("unknown command content:{:?}", unknown),
}
}
command
}
}
fn c_type_to_rust_type(text: &str) -> String {
if text.contains('*') {
match text {
"const GLchar *const*" => String::from("*const *const GLchar"),
"const void *const*" => String::from("*const *const c_void"),
"const void *" => String::from("*const c_void"),
"void *" => String::from("*mut c_void"),
"void **" => String::from("*mut *mut c_void"),
_otherwise => {
let mut t = if text.starts_with("const") {
format!("*{}", text)
} else {
format!("*mut {}", text)
};
t.pop();
t.pop();
t
}
}
} else {
String::from(text)
}
}
struct GlobalGlCommand<'a> {
gl_command: &'a GlCommand,
api: ApiGroup,
major_version_number: i32,
}
impl core::fmt::Display for GlobalGlCommand<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let InfoForGlCommandPrinting {
name,
rust_return_type,
arg_name_and_type_list,
arg_name_list,
trace_fmt,
trace_args,
docs,
atomic_ptr_name,
error_check,
arity,
extensions,
} = InfoForGlCommandPrinting::from_command_and_api(
self.gl_command,
self.api,
self.major_version_number,
false,
);
write!(
f,
"{docs}
#[cfg_attr(feature=\"inline\", inline)]
#[cfg_attr(feature=\"inline_always\", inline(always))]
{extensions}pub unsafe fn {name}({arg_name_and_type_list}){rust_return_type} {{
#[cfg(all(debug_assertions, feature = \"debug_trace_calls\"))]
{{
trace!(\"calling {name}({trace_fmt});\", {trace_args});
}}
let out = call_atomic_ptr_{arity}arg(\"{name}\", &{atomic_ptr_name}, {arg_name_list});
{error_check}
out
}}
{extensions}static {atomic_ptr_name}: APcv = ap_null();
/// Tries to load [`{name}`], returns if a non-null pointer was obtained.
#[doc(hidden)]
{extensions}pub unsafe fn {name}_load_with_dyn(
get_proc_address: &mut dyn FnMut(*const c_char) -> *mut c_void
) -> bool {{
load_dyn_name_atomic_ptr(get_proc_address, b\"{name}\\0\", &{atomic_ptr_name})
}}
/// Checks if the pointer for [`{name}`] is loaded (non-null).
#[inline]
#[doc(hidden)]
{extensions}pub fn {name}_is_loaded() -> bool {{
!{atomic_ptr_name}.load(RELAX).is_null()
}}",
name = name,
arg_name_and_type_list = arg_name_and_type_list,
rust_return_type = rust_return_type,
atomic_ptr_name = atomic_ptr_name,
arg_name_list = arg_name_list,
docs = docs,
trace_fmt = trace_fmt,
trace_args = trace_args,
error_check = error_check,
arity = arity,
extensions = extensions,
)
}
}
struct StructLoaderDisplayer<'a> {
gl_commands: &'a [GlCommand],
api: ApiGroup,
major_version_number: i32,
}
impl core::fmt::Display for StructLoaderDisplayer<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut struct_fields: Vec<String> = Vec::new();
show!(
f,
" impl GlFns {{
/// Constructs a new struct with all pointers loaded by the `get_proc_address` given.
pub unsafe fn load_with<F>(
mut get_proc_address: F,
) -> Self
where
F: FnMut(*const c_char) -> *mut c_void
{{
// Safety: The `GlFns` struct is nothing but `AtomicPtr` fields,
// which can be safely constructed with `zeroed`.
let out: Self = core::mem::zeroed();
out.load_all_with_dyn(&mut get_proc_address);
out
}}
#[cfg(feature = \"debug_automatic_glGetError\")]#[inline(never)]
unsafe fn automatic_glGetError(&self, name: &str) {{
let mut err = self.GetError();
while err != GL_NO_ERROR {{
report_error_code_from(name, err);
err = self.GetError();
}}
}}
/// Loads all pointers using the `get_proc_address` given.
#[doc(hidden)]
#[inline(never)]
pub unsafe fn load_all_with_dyn(
&self,
get_proc_address: &mut dyn FnMut(*const c_char) -> *mut c_void,
) {{"
);
for gl_command in self.gl_commands.iter() {
if gl_command.extensions.is_none() {
show!(
f,
" self.{short_name}_load_with_dyn(get_proc_address);",
short_name = &gl_command.name[2..]
);
} else {
let InfoForGlCommandPrinting { extensions, .. } =
InfoForGlCommandPrinting::from_command_and_api(
gl_command,
self.api,
self.major_version_number,
true,
);
show!(
f,
" {extensions}{{self.{short_name}_load_with_dyn(get_proc_address);}}",
short_name = &gl_command.name[2..],
extensions = extensions,
);
}
}
show!(f, " }}");
for gl_command in self.gl_commands.iter() {
let InfoForGlCommandPrinting {
name,
rust_return_type,
arg_name_and_type_list,
arg_name_list,
docs,
atomic_ptr_name,
trace_fmt,
trace_args,
error_check,
arity,
extensions,
} = InfoForGlCommandPrinting::from_command_and_api(
gl_command,
self.api,
self.major_version_number,
true,
);
let short_name = &name[2..];
struct_fields.push(format!(
"{extensions}{atomic_ptr_name}: APcv",
atomic_ptr_name = atomic_ptr_name,
extensions = extensions,
));
show!(
f,
"{docs}
#[cfg_attr(feature=\"inline\", inline)]
#[cfg_attr(feature=\"inline_always\", inline(always))]
{extensions}pub unsafe fn {short_name}(&self, {arg_name_and_type_list}){rust_return_type} {{
#[cfg(all(debug_assertions, feature = \"debug_trace_calls\"))]
{{
trace!(\"calling gl.{short_name}({trace_fmt});\", {trace_args});
}}
let out = call_atomic_ptr_{arity}arg(\"{name}\", &self.{atomic_ptr_name}, {arg_name_list});
{error_check}
out
}}
{extensions}#[doc(hidden)]
pub unsafe fn {short_name}_load_with_dyn(
&self,
get_proc_address: &mut dyn FnMut(*const c_char) -> *mut c_void
) -> bool {{
load_dyn_name_atomic_ptr(get_proc_address, b\"{name}\\0\", &self.{atomic_ptr_name})
}}
#[inline]
#[doc(hidden)]
{extensions}pub fn {short_name}_is_loaded(&self) -> bool {{
!self.{atomic_ptr_name}.load(RELAX).is_null()
}}",
name = name,
short_name = short_name,
arg_name_and_type_list = arg_name_and_type_list,
rust_return_type = rust_return_type,
docs = docs,
atomic_ptr_name = atomic_ptr_name,
arg_name_list = arg_name_list,
trace_fmt = trace_fmt,
trace_args = trace_args,
error_check = error_check,
arity = arity,
extensions = extensions,
);
}
show!(
f,
" }}
/// This holds the many, many function pointers for GL.
///
/// It's typically quite large (hundreds of pointers), depending on what API level and extensions you selected during the generation.
#[repr(C)]
pub struct GlFns {{"
);
for struct_field in struct_fields.iter() {
show!(f, " {},", struct_field);
}
show!(
f,
" }}
#[cfg(feature=\"bytemuck\")] unsafe impl bytemuck::Zeroable for GlFns {{ }}
impl core::fmt::Debug for GlFns {{ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {{ write!(f, \"GlFns\") }} }}
"
);
Ok(())
}
}
struct InfoForGlCommandPrinting {
name: String,
atomic_ptr_name: String,
arg_name_and_type_list: String,
arg_name_list: String,
rust_return_type: String,
docs: String,
trace_fmt: String,
trace_args: String,
error_check: String,
arity: usize,
extensions: String,
}
impl InfoForGlCommandPrinting {
fn from_command_and_api(
gl_command: &GlCommand, api: ApiGroup, major_version_number: i32,
struct_mode: bool,
) -> Self {
let name = gl_command.name.clone();
let atomic_ptr_name = format!("{name}_p", name = name);
let rust_return_type = {
let c_return_type =
&gl_command.proto[..gl_command.proto.len() - gl_command.name.len()];
if c_return_type.trim() == "void" {
String::new()
} else {
format!(" -> {}", c_type_to_rust_type(c_return_type))
}
};
let mut arg_name_and_type_list = String::new();
let mut arg_name_list = String::new();
let mut fn_type_list = String::new();
let mut docs_notes_list = String::new();
let mut trace_fmt = String::new();
let mut trace_args = String::new();
let arity = gl_command.params.len();
for gl_command_param in gl_command.params.iter() {
let mut words_iter = gl_command_param.text.split_whitespace();
let arg_name = {
let temp_name = words_iter.next_back().unwrap();
if temp_name == "type" {
"type_"
} else if temp_name == "ref" {
"ref_"
} else {
temp_name
}
};
let arg_type_text = gl_command_param.text
[..gl_command_param.text.len() - arg_name.len()]
.trim();
let arg_type = c_type_to_rust_type(arg_type_text);
if !arg_name_and_type_list.is_empty() {
arg_name_and_type_list.push_str(", ")
}
if !arg_name_list.is_empty() {
arg_name_list.push_str(", ")
}
if !fn_type_list.is_empty() {
fn_type_list.push_str(", ")
}
if !trace_fmt.is_empty() {
trace_fmt.push_str(", ")
}
if !trace_args.is_empty() {
trace_args.push_str(", ")
}
arg_name_and_type_list.push_str(arg_name);
arg_name_list.push_str(arg_name);
arg_name_and_type_list.push_str(": ");
arg_name_and_type_list.push_str(&arg_type);
fn_type_list.push_str(&arg_type);
if arg_type.contains('*') || arg_type.as_str() == "GLsync" {
trace_fmt.push_str("{:p}");
trace_args.push_str(arg_name);
} else if arg_type == "GLenum" {
trace_fmt.push_str("{:#X}");
trace_args.push_str(arg_name);
} else if [
"GLDEBUGPROC",
"GLDEBUGPROCARB",
"GLDEBUGPROCKHR",
"GLDEBUGPROCAMD",
"GLVULKANPROCNV",
]
.contains(&arg_type.as_str())
{
trace_fmt.push_str("{:?}");
trace_args.push_str("transmute::<_, Option<fn()>>(");
trace_args.push_str(arg_name);
trace_args.push_str(")");
} else {
trace_fmt.push_str("{:?}");
trace_args.push_str(arg_name);
};
if let Some(group_text) = gl_command_param.group.as_ref() {
if group_text == "Boolean" && arg_type.contains("GLboolean") {
} else {
docs_notes_list.push_str(&format!(
"/// * `{arg_name}` group: {group_text}\n",
arg_name = arg_name,
group_text = group_text,
));
}
}
if let Some(len_text) = gl_command_param.len.as_ref() {
docs_notes_list.push_str(&format!(
"/// * `{arg_name}` len: {len_text}\n",
arg_name = arg_name,
len_text = len_text,
));
}
}
if let Some(proto_group_text) = gl_command.proto_group.as_ref() {
if proto_group_text != "Boolean" {
docs_notes_list.push_str(&format!(
"/// * return value group: {proto_group_text}\n",
proto_group_text = proto_group_text,
));
}
}
if let Some(alias_of_text) = gl_command.alias_of.as_ref() {
docs_notes_list.push_str(&format!(
"/// * alias of: [`{alias_of_text}`]\n",
alias_of_text = alias_of_text,
));
}
if let Some(vec_equivalent_text) = gl_command.vec_equivalent.as_ref() {
docs_notes_list.push_str(&format!(
"/// * vector equivalent: [`{vec_equivalent_text}`]\n",
vec_equivalent_text = vec_equivalent_text,
));
}
docs_notes_list.pop();
let docs_name: String = (|| {
const SUFFIX_LIST: &[&str] = &[
"Matrix2x3fv",
"Matrix3x2fv",
"Matrix2x4fv",
"Matrix4x2fv",
"Matrix3x4fv",
"Matrix4x3fv",
"Matrix2fv",
"Matrix3fv",
"Matrix4fv",
"i64v",
"1uiv",
"2uiv",
"3uiv",
"4uiv",
"1ui",
"2ui",
"3ui",
"4ui",
"1fv",
"2fv",
"3fv",
"4fv",
"1iv",
"2iv",
"3iv",
"4iv",
"iv",
"fv",
"1f",
"2f",
"3f",
"4f",
"1i",
"2i",
"3i",
"4i",
];
let name = name.as_str();
for suffix in SUFFIX_LIST.iter().copied() {
if name.ends_with(suffix) {
return name[..name.len() - suffix.len()].to_string();
}
}
name.to_string()
})();
let docs = match api {
ApiGroup::Gl if major_version_number >= 2 => format!(
"/// [{name}](http://docs.gl/gl{major_version_number}/{docs_name})({arg_name_list}){newline_if_notes}{docs_notes_list}",
name = name,
docs_name = docs_name,
arg_name_list = arg_name_list,
newline_if_notes = if docs_notes_list.is_empty() { "" } else { "\n" },
docs_notes_list = docs_notes_list,
major_version_number = major_version_number,
),
ApiGroup::Gles2 => format!(
"/// [{name}](http://docs.gl/es{major_version_number}/{docs_name})({arg_name_list}){newline_if_notes}{docs_notes_list}",
name = name,
docs_name = docs_name,
arg_name_list = arg_name_list,
newline_if_notes = if docs_notes_list.is_empty() { "" } else { "\n" },
docs_notes_list = docs_notes_list,
major_version_number = major_version_number,
),
_ => format!(
"/// {name}({arg_name_list}){newline_if_notes}{docs_notes_list}",
name = name,
arg_name_list = arg_name_list,
newline_if_notes = if docs_notes_list.is_empty() { "" } else { "\n" },
docs_notes_list = docs_notes_list,
),
};
let error_check = if name != "glGetError" {
if struct_mode {
format!(
"#[cfg(all(debug_assertions, feature = \"debug_automatic_glGetError\"))]
{{
self.automatic_glGetError(\"{name}\");
}}",
name = name,
)
} else {
format!(
"#[cfg(all(debug_assertions, feature = \"debug_automatic_glGetError\"))]
{{
global_automatic_glGetError(\"{name}\");
}}",
name = name,
)
}
} else {
String::from("")
};
let mut extensions = String::from("");
if let Some(list) = gl_command.extensions.as_ref() {
extensions.push_str("#[cfg(any(");
for (i, l) in list.iter().enumerate() {
if i != 0 {
extensions.push(',');
}
write!(extensions, "feature=\"{l}\"", l = l).unwrap();
}
extensions.push_str("))]");
extensions.push_str("#[cfg_attr(docs_rs, doc(cfg(any(");
for (i, l) in list.iter().enumerate() {
if i != 0 {
extensions.push(',');
}
write!(extensions, "feature=\"{l}\"", l = l).unwrap();
}
extensions.push_str("))))]");
};
Self {
name,
arg_name_and_type_list,
arg_name_list,
rust_return_type,
docs,
atomic_ptr_name,
trace_fmt,
trace_args,
error_check,
arity,
extensions,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct GlCommandParam {
text: String,
group: Option<String>,
len: Option<String>,
}
impl GlCommandParam {
fn from_iter_and_attrs<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>, attrs: &str,
) -> Self {
let mut text = String::new();
let mut group = None;
let mut len = None;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"group" => group = Some(String::from(value)),
"len" => len = Some(String::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "param" } => break,
StartTag {
name: "ptype",
attrs: "",
} => text.push_str(grab_out_ptype_text(iter)),
StartTag {
name: "name",
attrs: "",
} => {
text.push(' ');
text.push_str(grab_out_name_text(iter))
}
Text(t) => text.push_str(t),
unknown => panic!("unknown: {:?}", unknown),
}
}
Self { text, group, len }
}
}
#[derive(Debug, Default, Clone)]
pub struct GlFeature {
pub api: ApiGroup,
pub name: String,
pub number: String,
pub required: Vec<GlRequirement>,
pub remove: Vec<GlRemoval>,
}
impl GlFeature {
fn from_iter_and_attrs<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>, attrs: &str,
) -> Self {
let mut feature = Self::default();
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"api" => feature.api = ApiGroup::from(value),
"name" => feature.name.push_str(value),
"number" => feature.number.push_str(value),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "feature" } => return feature,
StartTag {
name: "require",
attrs,
} => {
let mut profile = None;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"comment" => (),
"profile" => profile = Some(String::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "require" } => break,
EmptyTag {
name: "type",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.required.push(GlRequirement {
profile: profile.clone(),
api: None,
adjustment: ReqRem::Type(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "enum",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.required.push(GlRequirement {
profile: profile.clone(),
api: None,
adjustment: ReqRem::Enum(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "command",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.required.push(GlRequirement {
profile: profile.clone(),
api: None,
adjustment: ReqRem::Command(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "require",
attrs: _,
} => (),
StartTag {
name: "remove",
attrs,
} => {
let mut profile = None;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"comment" => (),
"profile" => profile = Some(String::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "remove" } => break,
EmptyTag {
name: "type",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.remove.push(GlRemoval {
profile: profile.clone(),
adjustment: ReqRem::Type(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "enum",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.remove.push(GlRemoval {
profile: profile.clone(),
adjustment: ReqRem::Enum(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "command",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => feature.remove.push(GlRemoval {
profile: profile.clone(),
adjustment: ReqRem::Command(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
unknown => panic!("{:?}", unknown),
}
}
}
unknown => panic!("unknown 'feature' content:{:?}", unknown),
}
}
}
}
#[derive(Debug, Clone)]
pub struct GlRequirement {
pub profile: Option<String>,
pub api: Option<ApiGroup>,
pub adjustment: ReqRem,
}
#[derive(Debug, Clone)]
pub struct GlRemoval {
profile: Option<String>,
adjustment: ReqRem,
}
#[derive(Debug, Clone)]
pub enum ReqRem {
Type(String),
Enum(String),
Command(String),
}
#[derive(Debug, Default, Clone)]
pub struct GlExtension {
pub name: String,
pub supported: String,
pub required: Vec<GlRequirement>,
}
impl GlExtension {
fn from_iter_and_attrs<'s>(
iter: &mut impl Iterator<Item = XmlElement<'s>>, attrs: &str,
) -> Self {
let mut extension = Self::default();
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"name" => extension.name.push_str(value),
"supported" => extension.supported.push_str(value),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "extension" } => return extension,
StartTag {
name: "require",
attrs,
} => {
let mut profile = None;
let mut api = None;
for TagAttribute { key, value } in TagAttributeIterator::new(attrs) {
match key {
"comment" => (),
"profile" => profile = Some(String::from(value)),
"api" => api = Some(ApiGroup::from(value)),
unknown => panic!("unknown: {:?}", unknown),
}
}
loop {
match iter.next().unwrap() {
EndTag { name: "require" } => break,
EmptyTag {
name: "type",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => extension.required.push(GlRequirement {
profile: profile.clone(),
api,
adjustment: ReqRem::Type(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "enum",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => extension.required.push(GlRequirement {
profile: profile.clone(),
api,
adjustment: ReqRem::Enum(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
EmptyTag {
name: "command",
attrs,
} => {
for TagAttribute { key, value } in
TagAttributeIterator::new(attrs)
{
match key {
"name" => extension.required.push(GlRequirement {
profile: profile.clone(),
api,
adjustment: ReqRem::Command(String::from(value)),
}),
"comment" => (),
unknown => panic!("unknown: {:?}", unknown),
}
}
}
unknown => panic!("unknown: {:?}", unknown),
}
}
}
unknown => panic!("unknown 'feature' content:{:?}", unknown),
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApiGroup {
Gl,
Gles1,
Gles2,
Glsc2,
}
impl ApiGroup {
pub fn supported(&self) -> &'static str {
match self {
ApiGroup::Gl => "gl",
ApiGroup::Gles1 => "gles1",
ApiGroup::Gles2 => "gles2",
ApiGroup::Glsc2 => "glsc2",
}
}
}
impl Default for ApiGroup {
fn default() -> Self {
ApiGroup::Gl
}
}
impl From<&str> for ApiGroup {
fn from(s: &str) -> Self {
match s {
"gl" => ApiGroup::Gl,
"gles1" => ApiGroup::Gles1,
"gles2" => ApiGroup::Gles2,
"glsc2" => ApiGroup::Glsc2,
_ => panic!("illegal:{}", s),
}
}
}