diff options
author | HampusM <hampus@hampusmat.com> | 2023-10-06 20:55:07 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2023-10-06 20:55:07 +0200 |
commit | f255db0f9252f4041b120dcaa00470889c4cb9f4 (patch) | |
tree | 94a56d2acc3cacdaebdae1dd9b37d533b9e7368f /glfw | |
parent | e40fd63dd35430f234d65806ca7e0d6bea364bfc (diff) |
feat: add GLFW wrapper library
Diffstat (limited to 'glfw')
-rw-r--r-- | glfw/Cargo.toml | 11 | ||||
-rw-r--r-- | glfw/build.rs | 28 | ||||
-rw-r--r-- | glfw/glfw.h | 2 | ||||
-rw-r--r-- | glfw/src/ffi.rs | 9 | ||||
-rw-r--r-- | glfw/src/init.rs | 44 | ||||
-rw-r--r-- | glfw/src/lib.rs | 50 | ||||
-rw-r--r-- | glfw/src/util.rs | 10 | ||||
-rw-r--r-- | glfw/src/window.rs | 128 |
8 files changed, 282 insertions, 0 deletions
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<dyn Error>> +{ + 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 <GLFW/glfw3.h> + 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<Glfw, Error> +{ + 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<Self, Error> + { + 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<unsafe extern "C" fn(), Error> + { + 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, +} |