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 --- 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 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 241 insertions(+) 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 (limited to 'glfw/src') 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