diff options
Diffstat (limited to 'opengl-bindings')
| -rw-r--r-- | opengl-bindings/Cargo.toml | 84 | ||||
| -rw-r--r-- | opengl-bindings/build.rs | 107 | ||||
| -rw-r--r-- | opengl-bindings/src/blending.rs | 181 | ||||
| -rw-r--r-- | opengl-bindings/src/buffer.rs | 290 | ||||
| -rw-r--r-- | opengl-bindings/src/data_types.rs | 47 | ||||
| -rw-r--r-- | opengl-bindings/src/debug.rs | 163 | ||||
| -rw-r--r-- | opengl-bindings/src/framebuffer.rs | 212 | ||||
| -rw-r--r-- | opengl-bindings/src/lib.rs | 149 | ||||
| -rw-r--r-- | opengl-bindings/src/misc.rs | 319 | ||||
| -rw-r--r-- | opengl-bindings/src/shader.rs | 412 | ||||
| -rw-r--r-- | opengl-bindings/src/texture.rs | 703 | ||||
| -rw-r--r-- | opengl-bindings/src/vertex_array.rs | 354 |
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 + }, +} |
