summaryrefslogtreecommitdiff
path: root/opengl-bindings/src
diff options
context:
space:
mode:
Diffstat (limited to 'opengl-bindings/src')
-rw-r--r--opengl-bindings/src/buffer.rs167
-rw-r--r--opengl-bindings/src/data_types.rs37
-rw-r--r--opengl-bindings/src/debug.rs161
-rw-r--r--opengl-bindings/src/lib.rs119
-rw-r--r--opengl-bindings/src/misc.rs190
-rw-r--r--opengl-bindings/src/shader.rs366
-rw-r--r--opengl-bindings/src/texture.rs236
-rw-r--r--opengl-bindings/src/vertex_array.rs260
8 files changed, 1536 insertions, 0 deletions
diff --git a/opengl-bindings/src/buffer.rs b/opengl-bindings/src/buffer.rs
new file mode 100644
index 0000000..c64ec8d
--- /dev/null
+++ b/opengl-bindings/src/buffer.rs
@@ -0,0 +1,167 @@
+use std::marker::PhantomData;
+use std::mem::size_of_val;
+use std::ptr::null;
+
+use safer_ffi::layout::ReprC;
+
+use crate::CurrentContextWithFns;
+
+#[derive(Debug)]
+pub struct Buffer<Item: ReprC>
+{
+ buf: crate::sys::types::GLuint,
+ _pd: PhantomData<Item>,
+}
+
+impl<Item: ReprC> Buffer<Item>
+{
+ #[must_use]
+ pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self
+ {
+ let mut buffer = crate::sys::types::GLuint::default();
+
+ unsafe {
+ current_context.fns().CreateBuffers(1, &raw mut buffer);
+ };
+
+ Self { buf: buffer, _pd: PhantomData }
+ }
+
+ /// Stores items in this buffer.
+ ///
+ /// # Errors
+ /// Returns `Err` if the total size (in bytes) is too large.
+ pub fn store(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ items: &[Item],
+ usage: Usage,
+ ) -> Result<(), Error>
+ {
+ let total_size = size_of_val(items);
+
+ let total_size: crate::sys::types::GLsizeiptr =
+ total_size
+ .try_into()
+ .map_err(|_| Error::TotalItemsSizeTooLarge {
+ total_size,
+ max_total_size: crate::sys::types::GLsizeiptr::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().NamedBufferData(
+ self.buf,
+ total_size,
+ items.as_ptr().cast(),
+ usage.into_gl(),
+ );
+ }
+
+ Ok(())
+ }
+
+ /// Maps the values in the `values` slice into `Item`s which is stored into this
+ /// buffer.
+ ///
+ /// # Errors
+ /// Returns `Err` if the total size (in bytes) is too large.
+ pub fn store_mapped<Value>(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ values: &[Value],
+ usage: Usage,
+ mut map_func: impl FnMut(&Value) -> Item,
+ ) -> Result<(), Error>
+ {
+ let item_size: crate::sys::types::GLsizeiptr = const {
+ assert!(size_of::<Item>() <= crate::sys::types::GLsizeiptr::MAX as usize);
+
+ size_of::<Item>().cast_signed()
+ };
+
+ let total_size = size_of::<Item>() * values.len();
+
+ let total_size: crate::sys::types::GLsizeiptr =
+ total_size
+ .try_into()
+ .map_err(|_| Error::TotalItemsSizeTooLarge {
+ total_size,
+ max_total_size: crate::sys::types::GLsizeiptr::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().NamedBufferData(
+ self.buf,
+ total_size,
+ null(),
+ usage.into_gl(),
+ );
+ }
+
+ for (index, value) in values.iter().enumerate() {
+ let item = map_func(value);
+
+ let offset = index * size_of::<Item>();
+
+ let Ok(offset_casted) = crate::sys::types::GLintptr::try_from(offset) else {
+ unreachable!(); // Reason: The total size can be casted to a GLintptr
+ // (done above) so offsets should be castable as well
+ };
+
+ unsafe {
+ current_context.fns().NamedBufferSubData(
+ self.buf,
+ offset_casted,
+ item_size,
+ (&raw const item).cast(),
+ );
+ }
+ }
+
+ Ok(())
+ }
+
+ pub(crate) fn object(&self) -> crate::sys::types::GLuint
+ {
+ self.buf
+ }
+}
+
+/// Buffer usage.
+#[derive(Debug)]
+pub enum Usage
+{
+ /// The buffer data is set only once and used by the GPU at most a few times.
+ Stream,
+
+ /// The buffer data is set only once and used many times.
+ Static,
+
+ /// The buffer data is changed a lot and used many times.
+ Dynamic,
+}
+
+impl Usage
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Stream => crate::sys::STREAM_DRAW,
+ Self::Static => crate::sys::STATIC_DRAW,
+ Self::Dynamic => crate::sys::DYNAMIC_DRAW,
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error(
+ "Total size of items ({total_size}) is too large. Must be < {max_total_size}"
+ )]
+ TotalItemsSizeTooLarge
+ {
+ total_size: usize,
+ max_total_size: usize,
+ },
+}
diff --git a/opengl-bindings/src/data_types.rs b/opengl-bindings/src/data_types.rs
new file mode 100644
index 0000000..7ead0ab
--- /dev/null
+++ b/opengl-bindings/src/data_types.rs
@@ -0,0 +1,37 @@
+use safer_ffi::derive_ReprC;
+use safer_ffi::layout::ReprC;
+
+#[derive(Debug, Clone)]
+#[derive_ReprC]
+#[repr(C)]
+pub struct Matrix<Value: ReprC, const ROWS: usize, const COLUMNS: usize>
+{
+ /// Items must be layed out this way for it to work with OpenGL shaders.
+ pub items: [[Value; ROWS]; COLUMNS],
+}
+
+#[derive(Debug, Clone)]
+#[derive_ReprC]
+#[repr(C)]
+pub struct Vec3<Value: ReprC>
+{
+ pub x: Value,
+ pub y: Value,
+ pub z: Value,
+}
+
+#[derive(Debug, Clone)]
+#[derive_ReprC]
+#[repr(C)]
+pub struct Vec2<Value>
+{
+ pub x: Value,
+ pub y: Value,
+}
+
+#[derive(Debug, Clone)]
+pub struct Dimens<Value>
+{
+ pub width: Value,
+ pub height: Value,
+}
diff --git a/opengl-bindings/src/debug.rs b/opengl-bindings/src/debug.rs
new file mode 100644
index 0000000..a9369a4
--- /dev/null
+++ b/opengl-bindings/src/debug.rs
@@ -0,0 +1,161 @@
+use std::ffi::c_void;
+use std::io::{stderr, Write};
+use std::mem::transmute;
+use std::panic::catch_unwind;
+
+use util_macros::FromRepr;
+
+use crate::CurrentContextWithFns;
+
+pub fn set_debug_message_callback(
+ current_context: &CurrentContextWithFns<'_>,
+ cb: MessageCallback,
+)
+{
+ unsafe {
+ current_context
+ .fns()
+ .DebugMessageCallback(Some(debug_message_cb), cb as *mut c_void);
+ }
+}
+
+/// Sets debug message parameters.
+///
+/// # Errors
+/// Returns `Err` if `ids` contains too many ids.
+pub fn set_debug_message_control(
+ current_context: &CurrentContextWithFns<'_>,
+ source: Option<MessageSource>,
+ ty: Option<MessageType>,
+ severity: Option<MessageSeverity>,
+ ids: &[u32],
+ ids_action: MessageIdsAction,
+) -> Result<(), SetDebugMessageControlError>
+{
+ let ids_len: crate::sys::types::GLsizei =
+ ids.len()
+ .try_into()
+ .map_err(|_| SetDebugMessageControlError::TooManyIds {
+ id_cnt: ids.len(),
+ max_id_cnt: crate::sys::types::GLsizei::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().DebugMessageControl(
+ source.map_or(crate::sys::DONT_CARE, |source| source as u32),
+ ty.map_or(crate::sys::DONT_CARE, |ty| ty as u32),
+ severity.map_or(crate::sys::DONT_CARE, |severity| severity as u32),
+ ids_len,
+ ids.as_ptr(),
+ ids_action as u8,
+ );
+ }
+
+ Ok(())
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum SetDebugMessageControlError
+{
+ #[error("Too many ids provided ({id_cnt}). Must be < {max_id_cnt}")]
+ TooManyIds
+ {
+ id_cnt: usize, max_id_cnt: usize
+ },
+}
+
+pub type MessageCallback = fn(
+ source: MessageSource,
+ ty: MessageType,
+ id: u32,
+ severity: MessageSeverity,
+ message: &str,
+);
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u8)] // GLboolean = u8
+pub enum MessageIdsAction
+{
+ Enable = crate::sys::TRUE,
+ Disable = crate::sys::FALSE,
+}
+
+#[derive(Debug, Clone, Copy, FromRepr)]
+#[repr(u32)] // GLenum = u32
+pub enum MessageSource
+{
+ Api = crate::sys::DEBUG_SOURCE_API,
+ WindowSystem = crate::sys::DEBUG_SOURCE_WINDOW_SYSTEM,
+ ShaderCompiler = crate::sys::DEBUG_SOURCE_SHADER_COMPILER,
+ ThirdParty = crate::sys::DEBUG_SOURCE_THIRD_PARTY,
+ Application = crate::sys::DEBUG_SOURCE_APPLICATION,
+ Other = crate::sys::DEBUG_SOURCE_OTHER,
+}
+
+#[derive(Debug, Clone, Copy, FromRepr)]
+#[repr(u32)] // GLenum = u32
+pub enum MessageType
+{
+ DeprecatedBehavior = crate::sys::DEBUG_TYPE_DEPRECATED_BEHAVIOR,
+ Error = crate::sys::DEBUG_TYPE_ERROR,
+ Marker = crate::sys::DEBUG_TYPE_MARKER,
+ Other = crate::sys::DEBUG_TYPE_OTHER,
+ Performance = crate::sys::DEBUG_TYPE_PERFORMANCE,
+ PopGroup = crate::sys::DEBUG_TYPE_POP_GROUP,
+ PushGroup = crate::sys::DEBUG_TYPE_PUSH_GROUP,
+ Portability = crate::sys::DEBUG_TYPE_PORTABILITY,
+ UndefinedBehavior = crate::sys::DEBUG_TYPE_UNDEFINED_BEHAVIOR,
+}
+
+#[derive(Debug, Clone, Copy, FromRepr)]
+#[repr(u32)] // GLenum = u32
+pub enum MessageSeverity
+{
+ High = crate::sys::DEBUG_SEVERITY_HIGH,
+ Medium = crate::sys::DEBUG_SEVERITY_MEDIUM,
+ Low = crate::sys::DEBUG_SEVERITY_LOW,
+ Notification = crate::sys::DEBUG_SEVERITY_NOTIFICATION,
+}
+
+extern "system" fn debug_message_cb(
+ source: crate::sys::types::GLenum,
+ ty: crate::sys::types::GLenum,
+ id: crate::sys::types::GLuint,
+ severity: crate::sys::types::GLenum,
+ message_length: crate::sys::types::GLsizei,
+ message: *const crate::sys::types::GLchar,
+ user_cb: *mut c_void,
+)
+{
+ let user_cb = unsafe { transmute::<*mut c_void, MessageCallback>(user_cb) };
+
+ let Ok(msg_length) = usize::try_from(message_length) else {
+ return;
+ };
+
+ // Unwinds are catched because unwinding from Rust code into foreign code is UB.
+ let res = catch_unwind(|| {
+ let msg_source = MessageSource::from_repr(source).unwrap();
+ let msg_type = MessageType::from_repr(ty).unwrap();
+ let msg_severity = MessageSeverity::from_repr(severity).unwrap();
+
+ // SAFETY: The received message should be a valid ASCII string
+ let message = unsafe {
+ std::str::from_utf8_unchecked(std::slice::from_raw_parts(
+ message.cast(),
+ msg_length,
+ ))
+ };
+
+ user_cb(msg_source, msg_type, id, msg_severity, message);
+ });
+
+ if res.is_err() {
+ // eprintln is not used since it can panic and unwinds are unwanted because
+ // unwinding from Rust code into foreign code is UB.
+ stderr()
+ .write_all(b"ERROR: Panic in debug message callback")
+ .ok();
+ println!();
+ }
+}
diff --git a/opengl-bindings/src/lib.rs b/opengl-bindings/src/lib.rs
new file mode 100644
index 0000000..7fd9933
--- /dev/null
+++ b/opengl-bindings/src/lib.rs
@@ -0,0 +1,119 @@
+#![deny(clippy::all, clippy::pedantic)]
+use std::ffi::CString;
+use std::process::abort;
+
+use glutin::context::{NotCurrentContext, PossiblyCurrentContext};
+use glutin::display::GetGlDisplay;
+use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext};
+use glutin::surface::{Surface, SurfaceTypeTrait};
+
+pub mod buffer;
+pub mod data_types;
+pub mod debug;
+pub mod misc;
+pub mod shader;
+pub mod texture;
+pub mod vertex_array;
+
+pub struct ContextWithFns
+{
+ context: PossiblyCurrentContext,
+ fns: Box<sys::Gl>,
+}
+
+impl ContextWithFns
+{
+ /// Returns a new `ContextWithFns`.
+ ///
+ /// # Errors
+ /// Returns `Err` if making this context current fails.
+ pub fn new<SurfaceType: SurfaceTypeTrait>(
+ context: NotCurrentContext,
+ surface: &Surface<SurfaceType>,
+ ) -> Result<Self, Error>
+ {
+ let context = context
+ .make_current(surface)
+ .map_err(Error::MakeContextCurrentFailed)?;
+
+ let display = context.display();
+
+ let gl = sys::Gl::load_with(|symbol| {
+ let Ok(symbol) = CString::new(symbol) else {
+ eprintln!("GL symbol contains nul byte");
+ abort();
+ };
+
+ display.get_proc_address(&symbol)
+ });
+
+ Ok(Self { context, fns: Box::new(gl) })
+ }
+
+ /// Attempts to make this context current.
+ ///
+ /// # Errors
+ /// Returns `Err` if making this context current fails.
+ pub fn make_current<SurfaceType: SurfaceTypeTrait>(
+ &self,
+ surface: &Surface<SurfaceType>,
+ ) -> Result<CurrentContextWithFns<'_>, Error>
+ {
+ if !self.context.is_current() {
+ self.context
+ .make_current(surface)
+ .map_err(Error::MakeContextCurrentFailed)?;
+ }
+
+ Ok(CurrentContextWithFns { ctx: self })
+ }
+
+ #[must_use]
+ pub fn context(&self) -> &PossiblyCurrentContext
+ {
+ &self.context
+ }
+
+ #[must_use]
+ pub fn context_mut(&mut self) -> &mut PossiblyCurrentContext
+ {
+ &mut self.context
+ }
+}
+
+pub struct CurrentContextWithFns<'ctx>
+{
+ ctx: &'ctx ContextWithFns,
+}
+
+impl CurrentContextWithFns<'_>
+{
+ #[inline]
+ pub(crate) fn fns(&self) -> &sys::Gl
+ {
+ &self.ctx.fns
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to make context current")]
+ MakeContextCurrentFailed(#[source] glutin::error::Error),
+}
+
+mod sys
+{
+ #![allow(
+ clippy::missing_safety_doc,
+ clippy::missing_transmute_annotations,
+ clippy::too_many_arguments,
+ clippy::unused_unit,
+ clippy::upper_case_acronyms,
+ clippy::doc_markdown,
+ clippy::unreadable_literal,
+ unsafe_op_in_unsafe_fn
+ )]
+
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
diff --git a/opengl-bindings/src/misc.rs b/opengl-bindings/src/misc.rs
new file mode 100644
index 0000000..bb54c1a
--- /dev/null
+++ b/opengl-bindings/src/misc.rs
@@ -0,0 +1,190 @@
+use bitflags::bitflags;
+
+use crate::data_types::{Dimens, Vec2};
+use crate::CurrentContextWithFns;
+
+/// Sets the viewport.
+///
+/// The `u32` values in `position` and `size` must fit in `i32`s.
+///
+/// # Errors
+/// Returns `Err` if any value in `position` or `size` does not fit into a `i32`.
+pub fn set_viewport(
+ current_context: &CurrentContextWithFns<'_>,
+ position: &Vec2<u32>,
+ size: &Dimens<u32>,
+) -> Result<(), SetViewportError>
+{
+ let position = Vec2::<crate::sys::types::GLint> {
+ x: position.x.try_into().map_err(|_| {
+ SetViewportError::PositionXValueTooLarge {
+ value: position.x,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ }
+ })?,
+ y: position.y.try_into().map_err(|_| {
+ SetViewportError::PositionYValueTooLarge {
+ value: position.y,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ }
+ })?,
+ };
+
+ let size = Dimens::<crate::sys::types::GLsizei> {
+ width: size.width.try_into().map_err(|_| {
+ SetViewportError::SizeWidthValueTooLarge {
+ value: size.width,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ }
+ })?,
+ height: size.height.try_into().map_err(|_| {
+ SetViewportError::SizeHeightValueTooLarge {
+ value: size.height,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ }
+ })?,
+ };
+
+ unsafe {
+ current_context
+ .fns()
+ .Viewport(position.x, position.y, size.width, size.height);
+ }
+
+ Ok(())
+}
+
+pub fn clear_buffers(current_context: &CurrentContextWithFns<'_>, mask: BufferClearMask)
+{
+ unsafe {
+ current_context.fns().Clear(mask.bits());
+ }
+}
+
+pub fn set_polygon_mode(
+ current_context: &CurrentContextWithFns<'_>,
+ face: impl Into<PolygonModeFace>,
+ mode: impl Into<PolygonMode>,
+)
+{
+ unsafe {
+ current_context
+ .fns()
+ .PolygonMode(face.into() as u32, mode.into() as u32);
+ }
+}
+
+pub fn enable(current_context: &CurrentContextWithFns<'_>, capacity: Capability)
+{
+ unsafe {
+ current_context.fns().Enable(capacity as u32);
+ }
+}
+
+pub fn disable(current_context: &CurrentContextWithFns<'_>, capability: Capability)
+{
+ unsafe {
+ current_context.fns().Disable(capability as u32);
+ }
+}
+
+pub fn set_enabled(
+ current_context: &CurrentContextWithFns<'_>,
+ capability: Capability,
+ enabled: bool,
+)
+{
+ if enabled {
+ enable(current_context, capability);
+ } else {
+ disable(current_context, capability);
+ }
+}
+
+#[must_use]
+pub fn get_context_flags(current_context: &CurrentContextWithFns<'_>) -> ContextFlags
+{
+ let mut context_flags = crate::sys::types::GLint::default();
+
+ unsafe {
+ current_context
+ .fns()
+ .GetIntegerv(crate::sys::CONTEXT_FLAGS, &raw mut context_flags);
+ }
+
+ ContextFlags::from_bits_truncate(context_flags.cast_unsigned())
+}
+
+bitflags! {
+ #[derive(Debug, Clone, Copy)]
+ pub struct BufferClearMask: u32 {
+ const COLOR = crate::sys::COLOR_BUFFER_BIT;
+ const DEPTH = crate::sys::DEPTH_BUFFER_BIT;
+ const STENCIL = crate::sys::STENCIL_BUFFER_BIT;
+ }
+}
+
+#[derive(Debug)]
+#[repr(u32)]
+pub enum Capability
+{
+ DepthTest = crate::sys::DEPTH_TEST,
+ MultiSample = crate::sys::MULTISAMPLE,
+ DebugOutput = crate::sys::DEBUG_OUTPUT,
+ DebugOutputSynchronous = crate::sys::DEBUG_OUTPUT_SYNCHRONOUS,
+}
+
+#[derive(Debug)]
+#[repr(u32)]
+pub enum PolygonMode
+{
+ Point = crate::sys::POINT,
+ Line = crate::sys::LINE,
+ Fill = crate::sys::FILL,
+}
+
+#[derive(Debug)]
+#[repr(u32)]
+pub enum PolygonModeFace
+{
+ Front = crate::sys::FRONT,
+ Back = crate::sys::BACK,
+ FrontAndBack = crate::sys::FRONT_AND_BACK,
+}
+
+bitflags! {
+#[derive(Debug, Clone, Copy)]
+pub struct ContextFlags: u32 {
+ const FORWARD_COMPATIBLE = crate::sys::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;
+ const DEBUG = crate::sys::CONTEXT_FLAG_DEBUG_BIT;
+ const ROBUST_ACCESS = crate::sys::CONTEXT_FLAG_ROBUST_ACCESS_BIT;
+}
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum SetViewportError
+{
+ #[error("Position X value ({value}) is too large. Must be < {max_value}")]
+ PositionXValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Position Y value ({value}) is too large. Must be < {max_value}")]
+ PositionYValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Size width value ({value}) is too large. Must be < {max_value}")]
+ SizeWidthValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Size height value ({value}) is too large. Must be < {max_value}")]
+ SizeHeightValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+}
diff --git a/opengl-bindings/src/shader.rs b/opengl-bindings/src/shader.rs
new file mode 100644
index 0000000..5ed66a2
--- /dev/null
+++ b/opengl-bindings/src/shader.rs
@@ -0,0 +1,366 @@
+use std::ffi::CStr;
+use std::ptr::null_mut;
+
+use safer_ffi::layout::ReprC;
+
+use crate::data_types::{Matrix, Vec3};
+use crate::CurrentContextWithFns;
+
+#[derive(Debug)]
+pub struct Shader
+{
+ shader: crate::sys::types::GLuint,
+}
+
+impl Shader
+{
+ #[must_use]
+ pub fn new(current_context: &CurrentContextWithFns<'_>, kind: Kind) -> Self
+ {
+ let shader = unsafe {
+ current_context
+ .fns()
+ .CreateShader(kind as crate::sys::types::GLenum)
+ };
+
+ Self { shader }
+ }
+
+ /// Sets the source code of this shader.
+ ///
+ /// # Errors
+ /// Returns `Err` if `source` is not ASCII.
+ pub fn set_source(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ source: &str,
+ ) -> Result<(), Error>
+ {
+ if !source.is_ascii() {
+ return Err(Error::SourceNotAscii);
+ }
+
+ let length: crate::sys::types::GLint =
+ source.len().try_into().map_err(|_| Error::SourceTooLarge {
+ length: source.len(),
+ max_length: crate::sys::types::GLint::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().ShaderSource(
+ self.shader,
+ 1,
+ &source.as_ptr().cast(),
+ &raw const length,
+ );
+ }
+
+ Ok(())
+ }
+
+ /// Compiles this shader.
+ ///
+ /// # Errors
+ /// Returns `Err` if compiling fails.
+ pub fn compile(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ ) -> Result<(), Error>
+ {
+ unsafe {
+ current_context.fns().CompileShader(self.shader);
+ }
+
+ let mut compile_success = crate::sys::types::GLint::default();
+
+ unsafe {
+ current_context.fns().GetShaderiv(
+ self.shader,
+ crate::sys::COMPILE_STATUS,
+ &raw mut compile_success,
+ );
+ }
+
+ if compile_success == 0 {
+ let info_log = self.get_info_log(current_context);
+
+ return Err(Error::CompileFailed { log: info_log });
+ }
+
+ Ok(())
+ }
+
+ pub fn delete(self, current_context: &CurrentContextWithFns<'_>)
+ {
+ unsafe {
+ current_context.fns().DeleteShader(self.shader);
+ }
+ }
+
+ fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String
+ {
+ const BUF_SIZE: crate::sys::types::GLsizei = 512;
+
+ let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize];
+
+ unsafe {
+ current_context.fns().GetShaderInfoLog(
+ self.shader,
+ BUF_SIZE,
+ null_mut(),
+ buf.as_mut_ptr(),
+ );
+ }
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
+ }
+}
+
+/// Shader kind.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[repr(u32)]
+pub enum Kind
+{
+ Vertex = crate::sys::VERTEX_SHADER,
+ Fragment = crate::sys::FRAGMENT_SHADER,
+}
+
+/// Shader program
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub struct Program
+{
+ program: crate::sys::types::GLuint,
+}
+
+impl Program
+{
+ #[must_use]
+ pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self
+ {
+ let program = unsafe { current_context.fns().CreateProgram() };
+
+ Self { program }
+ }
+
+ pub fn attach(&self, current_context: &CurrentContextWithFns<'_>, shader: &Shader)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .AttachShader(self.program, shader.shader);
+ }
+ }
+
+ /// Links this program.
+ ///
+ /// # Errors
+ /// Returns `Err` if linking fails.
+ pub fn link(&self, current_context: &CurrentContextWithFns<'_>) -> Result<(), Error>
+ {
+ unsafe {
+ current_context.fns().LinkProgram(self.program);
+ }
+
+ let mut link_success = crate::sys::types::GLint::default();
+
+ unsafe {
+ current_context.fns().GetProgramiv(
+ self.program,
+ crate::sys::LINK_STATUS,
+ &raw mut link_success,
+ );
+ }
+
+ if link_success == 0 {
+ let info_log = self.get_info_log(current_context);
+
+ return Err(Error::LinkFailed { log: info_log });
+ }
+
+ Ok(())
+ }
+
+ pub fn activate(&self, current_context: &CurrentContextWithFns<'_>)
+ {
+ unsafe {
+ current_context.fns().UseProgram(self.program);
+ }
+ }
+
+ pub fn set_uniform(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ name: &CStr,
+ var: &impl UniformVariable,
+ )
+ {
+ let location = UniformLocation(unsafe {
+ current_context
+ .fns()
+ .GetUniformLocation(self.program, name.as_ptr().cast())
+ });
+
+ var.set(current_context, self, location);
+ }
+
+ pub fn delete(self, current_context: &CurrentContextWithFns<'_>)
+ {
+ unsafe {
+ current_context.fns().DeleteProgram(self.program);
+ }
+ }
+
+ fn get_info_log(&self, current_context: &CurrentContextWithFns<'_>) -> String
+ {
+ const BUF_SIZE: crate::sys::types::GLsizei = 512;
+
+ let mut buf = vec![crate::sys::types::GLchar::default(); BUF_SIZE as usize];
+
+ unsafe {
+ current_context.fns().GetProgramInfoLog(
+ self.program,
+ BUF_SIZE,
+ null_mut(),
+ buf.as_mut_ptr(),
+ );
+ }
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
+ }
+}
+
+pub trait UniformVariable: ReprC + sealed::Sealed
+{
+ fn set(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ program: &Program,
+ uniform_location: UniformLocation,
+ );
+}
+
+impl UniformVariable for f32
+{
+ fn set(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ program: &Program,
+ uniform_location: UniformLocation,
+ )
+ {
+ unsafe {
+ current_context.fns().ProgramUniform1f(
+ program.program,
+ uniform_location.0,
+ *self,
+ );
+ }
+ }
+}
+
+impl sealed::Sealed for f32 {}
+
+impl UniformVariable for i32
+{
+ fn set(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ program: &Program,
+ uniform_location: UniformLocation,
+ )
+ {
+ unsafe {
+ current_context.fns().ProgramUniform1i(
+ program.program,
+ uniform_location.0,
+ *self,
+ );
+ }
+ }
+}
+
+impl sealed::Sealed for i32 {}
+
+impl UniformVariable for Vec3<f32>
+{
+ fn set(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ program: &Program,
+ uniform_location: UniformLocation,
+ )
+ {
+ unsafe {
+ current_context.fns().ProgramUniform3f(
+ program.program,
+ uniform_location.0,
+ self.x,
+ self.y,
+ self.z,
+ );
+ }
+ }
+}
+
+impl sealed::Sealed for Vec3<f32> {}
+
+impl UniformVariable for Matrix<f32, 4, 4>
+{
+ fn set(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ program: &Program,
+ uniform_location: UniformLocation,
+ )
+ {
+ unsafe {
+ current_context.fns().ProgramUniformMatrix4fv(
+ program.program,
+ uniform_location.0,
+ 1,
+ crate::sys::FALSE,
+ self.items.as_ptr().cast::<f32>(),
+ );
+ }
+ }
+}
+
+impl sealed::Sealed for Matrix<f32, 4, 4> {}
+
+#[derive(Debug)]
+pub struct UniformLocation(crate::sys::types::GLint);
+
+/// Shader error.
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("All characters in source are not within the ASCII range")]
+ SourceNotAscii,
+
+ #[error("Source is too large. Length ({length}) must be < {max_length}")]
+ SourceTooLarge
+ {
+ length: usize, max_length: usize
+ },
+
+ #[error("Failed to compile shader")]
+ CompileFailed
+ {
+ log: String
+ },
+
+ #[error("Failed to link shader program")]
+ LinkFailed
+ {
+ log: String
+ },
+}
+
+mod sealed
+{
+ pub trait Sealed {}
+}
diff --git a/opengl-bindings/src/texture.rs b/opengl-bindings/src/texture.rs
new file mode 100644
index 0000000..1859beb
--- /dev/null
+++ b/opengl-bindings/src/texture.rs
@@ -0,0 +1,236 @@
+use crate::data_types::Dimens;
+use crate::CurrentContextWithFns;
+
+#[derive(Debug)]
+pub struct Texture
+{
+ texture: crate::sys::types::GLuint,
+}
+
+impl Texture
+{
+ #[must_use]
+ pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self
+ {
+ let mut texture = crate::sys::types::GLuint::default();
+
+ unsafe {
+ current_context.fns().CreateTextures(
+ crate::sys::TEXTURE_2D,
+ 1,
+ &raw mut texture,
+ );
+ };
+
+ Self { texture }
+ }
+
+ pub fn bind_to_texture_unit(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ texture_unit: u32,
+ )
+ {
+ unsafe {
+ current_context
+ .fns()
+ .BindTextureUnit(texture_unit, self.texture);
+ }
+ }
+
+ /// Allocates the texture storage, stores pixel data & generates a mipmap.
+ ///
+ /// # Errors
+ /// Returns `Err` if any value in `size` does not fit into a `i32`.
+ pub fn generate(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ size: &Dimens<u32>,
+ data: &[u8],
+ pixel_data_format: PixelDataFormat,
+ ) -> Result<(), GenerateError>
+ {
+ let size = Dimens::<crate::sys::types::GLsizei> {
+ width: size.width.try_into().map_err(|_| {
+ GenerateError::SizeWidthValueTooLarge {
+ value: size.width,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ }
+ })?,
+ height: size.height.try_into().map_err(|_| {
+ GenerateError::SizeHeightValueTooLarge {
+ value: size.height,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ }
+ })?,
+ };
+
+ self.alloc_image(current_context, pixel_data_format, &size, data);
+
+ unsafe {
+ current_context.fns().GenerateTextureMipmap(self.texture);
+ }
+
+ Ok(())
+ }
+
+ pub fn set_wrap(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ wrapping: Wrapping,
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_WRAP_S,
+ wrapping as i32,
+ );
+
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_WRAP_T,
+ wrapping as i32,
+ );
+ }
+ }
+
+ pub fn set_magnifying_filter(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ filtering: Filtering,
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_MAG_FILTER,
+ filtering as i32,
+ );
+ }
+ }
+
+ pub fn set_minifying_filter(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ filtering: Filtering,
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_MIN_FILTER,
+ filtering as i32,
+ );
+ }
+ }
+
+ pub fn delete(self, current_context: &CurrentContextWithFns<'_>)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .DeleteTextures(1, &raw const self.texture);
+ }
+ }
+
+ fn alloc_image(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ pixel_data_format: PixelDataFormat,
+ size: &Dimens<crate::sys::types::GLsizei>,
+ data: &[u8],
+ )
+ {
+ unsafe {
+ current_context.fns().TextureStorage2D(
+ self.texture,
+ 1,
+ pixel_data_format.to_sized_internal_format(),
+ size.width,
+ size.height,
+ );
+
+ current_context.fns().TextureSubImage2D(
+ self.texture,
+ 0,
+ 0,
+ 0,
+ size.width,
+ size.height,
+ pixel_data_format.to_format(),
+ crate::sys::UNSIGNED_BYTE,
+ data.as_ptr().cast(),
+ );
+ }
+ }
+}
+
+const fn try_cast_u32_to_i32(val: u32) -> i32
+{
+ assert!(val <= i32::MAX as u32);
+
+ val.cast_signed()
+}
+
+/// Texture wrapping.
+#[derive(Debug, Clone, Copy)]
+#[repr(i32)]
+pub enum Wrapping
+{
+ Repeat = const { try_cast_u32_to_i32(crate::sys::REPEAT) },
+ MirroredRepeat = const { try_cast_u32_to_i32(crate::sys::MIRRORED_REPEAT) },
+ ClampToEdge = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_EDGE) },
+ ClampToBorder = const { try_cast_u32_to_i32(crate::sys::CLAMP_TO_BORDER) },
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(i32)]
+pub enum Filtering
+{
+ Nearest = const { try_cast_u32_to_i32(crate::sys::NEAREST) },
+ Linear = const { try_cast_u32_to_i32(crate::sys::LINEAR) },
+}
+
+/// Texture pixel data format.
+#[derive(Debug, Clone, Copy)]
+pub enum PixelDataFormat
+{
+ Rgb8,
+ Rgba8,
+}
+
+impl PixelDataFormat
+{
+ fn to_sized_internal_format(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Rgb8 => crate::sys::RGB8,
+ Self::Rgba8 => crate::sys::RGBA8,
+ }
+ }
+
+ fn to_format(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Rgb8 => crate::sys::RGB,
+ Self::Rgba8 => crate::sys::RGBA,
+ }
+ }
+}
+
+/// Error generating texture.
+#[derive(Debug, thiserror::Error)]
+pub enum GenerateError
+{
+ #[error("Size width value ({value}) is too large. Must be < {max_value}")]
+ SizeWidthValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+ #[error("Size height value ({value}) is too large. Must be < {max_value}")]
+ SizeHeightValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+}
diff --git a/opengl-bindings/src/vertex_array.rs b/opengl-bindings/src/vertex_array.rs
new file mode 100644
index 0000000..9942fe7
--- /dev/null
+++ b/opengl-bindings/src/vertex_array.rs
@@ -0,0 +1,260 @@
+use std::ffi::{c_int, c_void};
+use std::mem::size_of;
+
+use safer_ffi::layout::ReprC;
+
+use crate::buffer::Buffer;
+use crate::CurrentContextWithFns;
+
+#[derive(Debug)]
+pub struct VertexArray
+{
+ array: crate::sys::types::GLuint,
+}
+
+impl VertexArray
+{
+ #[must_use]
+ pub fn new(current_context: &CurrentContextWithFns<'_>) -> Self
+ {
+ let mut array = 0;
+
+ unsafe {
+ current_context.fns().CreateVertexArrays(1, &raw mut array);
+ }
+
+ Self { array }
+ }
+
+ /// Draws the currently bound vertex array.
+ ///
+ /// # Errors
+ /// Returns `Err` if:
+ /// - `start_index` is too large
+ /// - `cnt` is too large
+ pub fn draw_arrays(
+ current_context: &CurrentContextWithFns<'_>,
+ primitive_kind: PrimitiveKind,
+ start_index: u32,
+ cnt: u32,
+ ) -> Result<(), DrawError>
+ {
+ let start_index: crate::sys::types::GLint =
+ start_index
+ .try_into()
+ .map_err(|_| DrawError::StartIndexValueTooLarge {
+ value: start_index,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ })?;
+
+ let cnt: crate::sys::types::GLsizei =
+ cnt.try_into().map_err(|_| DrawError::CountValueTooLarge {
+ value: cnt,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?;
+
+ unsafe {
+ current_context
+ .fns()
+ .DrawArrays(primitive_kind.into_gl(), start_index, cnt);
+ }
+
+ Ok(())
+ }
+
+ /// Draws the currently bound vertex array.
+ ///
+ /// # Errors
+ /// Returns `Err` if `cnt` is too large.
+ pub fn draw_elements(
+ current_context: &CurrentContextWithFns<'_>,
+ primitive_kind: PrimitiveKind,
+ start_index: u32,
+ cnt: u32,
+ ) -> Result<(), DrawError>
+ {
+ let cnt: crate::sys::types::GLsizei =
+ cnt.try_into().map_err(|_| DrawError::CountValueTooLarge {
+ value: cnt,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?;
+
+ unsafe {
+ current_context.fns().DrawElements(
+ primitive_kind.into_gl(),
+ cnt,
+ crate::sys::UNSIGNED_INT,
+ // TODO: Make this not sometimes UB. DrawElements expects a actual
+ // pointer to a memory location when no VBO is bound.
+ // See: https://stackoverflow.com/q/21706113
+ std::ptr::without_provenance::<c_void>(start_index as usize),
+ );
+ }
+
+ Ok(())
+ }
+
+ pub fn bind_element_buffer(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ element_buffer: &Buffer<u32>,
+ )
+ {
+ unsafe {
+ current_context
+ .fns()
+ .VertexArrayElementBuffer(self.array, element_buffer.object());
+ }
+ }
+
+ pub fn bind_vertex_buffer<VertexT: ReprC>(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ binding_index: u32,
+ vertex_buffer: &Buffer<VertexT>,
+ offset: isize,
+ )
+ {
+ let vertex_size = const { cast_usize_to_c_int(size_of::<VertexT>()) };
+
+ unsafe {
+ current_context.fns().VertexArrayVertexBuffer(
+ self.array,
+ binding_index,
+ vertex_buffer.object(),
+ offset,
+ vertex_size,
+ );
+ }
+ }
+
+ pub fn enable_attrib(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ attrib_index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().EnableVertexArrayAttrib(
+ self.array,
+ attrib_index as crate::sys::types::GLuint,
+ );
+ }
+ }
+
+ pub fn set_attrib_format(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ attrib_index: u32,
+ data_type: DataType,
+ normalized: bool,
+ offset: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().VertexArrayAttribFormat(
+ self.array,
+ attrib_index,
+ data_type.size(),
+ data_type as u32,
+ if normalized {
+ crate::sys::TRUE
+ } else {
+ crate::sys::FALSE
+ },
+ offset,
+ );
+ }
+ }
+
+ /// Associate a vertex attribute and a vertex buffer binding.
+ pub fn set_attrib_vertex_buf_binding(
+ &self,
+ current_context: &CurrentContextWithFns<'_>,
+ attrib_index: u32,
+ vertex_buf_binding_index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().VertexArrayAttribBinding(
+ self.array,
+ attrib_index,
+ vertex_buf_binding_index,
+ );
+ }
+ }
+
+ pub fn bind(&self, current_context: &CurrentContextWithFns<'_>)
+ {
+ unsafe { current_context.fns().BindVertexArray(self.array) }
+ }
+}
+
+#[derive(Debug)]
+pub enum PrimitiveKind
+{
+ Triangles,
+}
+
+impl PrimitiveKind
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Triangles => crate::sys::TRIANGLES,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
+pub enum DataType
+{
+ Float = crate::sys::FLOAT,
+}
+
+impl DataType
+{
+ fn size(self) -> crate::sys::types::GLint
+ {
+ match self {
+ Self::Float => const { cast_usize_to_c_int(size_of::<f32>()) },
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum DrawError
+{
+ #[error("Start index value {value} is too large. Must be < {max_value}")]
+ StartIndexValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Count value {value} is too large. Must be < {max_value}")]
+ CountValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+}
+
+const fn cast_usize_to_c_int(num: usize) -> c_int
+{
+ assert!(num <= c_int::MAX.cast_unsigned() as usize);
+
+ c_int::from_ne_bytes(shorten_byte_array(num.to_ne_bytes()))
+}
+
+const fn shorten_byte_array<const SRC_LEN: usize, const DST_LEN: usize>(
+ src: [u8; SRC_LEN],
+) -> [u8; DST_LEN]
+{
+ assert!(DST_LEN < SRC_LEN);
+
+ let mut ret = [0; DST_LEN];
+
+ ret.copy_from_slice(src.split_at(DST_LEN).0);
+
+ ret
+}