From f255db0f9252f4041b120dcaa00470889c4cb9f4 Mon Sep 17 00:00:00 2001 From: HampusM Date: Fri, 6 Oct 2023 20:55:07 +0200 Subject: feat: add GLFW wrapper library --- Cargo.lock | 417 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + glfw/Cargo.toml | 11 ++ glfw/build.rs | 28 ++++ glfw/glfw.h | 2 + glfw/src/ffi.rs | 9 ++ glfw/src/init.rs | 44 ++++++ glfw/src/lib.rs | 50 +++++++ glfw/src/util.rs | 10 ++ glfw/src/window.rs | 128 ++++++++++++++++ 10 files changed, 702 insertions(+) create mode 100644 glfw/Cargo.toml create mode 100644 glfw/build.rs create mode 100644 glfw/glfw.h create mode 100644 glfw/src/ffi.rs create mode 100644 glfw/src/init.rs create mode 100644 glfw/src/lib.rs create mode 100644 glfw/src/util.rs create mode 100644 glfw/src/window.rs diff --git a/Cargo.lock b/Cargo.lock index 01430c1..dc88aa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,423 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "game-newest" version = "0.1.0" + +[[package]] +name = "glfw" +version = "0.1.0" +dependencies = [ + "bindgen", + "libc", + "thiserror", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index 17c9101..96fc084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,7 @@ name = "game-newest" version = "0.1.0" edition = "2021" +[workspace] +members = ["glfw"] + [dependencies] diff --git a/glfw/Cargo.toml b/glfw/Cargo.toml new file mode 100644 index 0000000..9f7ecde --- /dev/null +++ b/glfw/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "glfw" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2.148" +thiserror = "1.0.49" + +[build-dependencies] +bindgen = "0.68.1" diff --git a/glfw/build.rs b/glfw/build.rs new file mode 100644 index 0000000..aaf4446 --- /dev/null +++ b/glfw/build.rs @@ -0,0 +1,28 @@ +use std::env; +use std::error::Error; +use std::path::PathBuf; + +use bindgen::MacroTypeVariation; + +fn main() -> Result<(), Box> +{ + println!("cargo:rustc-link-lib=glfw"); + + println!("cargo:rerun-if-changed=glfw.h"); + + let bindings = bindgen::Builder::default() + .header("glfw.h") + .clang_arg("-fretain-comments-from-system-headers") + .generate_comments(true) + .allowlist_function("glfw.*") + .allowlist_type("GLFW.*") + .allowlist_var("GLFW.*") + .default_macro_constant_type(MacroTypeVariation::Signed) + .generate()?; + + let out_path = PathBuf::from(env::var("OUT_DIR")?); + + bindings.write_to_file(out_path.join("bindings.rs"))?; + + Ok(()) +} diff --git a/glfw/glfw.h b/glfw/glfw.h new file mode 100644 index 0000000..f5fa479 --- /dev/null +++ b/glfw/glfw.h @@ -0,0 +1,2 @@ +#include + diff --git a/glfw/src/ffi.rs b/glfw/src/ffi.rs new file mode 100644 index 0000000..d0affd0 --- /dev/null +++ b/glfw/src/ffi.rs @@ -0,0 +1,9 @@ +#![allow( + non_snake_case, + non_camel_case_types, + non_upper_case_globals, + unused, + clippy::unreadable_literal +)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/glfw/src/init.rs b/glfw/src/init.rs new file mode 100644 index 0000000..d102db6 --- /dev/null +++ b/glfw/src/init.rs @@ -0,0 +1,44 @@ +use crate::util::is_main_thread; +use crate::{get_glfw_error, Error}; + +/// Initializes GLFW and returns a initialization token. +/// +/// # Errors +/// Will return `Err` if +/// - The current thread is not the main thread +/// - A GLFW error occurs +pub fn initialize() -> Result +{ + if !is_main_thread() { + return Err(Error::NotInMainThread); + } + + // SAFETY: The current thread is the main thread + let success = unsafe { crate::ffi::glfwInit() }; + + if success == crate::ffi::GLFW_FALSE { + get_glfw_error()?; + } + + Ok(Glfw { _priv: &() }) +} + +/// GLFW initialization token. +pub struct Glfw +{ + /// This field has two purposes + /// - To make the struct not constructable without calling [`initialize`]. + /// - To make the struct `!Send` and `!Sync`. + _priv: *const (), +} + +impl Drop for Glfw +{ + fn drop(&mut self) + { + // SAFETY: The current thread cannot be any other thread than the main thread + // since the initialize function checks it and the GLFW initialization token is + // neither Send or Sync + unsafe { crate::ffi::glfwTerminate() }; + } +} diff --git a/glfw/src/lib.rs b/glfw/src/lib.rs new file mode 100644 index 0000000..889015b --- /dev/null +++ b/glfw/src/lib.rs @@ -0,0 +1,50 @@ +#![deny(clippy::all, clippy::pedantic)] + +use std::ffi::{c_char, CStr}; +use std::ptr::null; + +mod ffi; +mod init; +mod util; + +pub mod window; + +pub use window::{Size as WindowSize, Window}; + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + /// The current thread is not the main thread. + #[error("The current thread is not the main thread")] + NotInMainThread, + + /// An internal nul byte was found in a window title. + #[error("An internal nul byte was found in the window title")] + InternalNulByteInWindowTitle, + + /// GLFW error. + #[error("GLFW error {0} occured. {1}")] + GlfwError(i32, String), +} + +fn get_glfw_error() -> Result<(), Error> +{ + let mut description_ptr: *const c_char = null(); + + let err = unsafe { crate::ffi::glfwGetError(&mut description_ptr) }; + + if err == crate::ffi::GLFW_NO_ERROR { + return Ok(()); + } + + // SAFETY: The description is guaranteed by GLFW to be valid UTF-8 + let desc_str = unsafe { + std::str::from_utf8_unchecked(CStr::from_ptr(description_ptr).to_bytes()) + }; + + // The description has to be copied because it is guaranteed to be valid only + // until the next GLFW error occurs or the GLFW library is terminated. + let description = desc_str.to_string(); + + Err(Error::GlfwError(err, description)) +} diff --git a/glfw/src/util.rs b/glfw/src/util.rs new file mode 100644 index 0000000..f77aaf8 --- /dev/null +++ b/glfw/src/util.rs @@ -0,0 +1,10 @@ +use libc::{c_long, getpid, syscall, SYS_gettid}; + +pub fn is_main_thread() -> bool +{ + let ttid = unsafe { syscall(SYS_gettid) }; + + let pid = c_long::from(unsafe { getpid() }); + + ttid == pid +} diff --git a/glfw/src/window.rs b/glfw/src/window.rs new file mode 100644 index 0000000..6f5c845 --- /dev/null +++ b/glfw/src/window.rs @@ -0,0 +1,128 @@ +use std::ffi::{CStr, CString}; +use std::ptr::null_mut; + +use crate::init::{initialize, Glfw}; +use crate::{get_glfw_error, Error}; + +pub struct Window +{ + _init: Glfw, + handle: *mut crate::ffi::GLFWwindow, +} + +impl Window +{ + /// Creates a new window. + /// + /// # Errors + /// Will return `Err` if + /// - The title contains an internal nul byte + /// - A GLFW error occurs + pub fn create(size: &Size, title: &str) -> Result + { + let init = initialize()?; + + let c_title = + CString::new(title).map_err(|_| Error::InternalNulByteInWindowTitle)?; + + // SAFETY: The initialize function makes sure the current thread is the main + // thread + let handle = unsafe { + #[allow(clippy::cast_possible_wrap)] + crate::ffi::glfwCreateWindow( + size.width as i32, + size.height as i32, + c_title.as_ptr(), + null_mut(), + null_mut(), + ) + }; + + get_glfw_error()?; + + Ok(Self { + _init: init, + handle, + }) + } + + /// Makes the context of the window current for the calling thread. + /// + /// # Errors + /// Will return `Err` if a GLFW platform error occurs or if no OpenGL context is + /// present. + pub fn make_context_current(&self) -> Result<(), Error> + { + unsafe { crate::ffi::glfwMakeContextCurrent(self.handle) }; + + get_glfw_error()?; + + Ok(()) + } + + /// Returns the address of the specified OpenGL function, if it is supported by the + /// current context. + /// + /// # Errors + /// Will return `Err` if a GLFW platform error occurs or if no current context has + /// been set. + pub fn get_proc_address( + &self, + proc_name: &CStr, + ) -> Result + { + let proc_addr = unsafe { crate::ffi::glfwGetProcAddress(proc_name.as_ptr()) }; + + get_glfw_error()?; + + // SAFETY: Is only None when a error has occured and that case is handled above + Ok(unsafe { proc_addr.unwrap_unchecked() }) + } + + /// Processes all pending events. + /// + /// # Errors + /// Will return `Err` if a GLFW platform error occurs. + pub fn poll_events(&self) -> Result<(), Error> + { + // SAFETY: The initialize function (called when the window is created) makes sure + // the current thread is the main thread + unsafe { crate::ffi::glfwPollEvents() }; + + get_glfw_error()?; + + Ok(()) + } + + /// Swaps the front and back buffers of the window. + /// + /// # Errors + /// Will return `Err` if a GLFW platform error occurs or if no OpenGL window context + /// is present. + pub fn swap_buffers(&self) -> Result<(), Error> + { + unsafe { + crate::ffi::glfwSwapBuffers(self.handle); + }; + + get_glfw_error()?; + + Ok(()) + } + + /// Returns whether or not the window should close. + #[must_use] + pub fn should_close(&self) -> bool + { + let should_close = unsafe { crate::ffi::glfwWindowShouldClose(self.handle) }; + + should_close == crate::ffi::GLFW_TRUE + } +} + +/// Window size. +pub struct Size +{ + pub width: u32, + pub height: u32, +} -- cgit v1.2.3-18-g5258