diff options
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | build.rs | 23 | ||||
-rw-r--r-- | codegen/bindings.rs | 24 | ||||
-rw-r--r-- | codegen/command/functions.rs | 62 | ||||
-rw-r--r-- | codegen/command/globals.rs | 15 | ||||
-rw-r--r-- | codegen/command/mod.rs | 2 | ||||
-rw-r--r-- | codegen/formatting.rs | 54 | ||||
-rw-r--r-- | codegen/load_with.rs | 28 | ||||
-rw-r--r-- | codegen/mod.rs | 72 | ||||
-rw-r--r-- | src/lib.rs | 45 |
10 files changed, 272 insertions, 60 deletions
@@ -5,7 +5,12 @@ edition = "2021" description = "Low-level OpenGL bindings" [dependencies] -opengl-registry-macros = "0.1.0" [build-dependencies] bindgen = "0.63.0" +opengl-registry = "0.2.0" +quote = "1.0.23" +proc-macro2 = "1.0.56" +convert_case = "0.6.0" +regex = "1.7.1" +once_cell = "1.17.0" @@ -2,26 +2,17 @@ use std::env; use std::error::Error; use std::path::PathBuf; -use bindgen::{CodegenConfig, MacroTypeVariation}; +use crate::codegen::bindings::generate_bindings; +use crate::codegen::generate_using_registry; + +mod codegen; pub fn main() -> Result<(), Box<dyn Error>> { - println!("cargo:rerun-if-changed=gl.h"); - - let bindings = bindgen::Builder::default() - .header("gl.h") - .default_macro_constant_type(MacroTypeVariation::Signed) - .with_codegen_config(CodegenConfig::all() & !CodegenConfig::FUNCTIONS) - .allowlist_type("GL.*") - .allowlist_type("_cl_.*") - .allowlist_var("GL_.*") - .blocklist_item("GL_Z4.*") - .blocklist_item("GL_Z6.*") - .generate()?; - - let out_path = PathBuf::from(env::var("OUT_DIR")?); + let out_dir = PathBuf::from(env::var("OUT_DIR")?); - bindings.write_to_file(out_path.join("bindings.rs"))?; + generate_bindings(&out_dir.join("bindings.rs"))?; + generate_using_registry(&out_dir.join("generated.rs"))?; Ok(()) } diff --git a/codegen/bindings.rs b/codegen/bindings.rs new file mode 100644 index 0000000..d6b7e0d --- /dev/null +++ b/codegen/bindings.rs @@ -0,0 +1,24 @@ +use std::error::Error; +use std::path::Path; + +use bindgen::{CodegenConfig, MacroTypeVariation}; + +pub fn generate_bindings(dest_file: &Path) -> Result<(), Box<dyn Error>> +{ + println!("cargo:rerun-if-changed=gl.h"); + + let bindings = bindgen::Builder::default() + .header("gl.h") + .default_macro_constant_type(MacroTypeVariation::Signed) + .with_codegen_config(CodegenConfig::all() & !CodegenConfig::FUNCTIONS) + .allowlist_type("GL.*") + .allowlist_type("_cl_.*") + .allowlist_var("GL_.*") + .blocklist_item("GL_Z4.*") + .blocklist_item("GL_Z6.*") + .generate()?; + + bindings.write_to_file(dest_file)?; + + Ok(()) +} diff --git a/codegen/command/functions.rs b/codegen/command/functions.rs new file mode 100644 index 0000000..6d1a3d3 --- /dev/null +++ b/codegen/command/functions.rs @@ -0,0 +1,62 @@ +use std::error::Error; +use std::str::FromStr; + +use opengl_registry::command::Command; +use proc_macro2::{LexError, TokenStream}; +use quote::{format_ident, quote}; + +use crate::codegen::formatting::{clean_param_name, fix_type}; + +pub fn create_command_functions( + commands: &[Command], +) -> Result<Vec<TokenStream>, Box<dyn Error>> +{ + commands.iter().map(|command| { + let command_name_str = command.prototype().name(); + + let command_name = format_ident!("{}", command.prototype().name()); + + let command_name_prefixless = format_ident!("{}", command.prototype() + .name() + .strip_prefix("gl") + .unwrap_or_else(|| command.prototype().name())); + + let command_args = command + .parameters() + .iter() + .map(|param| { + let param_name = format_ident!("{}", clean_param_name(param.name())); + + let param_type = TokenStream::from_str(&fix_type(param.get_type()))?; + + Ok::<_, LexError>(quote! { #param_name: #param_type }) + }).collect::<Result<Vec<_>, _>>()?; + + let command_return_type = TokenStream::from_str(&fix_type(command.prototype().return_type()))?; + + let command_arg_types = command + .parameters() + .iter() + .map(|param| TokenStream::from_str(&fix_type(param.get_type()))) + .collect::<Result<Vec<_>, _>>()?; + + let command_arg_names = command + .parameters() + .iter() + .map(|param| format_ident!("{}", clean_param_name(param.name()))) + .collect::<Vec<_>>(); + + Ok(quote! { + #[doc = #command_name_str] + #[allow(non_snake_case, clippy::too_many_arguments)] + pub unsafe fn #command_name_prefixless(#(#command_args),*) -> #command_return_type { + type GLFunc = + unsafe extern "C" fn(#(#command_arg_types),*) -> #command_return_type; + + let gl_func = std::mem::transmute::<_, GLFunc>(functions::#command_name); + + gl_func(#(#command_arg_names),*) + } + }) + }).collect() +} diff --git a/codegen/command/globals.rs b/codegen/command/globals.rs new file mode 100644 index 0000000..e522e68 --- /dev/null +++ b/codegen/command/globals.rs @@ -0,0 +1,15 @@ +use opengl_registry::command::Command; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn create_command_globals(commands: &[Command]) -> Vec<TokenStream> +{ + commands.iter().map(|command| { + let command_name = format_ident!("{}", command.prototype().name()); + + quote! { + #[allow(non_upper_case_globals)] + pub static mut #command_name: unsafe extern "C" fn() = function_not_loaded; + } + }).collect() +} diff --git a/codegen/command/mod.rs b/codegen/command/mod.rs new file mode 100644 index 0000000..7559bc1 --- /dev/null +++ b/codegen/command/mod.rs @@ -0,0 +1,2 @@ +pub mod functions; +pub mod globals; diff --git a/codegen/formatting.rs b/codegen/formatting.rs new file mode 100644 index 0000000..c480be4 --- /dev/null +++ b/codegen/formatting.rs @@ -0,0 +1,54 @@ +use convert_case::{Case, Casing}; +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; + +static C_PTR_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(const )?([a-zA-Z0-9_]+)\s?\*(.*)").unwrap()); + +static REST_C_PTR_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(const)?\s?\*").unwrap()); + +pub fn clean_param_name(param_name: &str) -> String +{ + match param_name { + "ref" => "reference", + "type" => "ty", + "in" => "inside", + "box" => "a_box", + name => name, + } + .to_case(Case::Snake) +} + +pub fn fix_type(ty: &str) -> String +{ + let ty = ty.replace("struct", ""); + + let ty = c_ptr_to_rust_ptr(&ty); + + ty.replace("void", "std::ffi::c_void") +} + +pub fn c_ptr_to_rust_ptr(c_ptr: &str) -> String +{ + C_PTR_RE + .replace(c_ptr, |captures: &Captures| { + let const_or_mut = captures.get(1).map_or_else(|| "mut", |_| "const"); + + let type_name = captures.get(2).unwrap().as_str(); + + let rest = captures.get(3).unwrap(); + + let rest_ptr_matches = REST_C_PTR_RE.captures_iter(rest.as_str()); + + let rest_ptrs = rest_ptr_matches + .map(|capts| { + let const_or_mut = capts.get(1).map_or_else(|| "mut", |_| "const"); + + format!("*{const_or_mut} ") + }) + .collect::<Vec<_>>(); + + format!("{}*{const_or_mut} {type_name}", rest_ptrs.join(" ")) + }) + .to_string() +} diff --git a/codegen/load_with.rs b/codegen/load_with.rs new file mode 100644 index 0000000..0e8e672 --- /dev/null +++ b/codegen/load_with.rs @@ -0,0 +1,28 @@ +use opengl_registry::command::Command; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +pub fn create_load_with_function(commands: &[Command]) -> TokenStream +{ + let command_assignments = commands.iter().map(|command| { + let command_name = format_ident!("{}", command.prototype().name()); + + let command_name_str = command.prototype().name(); + + quote! { + functions::#command_name = get_proc_addr(#command_name_str); + } + }); + + quote! { + /// Loads OpenGL function pointers using the given function address retriever function. + pub fn load_with<GetProcAddrFn>(get_proc_addr: GetProcAddrFn) + where + GetProcAddrFn: Fn(&str) -> unsafe extern "C" fn(), + { + unsafe { + #(#command_assignments)* + } + } + } +} diff --git a/codegen/mod.rs b/codegen/mod.rs new file mode 100644 index 0000000..42dc9dc --- /dev/null +++ b/codegen/mod.rs @@ -0,0 +1,72 @@ +use std::error::Error; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use opengl_registry::api_interface_definition::FeatureKind; +use opengl_registry::Registry; +use quote::quote; + +use crate::codegen::command::functions::create_command_functions; +use crate::codegen::command::globals::create_command_globals; +use crate::codegen::load_with::create_load_with_function; + +pub mod bindings; + +mod command; +mod formatting; +mod load_with; + +pub fn generate_using_registry(dest_file: &Path) -> Result<(), Box<dyn Error>> +{ + let registry = Registry::retrieve()?; + + let removed_commands = registry + .api_interface_definitions() + .iter() + .flat_map(|api_interface_def| { + api_interface_def.removals().iter().flat_map(|removal| { + removal.features().iter().filter_map(|feature| { + if feature.kind() != FeatureKind::Command { + return None; + } + + Some(feature.name()) + }) + }) + }) + .collect::<Vec<_>>(); + + let filtered_commands = registry + .commands() + .iter() + .filter(|command| !removed_commands.contains(&command.prototype().name())) + .cloned() + .collect::<Vec<_>>(); + + let command_globals = create_command_globals(&filtered_commands); + let command_functions = create_command_functions(&filtered_commands)?; + let load_with_function = create_load_with_function(&filtered_commands); + + let tokens = quote! { + #(#command_functions)* + + #load_with_function + + mod functions + { + #(#command_globals)* + + extern "C" fn function_not_loaded() + { + panic!("OpenGL function not loaded"); + } + } + }; + + let mut file = File::create(dest_file)?; + + file.write_all(tokens.to_string().as_bytes())?; + + Ok(()) +} @@ -1,20 +1,9 @@ //! OpenGL bindings. #![deny(clippy::all, clippy::pedantic, missing_docs)] -use opengl_registry_macros::for_each_opengl_command; - -for_each_opengl_command! { - #[doc = stringify!(#gl_command_name)] - #[allow(non_snake_case, clippy::too_many_arguments)] - pub unsafe fn #gl_command_name_noprefix(#gl_command_args) -> #gl_command_ret_type { - type GLFunc = - unsafe extern "C" fn(#gl_command_arg_types) -> #gl_command_ret_type; - - let gl_func = std::mem::transmute::<_, GLFunc>(functions::#gl_command_name); +pub use ffi::*; - gl_func(#gl_command_arg_names) - } -} +include!(concat!(env!("OUT_DIR"), "/generated.rs")); mod ffi { @@ -29,33 +18,3 @@ mod ffi include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } - -pub use ffi::*; - -/// Loads OpenGL function pointers using the given function address retriever function. -pub fn load_with<GetProcAddrFn>(get_proc_addr: GetProcAddrFn) -where - GetProcAddrFn: Fn(&str) -> unsafe extern "C" fn(), -{ - for_each_opengl_command! { - unsafe { - functions::#gl_command_name = get_proc_addr(stringify!(#gl_command_name)); - } - }; -} - -mod functions -{ - use opengl_registry_macros::for_each_opengl_command; - - for_each_opengl_command!( - #[doc = stringify!(#gl_command_name)] - #[allow(non_upper_case_globals)] - pub static mut #gl_command_name: unsafe extern "C" fn() = function_not_loaded; - ); - - extern "C" fn function_not_loaded() - { - panic!("OpenGL function not loaded"); - } -} |