#![allow(clippy::trivial_regex)] #![allow(clippy::useless_format)] extern crate gl_generator; use std::env::var; use std::fs::File; use std::io::{Error as IoError, Write}; use std::path::Path; use gl_generator::{ generators::{gen_struct_name, gen_symbol_name, gen_types}, Api, Cmd, Fallbacks, Generator as GlGenerator, Profile, Registry, }; use lazy_static::lazy_static; use regex::{Captures, Regex}; fn main() { let out_dir = var("OUT_DIR").unwrap(); let mut file = File::create(&Path::new(&out_dir).join("bindings.rs")).unwrap(); let generator = Generator { generate_debug: var("CARGO_FEATURE_GENERATE_DEBUG").is_ok(), generate_global: var("CARGO_FEATURE_GENERATE_GLOBAL").is_ok(), generate_struct: var("CARGO_FEATURE_GENERATE_STRUCT").is_ok(), log_fn: if var("CARGO_FEATURE_USE_LOG_CRATE").is_ok() { "log::trace!" } else { "println!" }, }; Registry::new(Api::Gl, (4, 5), Profile::Compatibility, Fallbacks::All, []) .write_bindings(generator, &mut file) .unwrap(); } pub struct Generator { generate_debug: bool, generate_global: bool, generate_struct: bool, log_fn: &'static str, } impl GlGenerator for Generator { fn write(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { self.write_header(dest)?; self.write_type_aliases(registry, dest)?; self.write_enums(registry, dest)?; self.write_fnptr_struct_def(registry, dest)?; if self.generate_global { self.write_load_fn(registry, dest)?; self.write_fns(registry, dest)?; self.write_ptrs(registry, dest)?; self.write_fn_mods(registry, dest)?; } if self.generate_struct { self.write_struct_def(registry, dest)?; self.write_struct_impl(registry, dest)?; } Ok(()) } } impl Generator { fn write_header(&self, dest: &mut W) -> Result<(), IoError> where W: Write, { writeln!( dest, r#" mod __gl_imports {{ pub use std::os::raw; pub use std::mem::transmute; }}"# )?; Ok(()) } fn write_type_aliases(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { writeln!( dest, r#" pub mod types {{"# )?; gen_types(registry.api, dest)?; writeln!( dest, r#" }}"# )?; Ok(()) } fn write_enums(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { for enm in ®istry.enums { writeln!( dest, r#"pub const {ident}: {types_prefix}{ty} = {value}{cast_suffix};"#, ident = enm.ident, types_prefix = if enm.ty == "&'static str" { "" } else { "types::" }, ty = enm.ty, value = enm.value, cast_suffix = match enm.cast { true => format!(" as types::{}", enm.ty), false => String::new(), }, )?; } Ok(()) } fn write_fnptr_struct_def(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { writeln!( dest, r#" #[derive(Clone)] pub struct FnPtr {{ f: *const std::os::raw::c_void, is_loaded: bool, }} impl FnPtr {{ fn new(ptr: *const std::os::raw::c_void) -> FnPtr {{ if ptr.is_null() {{ FnPtr {{ f: missing_fn_panic as *const std::os::raw::c_void, is_loaded: false }} }} else {{ FnPtr {{ f: ptr, is_loaded: true }} }} }} #[inline] pub fn is_loaded(&self) -> bool {{ self.is_loaded }} }} #[inline(never)] fn missing_fn_panic() -> ! {{ panic!("{api} function was not loaded") }} #[inline(never)] fn metaloadfn( loadfn: &mut dyn FnMut(&'static str) -> *const std::os::raw::c_void, symbol: &'static str, fallbacks: &[&'static str]) -> *const std::os::raw::c_void {{ let mut ptr = loadfn(symbol); if ptr.is_null() {{ for &sym in fallbacks {{ ptr = loadfn(sym); if !ptr.is_null() {{ break; }} }} }} ptr }}"#, api = registry.api, ) } fn write_load_fn(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { write!( dest, r#" pub fn load_with(mut loadfn: F) where F: FnMut(&'static str) -> *const __gl_imports::raw::c_void {{ #[inline(never)] fn inner(loadfn: &mut dyn FnMut(&'static str) -> *const __gl_imports::raw::c_void) {{"# )?; for c in ®istry.cmds { write!( dest, r#" {cmd_name}::load_with(&mut *loadfn);"#, cmd_name = to_snake_case(&c.proto.ident), )?; } writeln!( dest, r#" }} inner(&mut loadfn) }}"# )?; Ok(()) } fn write_fns(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { let has_get_error = registry .cmds .iter() .any(|cmd| cmd.proto.ident == "GetError"); for cmd in ®istry.cmds { self.gen_func(dest, cmd, has_get_error, true)?; } Ok(()) } fn write_ptrs(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { writeln!( dest, r#" mod storage {{ use super::__gl_imports::raw; use super::FnPtr;"# )?; for c in ®istry.cmds { writeln!( dest, r#" pub static mut {name}: FnPtr = FnPtr {{ f: super::missing_fn_panic as *const raw::c_void, is_loaded: false }};"#, name = to_snake_case(&c.proto.ident) )?; } writeln!( dest, r#" }}"# )?; Ok(()) } fn write_fn_mods(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { for c in ®istry.cmds { let fallbacks = match registry.aliases.get(&c.proto.ident) { Some(v) => { let names = v .iter() .map(|name| format!("\"{}\"", gen_symbol_name(registry.api, &name))) .collect::>(); format!("&[{}]", names.join(", ")) } None => "&[]".to_string(), }; let fnname = to_snake_case(&c.proto.ident); let symbol = gen_symbol_name(registry.api, &c.proto.ident); let symbol = &symbol; writeln!( dest, r##" pub mod {fnname} {{ use super::{{storage, metaloadfn}}; use super::__gl_imports::raw; use super::FnPtr; #[inline] pub fn is_loaded() -> bool {{ unsafe {{ storage::{fnname}.is_loaded }} }} pub fn load_with(mut loadfn: F) where F: FnMut(&'static str) -> *const raw::c_void {{ unsafe {{ storage::{fnname} = FnPtr::new(metaloadfn(&mut loadfn, "{symbol}", {fallbacks})) }} }} }}"##, fnname = fnname, fallbacks = fallbacks, symbol = symbol )?; } Ok(()) } fn write_struct_def(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { writeln!( dest, r#" #[derive(Clone)] pub struct {api} {{"#, api = gen_struct_name(registry.api) )?; for cmd in ®istry.cmds { writeln!( dest, r#" pub {name}: FnPtr,"#, name = to_snake_case(&cmd.proto.ident) )?; } writeln!(dest, r#" }}"#)?; Ok(()) } fn write_struct_impl(&self, registry: &Registry, dest: &mut W) -> Result<(), IoError> where W: Write, { write!( dest, r#" impl {api} {{ pub fn load_with(mut loadfn: F) -> {api} where F: FnMut(&'static str) -> *const std::os::raw::c_void {{ let mut do_loadfn = |symbol: &'static str, symbols: &[&'static str]| {{ metaloadfn(&mut loadfn, symbol, symbols) }}; {api} {{"#, api = gen_struct_name(registry.api) )?; for cmd in ®istry.cmds { write!( dest, r#" {name}: FnPtr::new(do_loadfn("{symbol}", &[{fallbacks}])),"#, name = to_snake_case(&cmd.proto.ident), symbol = gen_symbol_name(registry.api, &cmd.proto.ident), fallbacks = match registry.aliases.get(&cmd.proto.ident) { Some(fbs) => fbs .iter() .map(|name| format!(r#""{}""#, gen_symbol_name(registry.api, &name))) .collect::>() .join(", "), None => format!(""), }, )? } writeln!( dest, r#" }} }}"# )?; let has_get_error = registry .cmds .iter() .any(|cmd| cmd.proto.ident == "GetError"); for cmd in ®istry.cmds { self.gen_func(dest, cmd, has_get_error, false)?; } writeln!( dest, r#" }} unsafe impl std::marker::Send for {api} {{}}"#, api = gen_struct_name(registry.api) ) } fn gen_func( &self, dest: &mut W, cmd: &Cmd, has_get_error: bool, is_global: bool, ) -> Result<(), IoError> where W: Write, { let ident = to_snake_case(&cmd.proto.ident); let idents = gen_parameters(cmd, true, false); let typed_params = gen_parameters(cmd, false, true); let mut print_ln = String::new(); let mut print_err = String::new(); if self.generate_debug { print_ln = format!( r#" {log_fn}("[OpenGL] {}({})" {}); "#, ident, (0..idents.len()) .map(|_| "{:?}".to_string()) .collect::>() .join(", "), idents .iter() .zip(typed_params.iter()) .map(|(name, ty)| if ty.contains("GLDEBUGPROC") { format!(r#", """#) } else { format!(r#", {}"#, name) }) .collect::>() .concat(), log_fn = self.log_fn, ); if cmd.proto.ident != "GetError" && has_get_error { print_err = format!( r#" let __get_err = __gl_imports::transmute::<_, extern "system" fn() -> u32>({this}get_error.f); match __get_err() {{ 0 => (), r => {log_fn}("[OpenGL] ^ GL error triggered: {{}}", r), }} "#, this = if is_global { "storage::" } else { "self." }, log_fn = self.log_fn, ); } } write!( dest, r#" #[inline] pub fn {name}({this_ty}{params}) -> {return_suffix} {{ unsafe {{{print_ln} let __f = __gl_imports::transmute::<_, extern "system" fn({typed_params}) -> {return_suffix}>({this}{name}.f); let __r = __f({idents}); {print_err} __r }} }}"#, this_ty = if is_global { "" } else { "&self, " }, this = if is_global { "storage::" } else { "self." }, name = ident, params = gen_parameters(cmd, true, true).join(", "), typed_params = typed_params.join(", "), return_suffix = cmd.proto.ty, idents = idents.join(", "), print_ln = print_ln, print_err = print_err, ) } } pub fn gen_parameters(cmd: &Cmd, with_idents: bool, with_types: bool) -> Vec { cmd.params .iter() .map(|binding| { if with_idents && with_types { format!("{}: {}", to_snake_case(&binding.ident), binding.ty) } else if with_types { format!("{}", binding.ty) } else if with_idents { format!("{}", to_snake_case(&binding.ident)) } else { panic!() } }) .collect() } fn to_snake_case(s: &str) -> String { lazy_static! { static ref RX01: Regex = Regex::new(r"([a-z])([A-Z])").unwrap(); static ref RX02: Regex = Regex::new(r"([a-z])([0-9][0-9A-Za-z]+)$").unwrap(); static ref RX03: Regex = Regex::new(r"([A-Z])([A-Z][a-z]+)").unwrap(); static ref RX04: Regex = Regex::new(r"([A-Z])([0-9])").unwrap(); static ref RX05: Regex = Regex::new(r"([A-Z][a-z]*)(uiv|usv)$").unwrap(); static ref RX06: Regex = Regex::new(r"([A-Z][a-z]*)(dv|fv|iv)$").unwrap(); static ref RX07: Regex = Regex::new(r"([A-Z][a-z]*)(v|f|i)$").unwrap(); static ref RX08: Regex = Regex::new(r"i_64").unwrap(); static ref RX09: Regex = Regex::new(r"u_i64").unwrap(); static ref RX10: Regex = Regex::new(r"f_i").unwrap(); static ref RX11: Regex = Regex::new(r"([a-z]{3,})i$").unwrap(); static ref RX12: Regex = Regex::new(r"([a-z])i_v").unwrap(); static ref RX13: Regex = Regex::new(r"([a-z])i64_v").unwrap(); static ref RX14: Regex = Regex::new(r"[Ii]nteger_64").unwrap(); } let s = RX01.replace_all(s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX02.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX03.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX04.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX05.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX06.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX07.replace_all(&s, |c: &Captures| format!("{}_{}", &c[1], &c[2])); let s = RX08.replace_all(&s, "_i64"); let s = RX09.replace_all(&s, "_ui64"); let s = RX10.replace_all(&s, "_fi"); let s = RX11.replace_all(&s, |c: &Captures| format!("{}_i", &c[1])); let s = RX12.replace_all(&s, |c: &Captures| format!("{}_i_v", &c[1])); let s = RX13.replace_all(&s, |c: &Captures| format!("{}_i64_v", &c[1])); let s = RX14.replace_all(&s, "Integer64"); s.into_owned().to_lowercase() }