diff options
Diffstat (limited to 'engine/src/opengl')
-rw-r--r-- | engine/src/opengl/currently_bound.rs | 22 | ||||
-rw-r--r-- | engine/src/opengl/mod.rs | 38 | ||||
-rw-r--r-- | engine/src/opengl/shader.rs | 198 | ||||
-rw-r--r-- | engine/src/opengl/vertex_array.rs | 131 | ||||
-rw-r--r-- | engine/src/opengl/vertex_buffer.rs | 93 |
5 files changed, 482 insertions, 0 deletions
diff --git a/engine/src/opengl/currently_bound.rs b/engine/src/opengl/currently_bound.rs new file mode 100644 index 0000000..eefa239 --- /dev/null +++ b/engine/src/opengl/currently_bound.rs @@ -0,0 +1,22 @@ +use std::marker::PhantomData; + +/// A token signifying a OpenGL object is currently bound. +pub struct CurrentlyBound<'token, Object> +{ + _token: PhantomData<&'token Object>, +} + +impl<'token, Object> CurrentlyBound<'token, Object> +{ + /// Returns a new `CurrentlyBound`. + /// + /// # Safety + /// A object must actually be currently bound. Otherwise, UB can occur. + #[must_use] + pub unsafe fn new() -> Self + { + Self { + _token: PhantomData, + } + } +} diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs new file mode 100644 index 0000000..a58e72e --- /dev/null +++ b/engine/src/opengl/mod.rs @@ -0,0 +1,38 @@ +use bitflags::bitflags; +use glfw::WindowSize; + +use crate::vector::Vec2; + +pub mod currently_bound; +pub mod shader; +pub mod vertex_array; +pub mod vertex_buffer; + +pub fn set_viewport(position: &Vec2<u32>, size: &WindowSize) +{ + unsafe { + #[allow(clippy::cast_possible_wrap)] + gl::Viewport( + position.x as i32, + position.y as i32, + size.width as i32, + size.height as i32, + ); + } +} + +pub fn clear_buffers(mask: BufferClearMask) +{ + unsafe { + gl::Clear(mask.bits()); + } +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct BufferClearMask: u32 { + const COLOR = gl::COLOR_BUFFER_BIT; + const DEPTH = gl::DEPTH_BUFFER_BIT; + const STENCIL = gl::STENCIL_BUFFER_BIT; + } +} diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs new file mode 100644 index 0000000..0bbca77 --- /dev/null +++ b/engine/src/opengl/shader.rs @@ -0,0 +1,198 @@ +use std::ffi::CStr; +use std::ptr::null_mut; + +#[derive(Debug)] +pub struct Shader +{ + shader: gl::types::GLuint, +} + +impl Shader +{ + pub fn new(kind: Kind) -> Self + { + let shader = unsafe { gl::CreateShader(kind.into_gl()) }; + + Self { shader } + } + + pub fn set_source(&self, source: &str) -> Result<(), Error> + { + if !source.is_ascii() { + return Err(Error::SourceNotAscii); + } + + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::ShaderSource( + self.shader, + 1, + &source.as_ptr().cast(), + &(source.len() as gl::types::GLint), + ); + } + + Ok(()) + } + + pub fn compile(&self) -> Result<(), Error> + { + unsafe { + gl::CompileShader(self.shader); + } + + let mut compile_success = gl::types::GLint::default(); + + unsafe { + gl::GetShaderiv(self.shader, gl::COMPILE_STATUS, &mut compile_success); + } + + if compile_success == 0 { + let info_log = self.get_info_log(); + + return Err(Error::CompileFailed(info_log)); + } + + Ok(()) + } + + fn get_info_log(&self) -> String + { + let mut buf = vec![gl::types::GLchar::default(); 512]; + + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::GetShaderInfoLog( + self.shader, + buf.len() as gl::types::GLsizei, + 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()) } + } +} + +impl Drop for Shader +{ + fn drop(&mut self) + { + unsafe { + gl::DeleteShader(self.shader); + } + } +} + +/// Shader kind. +#[derive(Debug, Clone, Copy)] +pub enum Kind +{ + Vertex, + Fragment, +} + +impl Kind +{ + fn into_gl(self) -> gl::types::GLenum + { + match self { + Self::Vertex => gl::VERTEX_SHADER, + Self::Fragment => gl::FRAGMENT_SHADER, + } + } +} + +/// Shader program +#[derive(Debug)] +pub struct Program +{ + program: gl::types::GLuint, +} + +impl Program +{ + pub fn new() -> Self + { + let program = unsafe { gl::CreateProgram() }; + + Self { program } + } + + pub fn attach(&self, shader: &Shader) + { + unsafe { + gl::AttachShader(self.program, shader.shader); + } + } + + pub fn link(&self) -> Result<(), Error> + { + unsafe { + gl::LinkProgram(self.program); + } + + let mut link_success = gl::types::GLint::default(); + + unsafe { + gl::GetProgramiv(self.program, gl::LINK_STATUS, &mut link_success); + } + + if link_success == 0 { + let info_log = self.get_info_log(); + + return Err(Error::CompileFailed(info_log)); + } + + Ok(()) + } + + pub fn activate(&self) + { + unsafe { + gl::UseProgram(self.program); + } + } + + fn get_info_log(&self) -> String + { + let mut buf = vec![gl::types::GLchar::default(); 512]; + + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::GetProgramInfoLog( + self.program, + buf.len() as gl::types::GLsizei, + 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()) } + } +} + +impl Drop for Program +{ + fn drop(&mut self) + { + unsafe { + gl::DeleteProgram(self.program); + } + } +} + +/// Shader error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("All characters in source are not within the ASCII range")] + SourceNotAscii, + + #[error("Failed to compile: {0}")] + CompileFailed(String), +} diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs new file mode 100644 index 0000000..f255b8a --- /dev/null +++ b/engine/src/opengl/vertex_array.rs @@ -0,0 +1,131 @@ +use std::mem::size_of; + +use crate::opengl::currently_bound::CurrentlyBound; +use crate::opengl::vertex_buffer::VertexBuffer; +use crate::vertex::{Attribute, AttributeComponentType, Vertex}; + +const VERTEX_STRIDE: usize = size_of::<Vertex>(); + +#[derive(Debug)] +pub struct VertexArray +{ + array: gl::types::GLuint, +} + +impl VertexArray +{ + pub fn new() -> Self + { + let mut array = 0; + + unsafe { + gl::GenVertexArrays(1, &mut array); + } + + Self { array } + } + + /// Draws the currently bound vertex array. + pub fn draw( + _currently_bound: &CurrentlyBound<Self>, + primitive_kind: PrimitiveKind, + start_index: u32, + index_cnt: u32, + ) + { + unsafe { + #[allow(clippy::cast_possible_wrap)] + gl::DrawArrays( + primitive_kind.into_gl(), + start_index as gl::types::GLint, + index_cnt as gl::types::GLsizei, + ); + } + } + + pub fn configure_attrs( + _currently_bound: &CurrentlyBound<Self>, + _vert_buf_curr_bound: &CurrentlyBound<VertexBuffer>, + ) + { + let mut offset = 0; + + for attr in Vertex::attrs() { + Self::vertex_attrib_ptr(attr, offset); + + Self::enable_vertex_attrib_array(attr.index); + + offset += attr.component_size * attr.component_cnt as usize; + } + } + + #[allow(clippy::inline_always)] + #[inline(always)] + pub fn bind(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) + { + unsafe { gl::BindVertexArray(self.array) } + + // SAFETY: A vertex array object is currently bound + let currently_bound = unsafe { CurrentlyBound::new() }; + + cb(currently_bound); + } + + fn vertex_attrib_ptr(vertex_attr: &Attribute, offset: usize) + { + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::VertexAttribPointer( + vertex_attr.index as gl::types::GLuint, + vertex_attr.component_cnt as i32, + Self::vertex_attr_type_to_gl(&vertex_attr.component_type), + gl::FALSE, + VERTEX_STRIDE as gl::types::GLsizei, + offset as *const _, + ); + } + } + + fn enable_vertex_attrib_array(index: usize) + { + unsafe { + #[allow(clippy::cast_possible_truncation)] + gl::EnableVertexAttribArray(index as gl::types::GLuint); + } + } + + fn vertex_attr_type_to_gl( + vertex_attr_type: &AttributeComponentType, + ) -> gl::types::GLenum + { + match vertex_attr_type { + AttributeComponentType::Float => gl::FLOAT, + } + } +} + +impl Drop for VertexArray +{ + fn drop(&mut self) + { + unsafe { + gl::DeleteVertexArrays(1, &self.array); + } + } +} + +#[derive(Debug)] +pub enum PrimitiveKind +{ + Triangles, +} + +impl PrimitiveKind +{ + fn into_gl(self) -> gl::types::GLenum + { + match self { + Self::Triangles => gl::TRIANGLES, + } + } +} diff --git a/engine/src/opengl/vertex_buffer.rs b/engine/src/opengl/vertex_buffer.rs new file mode 100644 index 0000000..0050a9f --- /dev/null +++ b/engine/src/opengl/vertex_buffer.rs @@ -0,0 +1,93 @@ +use std::mem::size_of_val; + +use crate::opengl::currently_bound::CurrentlyBound; +use crate::vertex::Vertex; + +#[derive(Debug)] +pub struct VertexBuffer +{ + buffer: gl::types::GLuint, +} + +impl VertexBuffer +{ + pub fn new() -> Self + { + let mut buffer = gl::types::GLuint::default(); + + unsafe { + gl::GenBuffers(1, &mut buffer); + }; + + Self { buffer } + } + + #[allow(clippy::inline_always)] + #[inline(always)] + pub fn bind(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) + { + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, self.buffer); + } + + // SAFETY: A vertex array object is currently bound + let currently_bound = unsafe { CurrentlyBound::new() }; + + cb(currently_bound); + } + + /// Stores vertices in the currently bound vertex bound. + pub fn store( + _currently_bound: &CurrentlyBound<Self>, + vertices: &[Vertex], + usage: BufferUsage, + ) + { + unsafe { + #[allow(clippy::cast_possible_wrap)] + gl::BufferData( + gl::ARRAY_BUFFER, + size_of_val(vertices) as gl::types::GLsizeiptr, + vertices.as_ptr().cast(), + usage.into_gl(), + ); + } + } +} + +impl Drop for VertexBuffer +{ + fn drop(&mut self) + { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + unsafe { + gl::DeleteBuffers(1, &self.buffer); + } + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum BufferUsage +{ + /// 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 BufferUsage +{ + fn into_gl(self) -> gl::types::GLenum + { + match self { + Self::Stream => gl::STREAM_DRAW, + Self::Static => gl::STATIC_DRAW, + Self::Dynamic => gl::DYNAMIC_DRAW, + } + } +} |