#![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 STANDARD_CRATE_DOCS: &str = concat!(
r#"//!
//! Generated by [phosphorus](https://docs.rs/phosphorus/"#,
env!("CARGO_PKG_VERSION"),
r#"/phosphorus/).
//!
//! 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.
//!
//! # Docs.rs
//! 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."#
);
#[derive(Debug, Clone, Default)]
pub struct GlApiSelection {
gl_types: Vec<GlType>,
gl_enums: HashMap<String, GlEnum>,
gl_commands: HashMap<String, GlCommand>,
api: ApiGroup,
version: (i32, i32),
}
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, "{}", STANDARD_CRATE_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;"
);
show!(
f,
"#[cfg(feature=\"chlorine\")]use chlorine::*;
#[cfg(not(feature=\"chlorine\"))]use std::os::raw::*;"
);
show!(
f,
"
#[cfg(feature = \"log\")] use log::{{error, trace}};
#[cfg(all(
not(feature = \"log\"),
any(
feature = \"debug_trace_calls\",
feature = \"debug_automatic_glGetError\"
)
))]
use std::{{println, eprintln}};
#[cfg(all(
not(feature = \"log\"),
feature = \"debug_trace_calls\"
))]
macro_rules! trace {{ ($($arg:tt)*) => {{ println!($($arg)*) }} }}
#[cfg(all(
not(feature = \"log\"),
feature = \"debug_automatic_glGetError\"
))]
macro_rules! error {{ ($($arg:tt)*) => {{ eprintln!($($arg)*) }} }}"
);
show!(
f,
"
use core::{{
sync::atomic::{{AtomicPtr, Ordering}},
mem::transmute,
ptr::null_mut,
}};
const RELAX: Ordering = Ordering::Relaxed;
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)] mod 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, "mod enums {{");
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)]
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)]
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().cast());
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 == usize::MAX || p_usize < 8 {{
ptr.store(null_mut(), RELAX);
false
}} else {{
ptr.store(p, RELAX);
true
}}
}}
#[inline(never)]
#[cfg(feature = \"debug_automatic_glGetError\")]
fn report_error_as_necessary_from(name: &str, err: GLenum) {{
match err {{
GL_NO_ERROR => (),
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),
}}
}}
/// The number of GL commands that were output by the bindings generator.
pub const NUMBER_OF_GENERATED_GL_COMMANDS: usize = {count};",
count = command_list.len()
);
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)]
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\")] mod global_commands {{");
show!(f, " use super::*;");
show!(
f,
"
/// 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 similar function, depending on your OS.
///
/// This returns the number of functions it loaded. You can compare it to the[`NUMBER_OF_GENERATED_GL_COMMANDS`] value, if you want.
pub fn load_global_gl_with<F>(
mut get_proc_address: F,
) -> usize
where
F: FnMut(*const c_char) -> *mut c_void
{{
let mut count = 0;"
);
for gl_command in command_list.iter() {
show!(
f,
" count += {name}_load_with_dyn(&mut get_proc_address) as usize;",
name = gl_command.name
);
}
show!(f, " count\n }}");
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\")] mod struct_commands {{");
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)),
}
}
}
for extension_name in extensions {
let the_extension = reg
.gl_extensions
.iter()
.find(|gl_ext| gl_ext.name.as_str() == *extension_name)
.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, 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),
}
}
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 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 }
}
}
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(gl_xml: &str) -> Self {
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)]
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)]
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, "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) => {
assert_eq!(s, "#ifdef __APPLE__\r\ntypedef void *GLhandleARB;\r\n#else\r\ntypedef unsigned int GLhandleARB;\r\n#endif");
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;"#
)
}
}
}
}
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,
}
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 }
}
}
#[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('`');
}
write!(
f,
"#[doc = \"{doc}\"]\npub const {name}: {ty} = {val};",
name = name,
ty = ty,
val = val,
doc = doc
)
}
}
#[derive(Debug, Default, Clone)]
pub struct GlCommand {
name: String,
proto: String,
proto_group: Option<String>,
params: Vec<GlCommandParam>,
glx_attrs: Option<String>,
alias_of: Option<String>,
vec_equivalent: Option<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,
} = InfoForGlCommandPrinting::from_command_and_api(
self.gl_command,
self.api,
self.major_version_number,
);
write!(
f,
"{docs}
#[cfg_attr(feature=\"inline\", inline)]
#[cfg_attr(feature=\"inline_always\", inline(always))]
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
}}
static {atomic_ptr_name}: APcv = ap_null();
/// Tries to load [`{name}`], returns if a non-null pointer was obtained.
///
/// # Safety
/// * This function promises to always pass a null terminated pointer to your closure.
#[doc(hidden)]
pub 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)]
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,
)
}
}
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 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 = unsafe {{ core::mem::zeroed() }};
out.load_all_with_dyn(&mut get_proc_address);
out
}}
/// Loads all pointers using the `get_proc_address` given.
#[doc(hidden)]
#[inline(never)]
pub 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() {
show!(
f,
" self.{short_name}_load_with_dyn(get_proc_address);",
short_name = &gl_command.name[2..]
);
}
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,
} = InfoForGlCommandPrinting::from_command_and_api(
gl_command,
self.api,
self.major_version_number,
);
let short_name = &name[2..];
struct_fields.push(format!(
"{atomic_ptr_name}: APcv",
atomic_ptr_name = atomic_ptr_name,
));
show!(
f,
"{docs}
#[cfg_attr(feature=\"inline\", inline)]
#[cfg_attr(feature=\"inline_always\", inline(always))]
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
}}
#[doc(hidden)]
pub 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)]
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,
);
}
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,
}
impl InfoForGlCommandPrinting {
fn from_command_and_api(
gl_command: &GlCommand,
api: ApiGroup,
major_version_number: i32,
) -> 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 = match name.as_str() {
"glGetBufferParameteriv"
| "glGetBufferParameteri64v"
| "glGetNamedBufferParameteriv"
| "glGetNamedBufferParameteri64v" => "glGetBufferParameter".to_string(),
otherwise => String::from(otherwise),
};
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" {
format!(
"#[cfg(all(debug_assertions, feature = \"debug_automatic_glGetError\"))]
{{
report_error_as_necessary_from(\"{name}\", glGetError());
}}",
name = name,
)
} else {
String::from("")
};
Self {
name,
arg_name_and_type_list,
arg_name_list,
rust_return_type,
docs,
atomic_ptr_name,
trace_fmt,
trace_args,
error_check,
arity,
}
}
}
#[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: api.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" => extension.required.push(GlRequirement {
profile: profile.clone(),
api: api.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" => extension.required.push(GlRequirement {
profile: profile.clone(),
api: api.clone(),
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),
}
}
}