summaryrefslogtreecommitdiff
path: root/engine/src/opengl
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-10-13 23:32:00 +0200
committerHampusM <hampus@hampusmat.com>2023-10-13 23:32:00 +0200
commit12f48046b2606fc77a1312a6d5e5fc7ff9feff88 (patch)
tree644f4abd6785a5f0c066c7fbadcfc5e820a41ebf /engine/src/opengl
parentcfa73b1ea42fa491ff9e00bb5efb5e5a5d860578 (diff)
refactor(engine): move uses of OpenGL to OpenGL module
Diffstat (limited to 'engine/src/opengl')
-rw-r--r--engine/src/opengl/currently_bound.rs22
-rw-r--r--engine/src/opengl/mod.rs38
-rw-r--r--engine/src/opengl/shader.rs198
-rw-r--r--engine/src/opengl/vertex_array.rs131
-rw-r--r--engine/src/opengl/vertex_buffer.rs93
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,
+ }
+ }
+}