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