summaryrefslogtreecommitdiff
path: root/opengl-bindings
diff options
context:
space:
mode:
Diffstat (limited to 'opengl-bindings')
-rw-r--r--opengl-bindings/Cargo.toml84
-rw-r--r--opengl-bindings/build.rs107
-rw-r--r--opengl-bindings/src/blending.rs181
-rw-r--r--opengl-bindings/src/buffer.rs290
-rw-r--r--opengl-bindings/src/data_types.rs47
-rw-r--r--opengl-bindings/src/debug.rs163
-rw-r--r--opengl-bindings/src/framebuffer.rs212
-rw-r--r--opengl-bindings/src/lib.rs149
-rw-r--r--opengl-bindings/src/misc.rs319
-rw-r--r--opengl-bindings/src/shader.rs412
-rw-r--r--opengl-bindings/src/texture.rs703
-rw-r--r--opengl-bindings/src/vertex_array.rs354
12 files changed, 3021 insertions, 0 deletions
diff --git a/opengl-bindings/Cargo.toml b/opengl-bindings/Cargo.toml
new file mode 100644
index 0000000..7670912
--- /dev/null
+++ b/opengl-bindings/Cargo.toml
@@ -0,0 +1,84 @@
+[package]
+name = "opengl-bindings"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+glutin = "0.32.3"
+thiserror = "1.0.49"
+safer-ffi = "0.1.13"
+bitflags = "2.4.0"
+tracing = "0.1.39"
+util-macros = { workspace = true }
+
+[build-dependencies]
+gl_generator = "=0.14.0"
+toml = "0.8.12"
+anyhow = "1.0.100"
+
+[package.metadata.build]
+gl_commands = [
+ "CreateBuffers",
+ "NamedBufferData",
+ "NamedBufferSubData",
+ "CreateVertexArrays",
+ "DrawArrays",
+ "DrawElementsBaseVertex",
+ "VertexArrayElementBuffer",
+ "VertexArrayVertexBuffer",
+ "EnableVertexArrayAttrib",
+ "DisableVertexArrayAttrib",
+ "VertexArrayAttribFormat",
+ "VertexArrayAttribBinding",
+ "BindVertexArray",
+ "TextureStorage2D",
+ "TextureSubImage2D",
+ "TextureSubImage3D",
+ "DeleteTextures",
+ "GenerateTextureMipmap",
+ "TextureParameteri",
+ "TextureParameterfv",
+ "CreateTextures",
+ "BindTextureUnit",
+ "DeleteShader",
+ "CreateShader",
+ "ShaderSource",
+ "CompileShader",
+ "GetShaderiv",
+ "GetShaderInfoLog",
+ "LinkProgram",
+ "GetProgramiv",
+ "CreateProgram",
+ "AttachShader",
+ "UseProgram",
+ "GetUniformLocation",
+ "ProgramUniform1f",
+ "ProgramUniform1i",
+ "ProgramUniform3f",
+ "ProgramUniformMatrix4fv",
+ "GetProgramInfoLog",
+ "DeleteProgram",
+ "Viewport",
+ "Clear",
+ "PolygonMode",
+ "Enable",
+ "Disable",
+ "GetIntegerv",
+ "DebugMessageCallback",
+ "DebugMessageControl",
+ "DeleteVertexArrays",
+ "DeleteBuffers",
+ "BindBufferBase",
+ "BlendFunc",
+ "BlendEquation",
+ "Scissor",
+ "DepthFunc",
+ "PixelStorei",
+ "CreateFramebuffers",
+ "DeleteFramebuffers",
+ "NamedFramebufferTexture",
+ "BindFramebuffer",
+ "NamedFramebufferDrawBuffer",
+ "NamedFramebufferReadBuffer",
+ "CullFace"
+]
diff --git a/opengl-bindings/build.rs b/opengl-bindings/build.rs
new file mode 100644
index 0000000..060472c
--- /dev/null
+++ b/opengl-bindings/build.rs
@@ -0,0 +1,107 @@
+use std::collections::HashSet;
+use std::env;
+use std::fs::File;
+use std::path::{Path, PathBuf};
+
+use anyhow::anyhow;
+use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator};
+
+fn main() -> Result<(), anyhow::Error>
+{
+ println!("cargo::rerun-if-changed=build.rs");
+ println!("cargo::rerun-if-changed=Cargo.toml");
+
+ let dest = env::var("OUT_DIR")?;
+
+ let mut file = File::create(Path::new(&dest).join("bindings.rs"))?;
+
+ let mut registry = Registry::new(Api::Gl, (4, 6), Profile::Core, Fallbacks::All, []);
+
+ let mut build_metadata = get_build_metadata()?;
+
+ filter_gl_commands(&mut registry, &mut build_metadata)?;
+
+ registry.write_bindings(StructGenerator, &mut file)?;
+
+ Ok(())
+}
+
+fn filter_gl_commands(
+ registry: &mut Registry,
+ build_metadata: &mut BuildMetadata,
+) -> Result<(), anyhow::Error>
+{
+ registry
+ .cmds
+ .retain(|command| build_metadata.gl_commands.remove(&command.proto.ident));
+
+ if !build_metadata.gl_commands.is_empty() {
+ return Err(anyhow!(
+ "Invalid GL commands: [{}]",
+ build_metadata
+ .gl_commands
+ .iter()
+ .cloned()
+ .collect::<Vec<_>>()
+ .join(", ")
+ ));
+ }
+
+ Ok(())
+}
+
+fn get_build_metadata() -> Result<BuildMetadata, anyhow::Error>
+{
+ let manifest_path = PathBuf::from(std::env::var("CARGO_MANIFEST_PATH")?);
+
+ let manifest = std::fs::read_to_string(manifest_path)?.parse::<toml::Table>()?;
+
+ let package = match manifest
+ .get("package")
+ .ok_or_else(|| anyhow!("Manifest does not have a package table"))?
+ {
+ toml::Value::Table(package) => Ok(package),
+ _ => Err(anyhow!("Manifest package must be a table")),
+ }?;
+
+ let metadata = match package
+ .get("metadata")
+ .ok_or_else(|| anyhow!("Manifest does not have a package.metadata table"))?
+ {
+ toml::Value::Table(metadata) => Ok(metadata),
+ _ => Err(anyhow!("Manifest package.metadata must be a table")),
+ }?;
+
+ let build_metadata = match metadata
+ .get("build")
+ .ok_or_else(|| anyhow!("Manifest does not have a package.metadata.build table"))?
+ {
+ toml::Value::Table(build_metadata) => Ok(build_metadata),
+ _ => Err(anyhow!("Manifest package.metadata.build must be a table")),
+ }?;
+
+ let gl_command_values = match build_metadata.get("gl_commands").ok_or_else(|| {
+ anyhow!("Manifest does not have a package.metadata.build.gl_commands array")
+ })? {
+ toml::Value::Array(gl_commands) => Ok(gl_commands),
+ _ => Err(anyhow!(
+ "Manifest package.metadata.build.gl_commands must be a array"
+ )),
+ }?;
+
+ let gl_commands = gl_command_values
+ .iter()
+ .map(|gl_command_val| match gl_command_val {
+ toml::Value::String(gl_command) => Ok(gl_command.clone()),
+ _ => Err(anyhow!("GL command must be a string")),
+ })
+ .collect::<Result<HashSet<_>, _>>()?;
+
+ Ok(BuildMetadata { gl_commands })
+}
+
+#[derive(Debug)]
+struct BuildMetadata
+{
+ gl_commands: HashSet<String>,
+}
diff --git a/opengl-bindings/src/blending.rs b/opengl-bindings/src/blending.rs
new file mode 100644
index 0000000..e4229ed
--- /dev/null
+++ b/opengl-bindings/src/blending.rs
@@ -0,0 +1,181 @@
+use crate::MaybeCurrentContextWithFns;
+
+#[tracing::instrument(skip(current_context))]
+pub fn configure(
+ current_context: &MaybeCurrentContextWithFns,
+ configuration: Configuration,
+)
+{
+ unsafe {
+ current_context.fns().BlendFunc(
+ configuration.source_factor.into_gl(),
+ configuration.destination_factor.into_gl(),
+ );
+
+ current_context
+ .fns()
+ .BlendEquation(configuration.equation.into_gl());
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Configuration
+{
+ source_factor: Factor,
+ destination_factor: Factor,
+ equation: Equation,
+}
+
+impl Configuration
+{
+ pub fn with_source_factor(mut self, source_factor: Factor) -> Self
+ {
+ self.source_factor = source_factor;
+ self
+ }
+
+ pub fn with_destination_factor(mut self, destination_factor: Factor) -> Self
+ {
+ self.destination_factor = destination_factor;
+ self
+ }
+
+ pub fn with_equation(mut self, equation: Equation) -> Self
+ {
+ self.equation = equation;
+ self
+ }
+}
+
+impl Default for Configuration
+{
+ fn default() -> Self
+ {
+ Self {
+ source_factor: Factor::One,
+ destination_factor: Factor::Zero,
+ equation: Equation::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+pub enum Factor
+{
+ /// Factor will be the RGBA color `(0,0,0,0)`
+ Zero,
+
+ /// Factor will be the RGBA color `(1,1,1,1)`
+ One,
+
+ /// Factor will be the source color
+ SrcColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - source color`
+ OneMinusSrcColor,
+
+ /// Factor will be the destination color
+ DstColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - destination color`
+ OneMinusDstColor,
+
+ /// Factor will be the alpha component of the source color.
+ SrcAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - source color alpha`
+ OneMinusSrcAlpha,
+
+ /// Factor will be the alpha component of the destination color.
+ DstAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - destination color alpha`
+ OneMinusDstAlpha,
+
+ /// Factor will be the constant color
+ ConstantColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - constant color`
+ OneMinusConstantColor,
+
+ /// Factor will be the alpha component of the constant color.
+ ConstantAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - constant color alpha`
+ OneMinusConstantAlpha,
+ // /// (i,i,i,1)
+ // SrcAlphaSaturate,
+ //
+ // /// (Rs1kR,Gs1kG,Bs1kB,As1kA)
+ // Src1Color,
+ //
+ // /// (1,1,1,1)−(Rs1kR,Gs1kG,Bs1kB,As1kA)
+ // OneMinusSrc1Color,
+ //
+ // /// (As1kA,As1kA,As1kA,As1kA)
+ // Src1Alpha,
+ //
+ // /// (1,1,1,1)−(As1kA,As1kA,As1kA,As1kA)
+ // OneMinusSrc1Alpha,
+}
+
+impl Factor
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Zero => crate::sys::ZERO,
+ Self::One => crate::sys::ONE,
+ Self::SrcColor => crate::sys::SRC_COLOR,
+ Self::OneMinusSrcColor => crate::sys::ONE_MINUS_SRC_COLOR,
+ Self::DstColor => crate::sys::DST_COLOR,
+ Self::OneMinusDstColor => crate::sys::ONE_MINUS_DST_COLOR,
+ Self::SrcAlpha => crate::sys::SRC_ALPHA,
+ Self::OneMinusSrcAlpha => crate::sys::ONE_MINUS_SRC_ALPHA,
+ Self::DstAlpha => crate::sys::DST_ALPHA,
+ Self::OneMinusDstAlpha => crate::sys::ONE_MINUS_DST_ALPHA,
+ Self::ConstantColor => crate::sys::CONSTANT_COLOR,
+ Self::OneMinusConstantColor => crate::sys::ONE_MINUS_CONSTANT_COLOR,
+ Self::ConstantAlpha => crate::sys::CONSTANT_ALPHA,
+ Self::OneMinusConstantAlpha => crate::sys::ONE_MINUS_CONSTANT_ALPHA,
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub enum Equation
+{
+ /// The destination color and source color is added to each other in the blend
+ /// function
+ #[default]
+ Add,
+
+ /// The destination color is subtracted from the source color in the blend function
+ Subtract,
+
+ /// The source color is subtracted from the destination color in the blend function
+ ReverseSubtract,
+
+ /// The blend function will take the component-wise minimum of the destination color
+ /// and the source color
+ Min,
+
+ /// The blend function will take the component-wise maximum of the destination color
+ /// and the source color
+ Max,
+}
+
+impl Equation
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Add => crate::sys::FUNC_ADD,
+ Self::Subtract => crate::sys::FUNC_SUBTRACT,
+ Self::ReverseSubtract => crate::sys::FUNC_REVERSE_SUBTRACT,
+ Self::Min => crate::sys::MIN,
+ Self::Max => crate::sys::MAX,
+ }
+ }
+}
diff --git a/opengl-bindings/src/buffer.rs b/opengl-bindings/src/buffer.rs
new file mode 100644
index 0000000..4d0fcba
--- /dev/null
+++ b/opengl-bindings/src/buffer.rs
@@ -0,0 +1,290 @@
+use std::any::type_name;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::mem::size_of_val;
+use std::ptr::null;
+
+use safer_ffi::layout::ReprC;
+
+use crate::MaybeCurrentContextWithFns;
+
+#[derive(Clone)]
+pub struct Buffer<Item: ReprC>
+{
+ buf: crate::sys::types::GLuint,
+ _pd: PhantomData<Item>,
+}
+
+impl<Item: ReprC> Buffer<Item>
+{
+ #[tracing::instrument(skip(current_context))]
+ #[must_use]
+ pub fn new(current_context: &MaybeCurrentContextWithFns) -> Self
+ {
+ let mut buffer = crate::sys::types::GLuint::default();
+
+ unsafe {
+ current_context.fns().CreateBuffers(1, &raw mut buffer);
+ };
+
+ Self { buf: buffer, _pd: PhantomData }
+ }
+
+ /// Initializes this buffer with a size and a usage.
+ ///
+ /// # Errors
+ /// Returns `Err` if the size (in bytes) is too large.
+ #[tracing::instrument(skip(current_context))]
+ pub fn init(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ size: usize,
+ usage: Usage,
+ ) -> Result<(), Error>
+ {
+ let size: crate::sys::types::GLsizeiptr =
+ size.try_into().map_err(|_| Error::SizeTooLarge {
+ size,
+ max_size: crate::sys::types::GLsizeiptr::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().NamedBufferData(
+ self.buf,
+ size,
+ null(),
+ usage.into_gl(),
+ );
+ }
+
+ Ok(())
+ }
+
+ /// Stores items in this buffer.
+ ///
+ /// # Errors
+ /// Returns `Err` if the total size (in bytes) is too large.
+ #[tracing::instrument(skip(current_context, items))]
+ pub fn store(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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::SizeTooLarge {
+ size: total_size,
+ max_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(())
+ }
+
+ /// Stores items in this buffer, starting at a byte offset.
+ ///
+ /// # Errors
+ /// Returns `Err` if the total size (in bytes) is too large.
+ #[tracing::instrument(skip(current_context, items))]
+ pub fn store_at_byte_offset(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ byte_offset: usize,
+ items: &[Item],
+ ) -> Result<(), Error>
+ {
+ let total_size = size_of_val(items);
+
+ let byte_offset: crate::sys::types::GLintptr =
+ byte_offset
+ .try_into()
+ .map_err(|_| Error::ByteOffsetTooLarge {
+ byte_offset,
+ max_byte_offset: crate::sys::types::GLintptr::MAX as usize,
+ })?;
+
+ let total_size: crate::sys::types::GLsizeiptr =
+ total_size.try_into().map_err(|_| Error::SizeTooLarge {
+ size: total_size,
+ max_size: crate::sys::types::GLsizeiptr::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().NamedBufferSubData(
+ self.buf,
+ byte_offset,
+ total_size,
+ items.as_ptr().cast(),
+ );
+ }
+
+ 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.
+ #[tracing::instrument(skip(current_context, values, map_func))]
+ pub fn store_mapped<Value>(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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::SizeTooLarge {
+ size: total_size,
+ max_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(())
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().DeleteBuffers(1, &raw const self.buf);
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn bind_to_indexed_target(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ target: BindingTarget,
+ index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().BindBufferBase(
+ target as crate::sys::types::GLenum,
+ index,
+ self.buf,
+ );
+ }
+ }
+
+ pub(crate) fn object(&self) -> crate::sys::types::GLuint
+ {
+ self.buf
+ }
+}
+
+impl<Item: ReprC> Debug for Buffer<Item>
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ let name = format!("Buffer<{}>", type_name::<Item>());
+
+ formatter
+ .debug_struct(&name)
+ .field("buf", &self.buf)
+ .finish()
+ }
+}
+
+/// Buffer usage.
+#[derive(Debug, Clone, Copy)]
+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, Clone, Copy)]
+#[repr(u32)]
+pub enum BindingTarget
+{
+ AtomicCounterBuffer = crate::sys::ATOMIC_COUNTER_BUFFER,
+ TransformFeedbackBuffer = crate::sys::TRANSFORM_FEEDBACK_BUFFER,
+ UniformBuffer = crate::sys::UNIFORM_BUFFER,
+ ShaderStorageBuffer = crate::sys::SHADER_STORAGE_BUFFER,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Size ({size}) is too large. Must be < {max_size}")]
+ SizeTooLarge
+ {
+ size: usize, max_size: usize
+ },
+
+ #[error("Byte offset ({byte_offset}) is too large. Must be < {max_byte_offset}")]
+ ByteOffsetTooLarge
+ {
+ byte_offset: usize,
+ max_byte_offset: usize,
+ },
+}
diff --git a/opengl-bindings/src/data_types.rs b/opengl-bindings/src/data_types.rs
new file mode 100644
index 0000000..a6c65fc
--- /dev/null
+++ b/opengl-bindings/src/data_types.rs
@@ -0,0 +1,47 @@
+use std::fmt::Display;
+
+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, Copy)]
+#[derive_ReprC]
+#[repr(C)]
+pub struct Vec2<Value>
+{
+ pub x: Value,
+ pub y: Value,
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct Dimens<Value>
+{
+ pub width: Value,
+ pub height: Value,
+}
+
+impl<Value: Display> Display for Dimens<Value>
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}x{}", self.width, self.height)
+ }
+}
diff --git a/opengl-bindings/src/debug.rs b/opengl-bindings/src/debug.rs
new file mode 100644
index 0000000..b842846
--- /dev/null
+++ b/opengl-bindings/src/debug.rs
@@ -0,0 +1,163 @@
+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::MaybeCurrentContextWithFns;
+
+#[tracing::instrument(skip(current_context, cb))]
+pub fn set_debug_message_callback(
+ current_context: &MaybeCurrentContextWithFns,
+ 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.
+#[tracing::instrument(skip(current_context))]
+pub fn set_debug_message_control(
+ current_context: &MaybeCurrentContextWithFns,
+ 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/framebuffer.rs b/opengl-bindings/src/framebuffer.rs
new file mode 100644
index 0000000..a758cc3
--- /dev/null
+++ b/opengl-bindings/src/framebuffer.rs
@@ -0,0 +1,212 @@
+use crate::texture::Texture;
+use crate::MaybeCurrentContextWithFns;
+
+#[derive(Debug, Clone)]
+pub struct Framebuffer
+{
+ inner: crate::sys::types::GLuint,
+}
+
+impl Framebuffer
+{
+ #[tracing::instrument(skip(current_context))]
+ #[must_use]
+ pub fn new(current_context: &MaybeCurrentContextWithFns) -> Self
+ {
+ let mut inner = 0;
+
+ unsafe {
+ current_context.fns().CreateFramebuffers(1, &raw mut inner);
+ }
+
+ Self { inner }
+ }
+
+ /// Attaches a texture object as a logical buffer of this framebuffer object.
+ #[tracing::instrument(skip(current_context))]
+ pub fn attach_texture(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attachment: Attachment,
+ texture: Texture,
+ texture_mipmap_level: u16,
+ )
+ {
+ unsafe {
+ current_context.fns().NamedFramebufferTexture(
+ self.inner,
+ attachment.into_gl(),
+ texture.into_raw(),
+ texture_mipmap_level.into(),
+ );
+ }
+ }
+
+ /// Detaches the texture object at the specified attachment point.
+ #[tracing::instrument(skip(current_context))]
+ pub fn detach_texture(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attachment: Attachment,
+ )
+ {
+ unsafe {
+ current_context.fns().NamedFramebufferTexture(
+ self.inner,
+ attachment.into_gl(),
+ 0,
+ 0,
+ );
+ }
+ }
+
+ /// Specifies which color buffer is to be drawn into when colors are written to this
+ /// framebuffer object.
+ ///
+ /// If `buffer` is `None`, no color buffer will be written into.
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_draw_buffer(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ buffer: Option<ColorAttachment>,
+ )
+ {
+ unsafe {
+ current_context.fns().NamedFramebufferDrawBuffer(
+ self.inner,
+ buffer
+ .map(ColorAttachment::into_gl)
+ .unwrap_or(crate::sys::NONE),
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .DeleteFramebuffers(1, &raw const self.inner);
+ }
+ }
+
+ pub fn from_raw(raw: u32) -> Self
+ {
+ Self { inner: raw }
+ }
+
+ pub fn into_raw(self) -> u32
+ {
+ self.inner
+ }
+}
+
+/// Binds a framebuffer to a framebuffer target.
+///
+/// If `framebuffer` is `None`, breaks the existing binding of a framebuffer object to
+/// the framebuffer target `target`.
+#[tracing::instrument(skip(current_context))]
+pub fn bind(
+ current_context: &MaybeCurrentContextWithFns,
+ target: Target,
+ framebuffer: Option<&Framebuffer>,
+)
+{
+ unsafe {
+ current_context.fns().BindFramebuffer(
+ target.into_gl(),
+ framebuffer
+ .map(|framebuffer| framebuffer.inner)
+ .unwrap_or(0),
+ );
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Target
+{
+ Draw,
+ Read,
+ DrawAndRead,
+}
+
+impl Target
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Draw => crate::sys::DRAW_FRAMEBUFFER,
+ Self::Read => crate::sys::READ_FRAMEBUFFER,
+ Self::DrawAndRead => crate::sys::FRAMEBUFFER,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Attachment
+{
+ Color(ColorAttachment),
+ Depth,
+ Stencil,
+ DepthStencil,
+}
+
+impl Attachment
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Color(color_attachment) => color_attachment.into_gl(),
+ Self::Depth => crate::sys::DEPTH_ATTACHMENT,
+ Self::Stencil => crate::sys::STENCIL_ATTACHMENT,
+ Self::DepthStencil => crate::sys::DEPTH_STENCIL_ATTACHMENT,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ColorAttachment(pub u8);
+
+impl ColorAttachment
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self.0 {
+ 0 => crate::sys::COLOR_ATTACHMENT0,
+ 1 => crate::sys::COLOR_ATTACHMENT1,
+ 10 => crate::sys::COLOR_ATTACHMENT10,
+ 11 => crate::sys::COLOR_ATTACHMENT11,
+ 12 => crate::sys::COLOR_ATTACHMENT12,
+ 13 => crate::sys::COLOR_ATTACHMENT13,
+ 14 => crate::sys::COLOR_ATTACHMENT14,
+ 15 => crate::sys::COLOR_ATTACHMENT15,
+ 16 => crate::sys::COLOR_ATTACHMENT16,
+ 17 => crate::sys::COLOR_ATTACHMENT17,
+ 18 => crate::sys::COLOR_ATTACHMENT18,
+ 19 => crate::sys::COLOR_ATTACHMENT19,
+ 2 => crate::sys::COLOR_ATTACHMENT2,
+ 20 => crate::sys::COLOR_ATTACHMENT20,
+ 21 => crate::sys::COLOR_ATTACHMENT21,
+ 22 => crate::sys::COLOR_ATTACHMENT22,
+ 23 => crate::sys::COLOR_ATTACHMENT23,
+ 24 => crate::sys::COLOR_ATTACHMENT24,
+ 25 => crate::sys::COLOR_ATTACHMENT25,
+ 26 => crate::sys::COLOR_ATTACHMENT26,
+ 27 => crate::sys::COLOR_ATTACHMENT27,
+ 28 => crate::sys::COLOR_ATTACHMENT28,
+ 29 => crate::sys::COLOR_ATTACHMENT29,
+ 3 => crate::sys::COLOR_ATTACHMENT3,
+ 30 => crate::sys::COLOR_ATTACHMENT30,
+ 31 => crate::sys::COLOR_ATTACHMENT31,
+ 4 => crate::sys::COLOR_ATTACHMENT4,
+ 5 => crate::sys::COLOR_ATTACHMENT5,
+ 6 => crate::sys::COLOR_ATTACHMENT6,
+ 7 => crate::sys::COLOR_ATTACHMENT7,
+ 8 => crate::sys::COLOR_ATTACHMENT8,
+ 9 => crate::sys::COLOR_ATTACHMENT9,
+ _ => panic!("Invalid color attachment index. Must be < 32"),
+ }
+ }
+}
diff --git a/opengl-bindings/src/lib.rs b/opengl-bindings/src/lib.rs
new file mode 100644
index 0000000..ec39c83
--- /dev/null
+++ b/opengl-bindings/src/lib.rs
@@ -0,0 +1,149 @@
+#![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::{GlSurface, Surface, SurfaceTypeTrait};
+
+pub mod blending;
+pub mod buffer;
+pub mod data_types;
+pub mod debug;
+pub mod framebuffer;
+pub mod misc;
+pub mod shader;
+pub mod texture;
+pub mod vertex_array;
+
+pub struct MaybeCurrentContextWithFns
+{
+ context: PossiblyCurrentContext,
+ fns: Box<sys::Gl>,
+}
+
+impl MaybeCurrentContextWithFns
+{
+ /// Returns a new `ContextWithFns`.
+ ///
+ /// # Errors
+ /// Returns `Err` if making this context current fails.
+ #[tracing::instrument(skip(context, surface))]
+ pub fn new<SurfaceType: SurfaceTypeTrait>(
+ context: NotCurrentContext,
+ surface: &Surface<SurfaceType>,
+ ) -> Result<Self, MakeContextCurrentError>
+ {
+ let context = context
+ .make_current(surface)
+ .map_err(MakeContextCurrentError)?;
+
+ 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 & the specified surface current in the calling
+ /// thread.
+ ///
+ /// # Errors
+ /// Returns `Err` if making this context current fails.
+ #[tracing::instrument(skip(self, surface))]
+ pub fn make_current<SurfaceType: SurfaceTypeTrait>(
+ &self,
+ surface: &Surface<SurfaceType>,
+ ) -> Result<(), MakeContextCurrentError>
+ {
+ if !self.context.is_current() || !surface.is_current(&self.context) {
+ self.context
+ .make_current(surface)
+ .map_err(MakeContextCurrentError)?;
+ }
+
+ Ok(())
+ }
+
+ /// Attempts to make this context current on the calling thread.
+ ///
+ /// # Errors
+ /// Returns `Err` if making this context current fails.
+ #[tracing::instrument(skip(self))]
+ pub fn make_current_surfaceless(&self) -> Result<(), MakeContextCurrentError>
+ {
+ if !self.context.is_current() {
+ make_glutin_context_current_surfaceless(&self.context)
+ .map_err(MakeContextCurrentError)?;
+ }
+
+ Ok(())
+ }
+
+ #[tracing::instrument(skip(self))]
+ #[must_use]
+ pub fn context(&self) -> &PossiblyCurrentContext
+ {
+ &self.context
+ }
+
+ #[tracing::instrument(skip(self))]
+ #[inline]
+ pub(crate) fn fns(&self) -> &sys::Gl
+ {
+ debug_assert!(self.context.is_current());
+
+ &self.fns
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("Failed to make context current")]
+pub struct MakeContextCurrentError(#[source] glutin::error::Error);
+
+#[tracing::instrument(skip(context))]
+fn make_glutin_context_current_surfaceless(
+ context: &PossiblyCurrentContext,
+) -> Result<(), glutin::error::Error>
+{
+ #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
+ compile_error!("Unsupported target OS");
+
+ match context {
+ #[cfg(any(target_os = "windows", target_os = "linux"))]
+ PossiblyCurrentContext::Egl(context) => context.make_current_surfaceless(),
+
+ #[cfg(target_os = "linux")]
+ PossiblyCurrentContext::Glx(context) => context.make_current_surfaceless(),
+
+ #[cfg(target_os = "windows")]
+ PossiblyCurrentContext::Wgl(context) => context.make_current_surfaceless(),
+
+ #[cfg(target_os = "macos")]
+ PossiblyCurrentContext::Cgl(context) => context.make_current_surfaceless(),
+ }
+}
+
+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..9ad8c4a
--- /dev/null
+++ b/opengl-bindings/src/misc.rs
@@ -0,0 +1,319 @@
+use bitflags::bitflags;
+
+use crate::data_types::{Dimens, Vec2};
+use crate::MaybeCurrentContextWithFns;
+
+/// 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`.
+#[tracing::instrument(skip(current_context))]
+pub fn set_viewport(
+ current_context: &MaybeCurrentContextWithFns,
+ 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(())
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn get_viewport(
+ current_context: &MaybeCurrentContextWithFns,
+) -> (Vec2<u32>, Dimens<u32>)
+{
+ let mut values = [0i32; 4];
+
+ unsafe {
+ current_context
+ .fns()
+ .GetIntegerv(crate::sys::VIEWPORT, values.as_mut_ptr());
+ }
+
+ let [x, y, width, height] = values;
+
+ let pos = Vec2::<u32> {
+ x: x.try_into().expect("Negative viewport x coordinate"),
+ y: y.try_into().expect("Negative viewport y coordinate"),
+ };
+
+ let size = Dimens::<u32> {
+ width: width.try_into().expect("Negative viewport width"),
+ height: height.try_into().expect("Negative viewport height"),
+ };
+
+ (pos, size)
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn clear_buffers(current_context: &MaybeCurrentContextWithFns, mask: BufferClearMask)
+{
+ unsafe {
+ current_context.fns().Clear(mask.bits());
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn set_polygon_mode(
+ current_context: &MaybeCurrentContextWithFns,
+ face: PolygonModeFace,
+ mode: PolygonMode,
+)
+{
+ unsafe {
+ current_context.fns().PolygonMode(face as u32, mode as u32);
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn set_face_culling_mode(
+ current_context: &MaybeCurrentContextWithFns,
+ face_culling_mode: FaceCullingMode,
+)
+{
+ unsafe { current_context.fns().CullFace(face_culling_mode as u32) }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn enable(current_context: &MaybeCurrentContextWithFns, capacity: Capability)
+{
+ unsafe {
+ current_context.fns().Enable(capacity as u32);
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn disable(current_context: &MaybeCurrentContextWithFns, capability: Capability)
+{
+ unsafe {
+ current_context.fns().Disable(capability as u32);
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn set_enabled(
+ current_context: &MaybeCurrentContextWithFns,
+ capability: Capability,
+ enabled: bool,
+)
+{
+ if enabled {
+ enable(current_context, capability);
+ } else {
+ disable(current_context, capability);
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+#[must_use]
+pub fn get_context_flags(current_context: &MaybeCurrentContextWithFns) -> 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())
+}
+
+/// Defines a rectangle, called the scissor box, in window coordinates.
+#[tracing::instrument(skip(current_context))]
+pub fn define_scissor_box(
+ current_context: &MaybeCurrentContextWithFns,
+ lower_left_corner_pos: Vec2<u16>,
+ size: Dimens<u16>,
+)
+{
+ let lower_left_corner_pos = Vec2::<crate::sys::types::GLint> {
+ x: lower_left_corner_pos.x.into(),
+ y: lower_left_corner_pos.y.into(),
+ };
+
+ let size = Dimens::<crate::sys::types::GLsizei> {
+ width: size.width.into(),
+ height: size.height.into(),
+ };
+
+ unsafe {
+ current_context.fns().Scissor(
+ lower_left_corner_pos.x,
+ lower_left_corner_pos.y,
+ size.width,
+ size.height,
+ );
+ }
+}
+
+#[tracing::instrument(skip(current_context))]
+pub fn set_depth_function(
+ current_context: &MaybeCurrentContextWithFns,
+ depth_function: DepthFunction,
+)
+{
+ unsafe {
+ current_context.fns().DepthFunc(depth_function as u32);
+ }
+}
+
+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, Clone, Copy)]
+#[repr(u32)]
+#[non_exhaustive]
+pub enum Capability
+{
+ DepthTest = crate::sys::DEPTH_TEST,
+ ScissorTest = crate::sys::SCISSOR_TEST,
+ MultiSample = crate::sys::MULTISAMPLE,
+ DebugOutput = crate::sys::DEBUG_OUTPUT,
+ DebugOutputSynchronous = crate::sys::DEBUG_OUTPUT_SYNCHRONOUS,
+ Blend = crate::sys::BLEND,
+ CullFace = crate::sys::CULL_FACE,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
+pub enum PolygonMode
+{
+ Point = crate::sys::POINT,
+ Line = crate::sys::LINE,
+ Fill = crate::sys::FILL,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
+pub enum PolygonModeFace
+{
+ Front = crate::sys::FRONT,
+ Back = crate::sys::BACK,
+ FrontAndBack = crate::sys::FRONT_AND_BACK,
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+#[repr(u32)]
+pub enum FaceCullingMode
+{
+ Front = crate::sys::FRONT,
+
+ #[default]
+ 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, Default, Clone, Copy)]
+#[repr(u32)]
+pub enum DepthFunction
+{
+ /// Never passes.
+ Never = crate::sys::NEVER,
+
+ /// Passes if the incoming depth value is less than the stored depth value.
+ #[default]
+ Less = crate::sys::LESS,
+
+ /// Passes if the incoming depth value is equal to the stored depth value.
+ Equal = crate::sys::EQUAL,
+
+ /// Passes if the incoming depth value is less than or equal to the stored depth
+ /// value.
+ LessOrEqual = crate::sys::LEQUAL,
+
+ /// Passes if the incoming depth value is greater than the stored depth value.
+ Greater = crate::sys::GREATER,
+
+ /// Passes if the incoming depth value is not equal to the stored depth value.
+ NotEqual = crate::sys::NOTEQUAL,
+
+ /// Passes if the incoming depth value is greater than or equal to the stored depth
+ /// value.
+ GreaterOrEqual = crate::sys::GEQUAL,
+
+ /// Always passes.
+ Always = crate::sys::ALWAYS,
+}
+
+#[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..e3df35c
--- /dev/null
+++ b/opengl-bindings/src/shader.rs
@@ -0,0 +1,412 @@
+use std::ffi::CStr;
+use std::ptr::null_mut;
+
+use safer_ffi::layout::ReprC;
+
+use crate::data_types::{Matrix, Vec3};
+use crate::MaybeCurrentContextWithFns;
+
+#[derive(Debug, Clone)]
+pub struct Shader
+{
+ shader: crate::sys::types::GLuint,
+}
+
+impl Shader
+{
+ #[tracing::instrument(skip(current_context))]
+ #[must_use]
+ pub fn new(current_context: &MaybeCurrentContextWithFns, 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.
+ #[tracing::instrument(skip(current_context, source))]
+ pub fn set_source(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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.
+ #[tracing::instrument(skip(current_context))]
+ pub fn compile(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ ) -> 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(())
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().DeleteShader(self.shader);
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ fn get_info_log(&self, current_context: &MaybeCurrentContextWithFns) -> 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, Clone, PartialEq, Eq, Hash)]
+pub struct Program
+{
+ program: crate::sys::types::GLuint,
+}
+
+impl Program
+{
+ #[tracing::instrument(skip(current_context))]
+ #[must_use]
+ pub fn new(current_context: &MaybeCurrentContextWithFns) -> Self
+ {
+ let program = unsafe { current_context.fns().CreateProgram() };
+
+ Self { program }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn attach(&self, current_context: &MaybeCurrentContextWithFns, shader: &Shader)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .AttachShader(self.program, shader.shader);
+ }
+ }
+
+ /// Links this program.
+ ///
+ /// # Errors
+ /// Returns `Err` if linking fails.
+ #[tracing::instrument(skip(current_context))]
+ pub fn link(&self, current_context: &MaybeCurrentContextWithFns)
+ -> 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(())
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn activate(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().UseProgram(self.program);
+ }
+ }
+
+ #[tracing::instrument(skip(current_context, var))]
+ pub fn set_uniform_at_location(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ location: UniformLocation,
+ var: &impl UniformVariable,
+ )
+ {
+ var.set(current_context, self, location);
+ }
+
+ #[tracing::instrument(skip(current_context, var))]
+ pub fn set_uniform(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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 from_raw(raw: u32) -> Self
+ {
+ Self { program: raw }
+ }
+
+ pub fn into_raw(self) -> u32
+ {
+ self.program
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().DeleteProgram(self.program);
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ fn get_info_log(&self, current_context: &MaybeCurrentContextWithFns) -> 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: &MaybeCurrentContextWithFns,
+ program: &Program,
+ uniform_location: UniformLocation,
+ );
+}
+
+impl UniformVariable for f32
+{
+ #[tracing::instrument(skip(current_context))]
+ fn set(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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
+{
+ #[tracing::instrument(skip(current_context, program, uniform_location))]
+ fn set(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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>
+{
+ #[tracing::instrument(skip(current_context, program, uniform_location))]
+ fn set(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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>
+{
+ #[tracing::instrument(skip(current_context, program, uniform_location))]
+ fn set(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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, Clone)]
+pub struct UniformLocation(crate::sys::types::GLint);
+
+impl UniformLocation
+{
+ pub fn from_number(number: i32) -> Self
+ {
+ Self(number)
+ }
+}
+
+/// 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..e0bd19f
--- /dev/null
+++ b/opengl-bindings/src/texture.rs
@@ -0,0 +1,703 @@
+use crate::data_types::{Dimens, Vec2};
+use crate::MaybeCurrentContextWithFns;
+
+#[derive(Debug, Clone)]
+pub struct Builder
+{
+ size: Dimens<u32>,
+ mipmap_levels: u16,
+}
+
+impl Builder
+{
+ pub fn size(mut self, size: Dimens<u32>) -> Self
+ {
+ self.size = size;
+ self
+ }
+
+ pub fn mipmap_levels(mut self, mipmap_levels: u16) -> Self
+ {
+ self.mipmap_levels = mipmap_levels;
+ self
+ }
+
+ #[tracing::instrument(skip(current_context, image))]
+ #[must_use]
+ pub fn create_2d<'image>(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ image: Option<&[u8]>,
+ pixel_data_format: PixelDataFormat,
+ ) -> Result<Texture, Error>
+ {
+ if let Some(image) = &image {
+ check_image_buffer_len_correct_for_size(
+ image,
+ [self.size.width, self.size.height, 1],
+ pixel_data_format,
+ )?;
+ }
+
+ let size = try_convert_size([self.size.width, self.size.height])?;
+
+ let texture = Texture::new(current_context, crate::sys::TEXTURE_2D);
+
+ texture.alloc_2d(current_context, self.mipmap_levels, pixel_data_format, size);
+
+ if let Some(image) = image {
+ texture.sub_image_2d(
+ current_context,
+ 0,
+ [0, 0],
+ size,
+ pixel_data_format,
+ image,
+ );
+
+ texture.generate_mipmap(current_context);
+ }
+
+ Ok(texture)
+ }
+
+ #[tracing::instrument(skip(current_context, images))]
+ #[must_use]
+ pub fn create_cube_map<'image>(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ images: Option<[(CubeMapFace, &[u8]); 6]>,
+ pixel_data_format: PixelDataFormat,
+ ) -> Result<Texture, Error>
+ {
+ for (_, image) in images.iter().flatten() {
+ check_image_buffer_len_correct_for_size(
+ image.as_ref(),
+ [self.size.width, self.size.height, 1],
+ pixel_data_format,
+ )?;
+ }
+
+ let size = try_convert_size([self.size.width, self.size.height])?;
+
+ let texture = Texture::new(current_context, crate::sys::TEXTURE_CUBE_MAP);
+
+ texture.alloc_2d(current_context, self.mipmap_levels, pixel_data_format, size);
+
+ for (face, image) in images.iter().flatten() {
+ texture.sub_image_3d(
+ current_context,
+ 0,
+ [0, 0, *face as crate::sys::types::GLint],
+ [size[0], size[1], 1],
+ pixel_data_format,
+ *image,
+ );
+ }
+
+ if images.is_some() {
+ texture.generate_mipmap(current_context);
+ }
+
+ Ok(texture)
+ }
+}
+
+impl Default for Builder
+{
+ fn default() -> Self
+ {
+ Self {
+ size: Dimens::default(),
+ mipmap_levels: 1,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Texture
+{
+ texture: crate::sys::types::GLuint,
+}
+
+impl Texture
+{
+ pub fn builder() -> Builder
+ {
+ Builder::default()
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn bind_to_texture_unit(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ texture_unit: u32,
+ )
+ {
+ unsafe {
+ current_context
+ .fns()
+ .BindTextureUnit(texture_unit, self.texture);
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn generate_mipmap(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().GenerateTextureMipmap(self.texture);
+ }
+ }
+
+ #[tracing::instrument(skip_all)]
+ pub fn store_image_2d(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_level: u16,
+ offset: Vec2<u32>,
+ size: Dimens<u32>,
+ pixel_data_format: PixelDataFormat,
+ image: &[u8],
+ ) -> Result<(), Error>
+ {
+ check_image_buffer_len_correct_for_size(
+ image,
+ [size.width, size.height, 1],
+ pixel_data_format,
+ )?;
+
+ let offset = try_convert_offset([offset.x, offset.y])?;
+ let size = try_convert_size([size.width, size.height])?;
+
+ self.sub_image_2d(
+ current_context,
+ mipmap_level,
+ offset,
+ size,
+ pixel_data_format,
+ image,
+ );
+
+ Ok(())
+ }
+
+ #[tracing::instrument(skip_all)]
+ pub fn store_image_3d(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_level: u16,
+ offset: [u32; 3],
+ size: [u32; 3],
+ pixel_data_format: PixelDataFormat,
+ image: &[u8],
+ ) -> Result<(), Error>
+ {
+ check_image_buffer_len_correct_for_size(image, size, pixel_data_format)?;
+
+ let offset = try_convert_offset(offset)?;
+ let size = try_convert_size(size)?;
+
+ self.sub_image_3d(
+ current_context,
+ mipmap_level,
+ offset,
+ size,
+ pixel_data_format,
+ image,
+ );
+
+ Ok(())
+ }
+
+ #[tracing::instrument(skip_all, fields(wrapping = ?wrapping))]
+ pub fn set_wrap(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ 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,
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_magnifying_filter(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ filtering: Filtering,
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_MAG_FILTER,
+ filtering as i32,
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_minifying_filter(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ filtering: Filtering,
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameteri(
+ self.texture,
+ crate::sys::TEXTURE_MIN_FILTER,
+ filtering as i32,
+ );
+ }
+ }
+
+ /// Sets four floating-point values that define the border values that should be used
+ /// for border texels. If a texel is sampled from the border of the texture, these
+ /// values are interpreted as an RGBA color to match the texture's internal format
+ /// and substituted for the non-existent texel data. If the texture contains depth
+ /// components, the first value is interpreted as a depth value.
+ ///
+ /// The values are stored unmodified as floating-point values.
+ ///
+ /// The initial values are (0.0, 0.0, 0.0, 0.0).
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_float_border_values(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ values: [f32; 4],
+ )
+ {
+ unsafe {
+ current_context.fns().TextureParameterfv(
+ self.texture,
+ crate::sys::TEXTURE_BORDER_COLOR,
+ values.as_ptr(),
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .DeleteTextures(1, &raw const self.texture);
+ }
+ }
+
+ pub fn from_raw(raw: u32) -> Self
+ {
+ Self { texture: raw }
+ }
+
+ pub fn into_raw(self) -> u32
+ {
+ self.texture
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ fn new(
+ current_context: &MaybeCurrentContextWithFns,
+ target: crate::sys::types::GLenum,
+ ) -> Self
+ {
+ let mut texture = crate::sys::types::GLuint::default();
+
+ unsafe {
+ current_context
+ .fns()
+ .CreateTextures(target, 1, &raw mut texture);
+ };
+
+ Self { texture }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ fn alloc_2d(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_levels: u16,
+ pixel_data_format: PixelDataFormat,
+ size: [crate::sys::types::GLsizei; 2],
+ )
+ {
+ unsafe {
+ current_context.fns().TextureStorage2D(
+ self.texture,
+ mipmap_levels.into(),
+ pixel_data_format.into_gl_sized_internal_format(),
+ size[0],
+ size[1],
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context, image))]
+ fn sub_image_2d(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_level: u16,
+ offset: [crate::sys::types::GLint; 2],
+ size: [crate::sys::types::GLsizei; 2],
+ pixel_data_format: PixelDataFormat,
+ image: &[u8],
+ )
+ {
+ let mut is_pixel_unpack_alignment_changed = false;
+
+ if let Some(new_pixel_unpack_alignment) =
+ pixel_data_format.requires_adjust_pixel_unpack_alignment()
+ {
+ set_pixel_unpack_alignment(current_context, new_pixel_unpack_alignment);
+
+ is_pixel_unpack_alignment_changed = true;
+ }
+
+ unsafe {
+ current_context.fns().TextureSubImage2D(
+ self.texture,
+ mipmap_level.into(),
+ offset[0],
+ offset[1],
+ size[0],
+ size[1],
+ pixel_data_format.into_gl_format(),
+ pixel_data_format.into_gl_data_type(),
+ image.as_ptr().cast(),
+ );
+ }
+
+ if is_pixel_unpack_alignment_changed {
+ set_pixel_unpack_alignment(current_context, PixelAlignment::default());
+ }
+ }
+
+ #[tracing::instrument(skip(current_context, image))]
+ fn sub_image_3d(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_level: u16,
+ offset: [crate::sys::types::GLint; 3],
+ size: [crate::sys::types::GLsizei; 3],
+ pixel_data_format: PixelDataFormat,
+ image: &[u8],
+ )
+ {
+ let mut is_pixel_unpack_alignment_changed = false;
+
+ if let Some(new_pixel_unpack_alignment) =
+ pixel_data_format.requires_adjust_pixel_unpack_alignment()
+ {
+ set_pixel_unpack_alignment(current_context, new_pixel_unpack_alignment);
+
+ is_pixel_unpack_alignment_changed = true;
+ }
+
+ unsafe {
+ current_context.fns().TextureSubImage3D(
+ self.texture,
+ mipmap_level.into(),
+ offset[0],
+ offset[1],
+ offset[2],
+ size[0],
+ size[1],
+ size[2],
+ pixel_data_format.into_gl_format(),
+ pixel_data_format.into_gl_data_type(),
+ image.as_ptr().cast(),
+ );
+ }
+
+ if is_pixel_unpack_alignment_changed {
+ set_pixel_unpack_alignment(current_context, PixelAlignment::default());
+ }
+ }
+}
+
+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) },
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u32)]
+pub enum RgbDataType
+{
+ UnsignedByte = crate::sys::UNSIGNED_BYTE,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u32)]
+pub enum RgbaDataType
+{
+ UnsignedByte = crate::sys::UNSIGNED_BYTE,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u32)]
+pub enum SrgbDataType
+{
+ UnsignedByte = crate::sys::UNSIGNED_BYTE,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u32)]
+pub enum SrgbaDataType
+{
+ UnsignedByte = crate::sys::UNSIGNED_BYTE,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+#[repr(u32)]
+pub enum DepthComponentDataType
+{
+ Float32 = crate::sys::FLOAT,
+}
+
+/// Texture pixel data format.
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+pub enum PixelDataFormat
+{
+ Rgb(RgbDataType),
+ Rgba(RgbaDataType),
+ Srgb(SrgbDataType),
+ Srgba(SrgbaDataType),
+ DepthComponent(DepthComponentDataType),
+}
+
+impl PixelDataFormat
+{
+ fn into_gl_sized_internal_format(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Rgb(RgbDataType::UnsignedByte) => crate::sys::RGB8,
+ Self::Rgba(RgbaDataType::UnsignedByte) => crate::sys::RGBA8,
+ Self::Srgb(SrgbDataType::UnsignedByte) => crate::sys::SRGB8,
+ Self::Srgba(SrgbaDataType::UnsignedByte) => crate::sys::SRGB8_ALPHA8,
+ Self::DepthComponent(DepthComponentDataType::Float32) => {
+ crate::sys::DEPTH_COMPONENT32F
+ }
+ }
+ }
+
+ fn into_gl_format(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Rgb(_) | Self::Srgb(_) => crate::sys::RGB,
+ Self::Rgba(_) | Self::Srgba(_) => crate::sys::RGBA,
+ Self::DepthComponent(_) => crate::sys::DEPTH_COMPONENT,
+ }
+ }
+
+ fn into_gl_data_type(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Rgb(ty) => ty as crate::sys::types::GLenum,
+ Self::Srgb(ty) => ty as crate::sys::types::GLenum,
+ Self::Rgba(ty) => ty as crate::sys::types::GLenum,
+ Self::Srgba(ty) => ty as crate::sys::types::GLenum,
+ Self::DepthComponent(ty) => ty as crate::sys::types::GLenum,
+ }
+ }
+
+ fn pixel_width(&self) -> usize
+ {
+ match self {
+ Self::Rgb(_) | Self::Srgb(_) => 3,
+ Self::Rgba(_) | Self::Srgba(_) => 4,
+ Self::DepthComponent(_) => 1,
+ }
+ }
+
+ fn requires_adjust_pixel_unpack_alignment(&self) -> Option<PixelAlignment>
+ {
+ if matches!(
+ self,
+ PixelDataFormat::Rgb(RgbDataType::UnsignedByte)
+ | PixelDataFormat::Srgb(SrgbDataType::UnsignedByte)
+ ) {
+ return Some(PixelAlignment::Byte);
+ }
+
+ None
+ }
+}
+
+impl Default for PixelDataFormat
+{
+ fn default() -> Self
+ {
+ PixelDataFormat::Rgba(RgbaDataType::UnsignedByte)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum CubeMapFace
+{
+ PositiveX = 0,
+ NegativeX = 1,
+ PositiveY = 2,
+ NegativeY = 3,
+ PositiveZ = 4,
+ NegativeZ = 5,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Size value ({value}) is too large. Must be < {max_value}")]
+ ValueInSizeIsTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Offset value ({value}) is too large. Must be < {max_value}")]
+ ValueInOffsetIsTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error(
+ "Incorrect image buffer length for size {}x{}x{}. Expected {}, found {}",
+ size[0],
+ size[1],
+ size[2],
+ expected_buffer_len,
+ buffer_len
+ )]
+ IncorrectImageBufferLengthForSize
+ {
+ expected_buffer_len: usize,
+ buffer_len: usize,
+ size: [u32; 3],
+ },
+}
+
+fn try_convert_size<const LEN: usize>(
+ size: [u32; LEN],
+) -> Result<[crate::sys::types::GLsizei; LEN], Error>
+{
+ let mut output = [0; LEN];
+
+ for (value, output_val) in size.into_iter().zip(&mut output) {
+ *output_val = value.try_into().map_err(|_| Error::ValueInSizeIsTooLarge {
+ value: value,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?;
+ }
+
+ Ok(output)
+}
+
+fn try_convert_offset<const LEN: usize>(
+ offset: [u32; LEN],
+) -> Result<[crate::sys::types::GLint; LEN], Error>
+{
+ let mut output = [0; LEN];
+
+ for (value, output_val) in offset.into_iter().zip(&mut output) {
+ *output_val = value
+ .try_into()
+ .map_err(|_| Error::ValueInOffsetIsTooLarge {
+ value: value,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ })?;
+ }
+
+ Ok(output)
+}
+
+fn check_image_buffer_len_correct_for_size(
+ image: &[u8],
+ size: [u32; 3],
+ pixel_data_format: PixelDataFormat,
+) -> Result<(), Error>
+{
+ let pixel_width = pixel_data_format.pixel_width();
+
+ let [width, height, depth] = size;
+
+ let expected_buffer_len =
+ width as usize * height as usize * depth as usize * pixel_width;
+
+ if expected_buffer_len != image.len() {
+ return Err(Error::IncorrectImageBufferLengthForSize {
+ expected_buffer_len,
+ buffer_len: image.len(),
+ size,
+ });
+ }
+
+ Ok(())
+}
+
+#[tracing::instrument(skip(current_context))]
+fn set_pixel_unpack_alignment(
+ current_context: &MaybeCurrentContextWithFns,
+ alignment: PixelAlignment,
+)
+{
+ let alignment = match alignment {
+ PixelAlignment::Byte => 1,
+ // PixelAlignment::TwoBytes => 2,
+ PixelAlignment::FourBytes => 4,
+ // PixelAlignment::EightBytes => 8,
+ };
+
+ unsafe {
+ current_context
+ .fns()
+ .PixelStorei(crate::sys::UNPACK_ALIGNMENT, alignment);
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+enum PixelAlignment
+{
+ Byte,
+ // TwoBytes,
+ #[default]
+ FourBytes,
+ // EightBytes,
+}
diff --git a/opengl-bindings/src/vertex_array.rs b/opengl-bindings/src/vertex_array.rs
new file mode 100644
index 0000000..723bbfb
--- /dev/null
+++ b/opengl-bindings/src/vertex_array.rs
@@ -0,0 +1,354 @@
+use std::ffi::c_void;
+
+use safer_ffi::layout::ReprC;
+
+use crate::buffer::Buffer;
+use crate::MaybeCurrentContextWithFns;
+
+#[derive(Debug, Clone)]
+pub struct VertexArray
+{
+ array: crate::sys::types::GLuint,
+}
+
+impl VertexArray
+{
+ #[tracing::instrument(skip(current_context))]
+ #[must_use]
+ pub fn new(current_context: &MaybeCurrentContextWithFns) -> 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:
+ /// - `vertex_offset` is too large
+ /// - `vertex_cnt` is too large
+ #[tracing::instrument(skip(current_context))]
+ pub fn draw_arrays(
+ current_context: &MaybeCurrentContextWithFns,
+ primitive_kind: PrimitiveKind,
+ vertex_offset: u32,
+ vertex_cnt: u32,
+ ) -> Result<(), DrawError>
+ {
+ let vertex_offset: crate::sys::types::GLint =
+ vertex_offset.try_into().map_err(|_| {
+ DrawError::VertexOffsetValueTooLarge {
+ value: vertex_offset,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ }
+ })?;
+
+ let vertex_cnt: crate::sys::types::GLsizei =
+ vertex_cnt
+ .try_into()
+ .map_err(|_| DrawError::VertexCountValueTooLarge {
+ value: vertex_cnt,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?;
+
+ unsafe {
+ current_context.fns().DrawArrays(
+ primitive_kind.into_gl(),
+ vertex_offset,
+ vertex_cnt,
+ );
+ }
+
+ Ok(())
+ }
+
+ /// Draws the currently bound vertex array.
+ ///
+ /// # Errors
+ /// Returns `Err` if `cnt` is too large.
+ #[tracing::instrument(skip(current_context))]
+ pub fn draw_elements(
+ current_context: &MaybeCurrentContextWithFns,
+ DrawElementsOptions {
+ primitive_kind,
+ element_offset,
+ element_cnt,
+ vertex_offset,
+ }: DrawElementsOptions,
+ ) -> Result<(), DrawError>
+ {
+ let element_cnt: crate::sys::types::GLsizei =
+ element_cnt
+ .try_into()
+ .map_err(|_| DrawError::ElementCountValueTooLarge {
+ value: element_cnt,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?;
+
+ let vertex_offset: crate::sys::types::GLint =
+ vertex_offset.try_into().map_err(|_| {
+ DrawError::VertexOffsetValueTooLarge {
+ value: vertex_offset,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ }
+ })?;
+
+ unsafe {
+ current_context.fns().DrawElementsBaseVertex(
+ primitive_kind.into_gl(),
+ element_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>(
+ element_offset as usize * size_of::<u32>(),
+ ),
+ vertex_offset,
+ );
+ }
+
+ Ok(())
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn bind_element_buffer(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ element_buffer: &Buffer<u32>,
+ )
+ {
+ unsafe {
+ current_context
+ .fns()
+ .VertexArrayElementBuffer(self.array, element_buffer.object());
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn bind_vertex_buffer<VertexBufferItem: ReprC>(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ binding_index: u32,
+ vertex_buffer: &Buffer<VertexBufferItem>,
+ vertex_buffer_spec: VertexBufferSpec,
+ ) -> Result<(), BindVertexBufferError>
+ {
+ let offset: crate::sys::types::GLintptr = vertex_buffer_spec
+ .offset
+ .try_into()
+ .map_err(|_| BindVertexBufferError::OffsetValueTooLarge {
+ value: vertex_buffer_spec.offset,
+ max_value: crate::sys::types::GLintptr::MAX as usize,
+ })?;
+
+ let vertex_size: crate::sys::types::GLsizei = vertex_buffer_spec
+ .vertex_size
+ .try_into()
+ .map_err(|_| BindVertexBufferError::VertexSizeValueTooLarge {
+ value: vertex_buffer_spec.vertex_size,
+ max_value: crate::sys::types::GLsizei::MAX as usize,
+ })?;
+
+ unsafe {
+ current_context.fns().VertexArrayVertexBuffer(
+ self.array,
+ binding_index,
+ vertex_buffer.object(),
+ offset,
+ vertex_size,
+ );
+ }
+
+ Ok(())
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn enable_attrib(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attrib_index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().EnableVertexArrayAttrib(
+ self.array,
+ attrib_index as crate::sys::types::GLuint,
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn disable_attrib(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attrib_index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().DisableVertexArrayAttrib(
+ self.array,
+ attrib_index as crate::sys::types::GLuint,
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_attrib_format(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attrib_index: u32,
+ attrib_format: AttributeFormat,
+ )
+ {
+ unsafe {
+ current_context.fns().VertexArrayAttribFormat(
+ self.array,
+ attrib_index,
+ attrib_format.count.into(),
+ attrib_format.data_type.into_gl(),
+ if attrib_format.normalized {
+ crate::sys::TRUE
+ } else {
+ crate::sys::FALSE
+ },
+ attrib_format.offset,
+ );
+ }
+ }
+
+ /// Associate a vertex attribute and a vertex buffer binding.
+ #[tracing::instrument(skip(current_context))]
+ pub fn set_attrib_vertex_buf_binding(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ attrib_index: u32,
+ vertex_buf_binding_index: u32,
+ )
+ {
+ unsafe {
+ current_context.fns().VertexArrayAttribBinding(
+ self.array,
+ attrib_index,
+ vertex_buf_binding_index,
+ );
+ }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn bind(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe { current_context.fns().BindVertexArray(self.array) }
+ }
+
+ #[tracing::instrument(skip(current_context))]
+ pub fn delete(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context
+ .fns()
+ .DeleteVertexArrays(1, &raw const self.array);
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+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)]
+#[non_exhaustive]
+pub enum DataType
+{
+ Float,
+}
+
+impl DataType
+{
+ fn into_gl(self) -> crate::sys::types::GLenum
+ {
+ match self {
+ Self::Float => crate::sys::FLOAT,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct AttributeFormat
+{
+ pub data_type: DataType,
+ pub count: u8,
+ pub normalized: bool,
+ pub offset: u32,
+}
+
+#[derive(Debug, Clone)]
+pub struct VertexBufferSpec
+{
+ pub offset: usize,
+ pub vertex_size: usize,
+}
+
+#[derive(Debug, Clone)]
+pub struct DrawElementsOptions
+{
+ pub primitive_kind: PrimitiveKind,
+ pub element_offset: u32,
+ pub element_cnt: u32,
+ pub vertex_offset: u32,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum DrawError
+{
+ #[error("Vertex offset value {value} is too large. Must be < {max_value}")]
+ VertexOffsetValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Vertex count value {value} is too large. Must be < {max_value}")]
+ VertexCountValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error("Element count value {value} is too large. Must be < {max_value}")]
+ ElementCountValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum BindVertexBufferError
+{
+ #[error("Offset value {value} is too large. Must be < {max_value}")]
+ OffsetValueTooLarge
+ {
+ value: usize, max_value: usize
+ },
+
+ #[error("Vertex size value {value} is too large. Must be < {max_value}")]
+ VertexSizeValueTooLarge
+ {
+ value: usize, max_value: usize
+ },
+}