From 715bbaf459b88575e11d90ec16bad3841bafd259 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sat, 7 Oct 2023 20:52:09 +0200 Subject: feat(engine): add ability to render triangles --- engine/fragment.glsl | 7 ++ engine/src/lib.rs | 7 +- engine/src/object.rs | 69 ++++++++++++ engine/src/renderer/mod.rs | 79 +++++++++++++- engine/src/renderer/vertex_array.rs | 62 +++++++++++ engine/src/renderer/vertex_buffers.rs | 83 ++++++++++++++ engine/src/shader.rs | 198 ++++++++++++++++++++++++++++++++++ engine/vertex.glsl | 7 ++ 8 files changed, 507 insertions(+), 5 deletions(-) create mode 100644 engine/fragment.glsl create mode 100644 engine/src/object.rs create mode 100644 engine/src/renderer/vertex_array.rs create mode 100644 engine/src/renderer/vertex_buffers.rs create mode 100644 engine/src/shader.rs create mode 100644 engine/vertex.glsl (limited to 'engine') diff --git a/engine/fragment.glsl b/engine/fragment.glsl new file mode 100644 index 0000000..0c56a32 --- /dev/null +++ b/engine/fragment.glsl @@ -0,0 +1,7 @@ +#version 330 core +out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ea74fd1..28cd2da 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -3,10 +3,13 @@ use glfw::Window; pub use glfw::WindowSize; +use crate::object::Object; use crate::vector::Vec2; mod renderer; +mod shader; +pub mod object; pub mod vector; pub struct Engine @@ -42,12 +45,12 @@ impl Engine /// /// # Errors /// Will return `Err` if updating the window fails. - pub fn start(&self, mut func: impl FnMut()) -> Result<(), Error> + pub fn start(&self, objects: &[Object], mut func: impl FnMut()) -> Result<(), Error> { while !self.window.should_close() { func(); - crate::renderer::render(); + crate::renderer::render(objects.iter().map(Object::renderable)); self.window .swap_buffers() diff --git a/engine/src/object.rs b/engine/src/object.rs new file mode 100644 index 0000000..ff84127 --- /dev/null +++ b/engine/src/object.rs @@ -0,0 +1,69 @@ +use crate::renderer::Renderable; +use crate::shader::{Kind as ShaderKind, Program as ShaderProgram, Shader}; + +pub struct Object +{ + renderable: Renderable, +} + +impl Object +{ + /// Returns a new `Object`. + /// + /// # Errors + /// Will return `Err` if shader creation fails or if shader program linking fails. + pub fn new(vertices: &[f32]) -> Result + { + let vertex_shader = Shader::new(ShaderKind::Vertex); + + vertex_shader + .set_source(include_str!("../vertex.glsl")) + .map_err(Error::CreateVertexShaderFailed)?; + + vertex_shader + .compile() + .map_err(Error::CreateVertexShaderFailed)?; + + let fragment_shader = Shader::new(ShaderKind::Fragment); + + fragment_shader + .set_source(include_str!("../fragment.glsl")) + .map_err(Error::CreateFragmentShaderFailed)?; + + fragment_shader + .compile() + .map_err(Error::CreateFragmentShaderFailed)?; + + let shader_program = ShaderProgram::new(); + + shader_program.attach(&vertex_shader); + shader_program.attach(&fragment_shader); + + shader_program + .link() + .map_err(Error::LinkShaderProgramFailed)?; + + let renderable = Renderable::new(shader_program, vertices); + + Ok(Self { renderable }) + } + + pub(crate) fn renderable(&self) -> &Renderable + { + &self.renderable + } +} + +/// Object error +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to create vertex shader")] + CreateVertexShaderFailed(#[source] crate::shader::Error), + + #[error("Failed to create fragment shader")] + CreateFragmentShaderFailed(#[source] crate::shader::Error), + + #[error("Failed to link shader program")] + LinkShaderProgramFailed(#[source] crate::shader::Error), +} diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index 9cd379d..3c42f93 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -1,10 +1,17 @@ -use std::ffi::{c_void, CString}; +use std::ffi::{c_float, c_void, CString}; +use std::mem::size_of; use std::process::abort; +use std::ptr::null; use glfw::WindowSize; +use crate::renderer::vertex_array::{PrimitiveKind, VertexArray}; +use crate::renderer::vertex_buffers::{BufferUsage, VertexBuffers}; use crate::vector::Vec2; +mod vertex_array; +mod vertex_buffers; + pub fn initialize(window: &glfw::Window) -> Result<(), Error> { gl::load_with(|symbol| { @@ -30,10 +37,51 @@ pub fn initialize(window: &glfw::Window) -> Result<(), Error> Ok(()) } -pub fn render() +pub fn render<'renderable>(renderables: impl IntoIterator) { unsafe { - gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + + for renderable in renderables { + renderable.shader_program.activate(); + + renderable.vertex_arr.draw(PrimitiveKind::Triangles, 0, 3); + } +} + +pub struct Renderable +{ + shader_program: crate::shader::Program, + vertex_arr: VertexArray, +} + +impl Renderable +{ + pub fn new(shader_program: crate::shader::Program, vertices: &[f32]) -> Self + { + let vertex_arr = VertexArray::new(); + + vertex_arr.bind(); + + let vertex_buffer = VertexBuffers::<1>::new(); + + vertex_buffer + .store(0, vertices, BufferUsage::Static) + .unwrap(); + + vertex_attrib_ptr(0); + enable_vertex_attrib_array(0); + + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + } + + Self { + shader_program, + vertex_arr, + } } } @@ -57,3 +105,28 @@ pub enum Error #[error("Failed to get window size")] GetWindowSizeFailed(#[source] glfw::Error), } + +fn vertex_attrib_ptr(index: usize) +{ + let stride = 3 * size_of::(); + + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::VertexAttribPointer( + index as gl::types::GLuint, + 3, + gl::FLOAT, + gl::FALSE, + stride as gl::types::GLsizei, + null(), + ); + } +} + +fn enable_vertex_attrib_array(index: usize) +{ + unsafe { + #[allow(clippy::cast_possible_truncation)] + gl::EnableVertexAttribArray(index as gl::types::GLuint); + } +} diff --git a/engine/src/renderer/vertex_array.rs b/engine/src/renderer/vertex_array.rs new file mode 100644 index 0000000..e54f638 --- /dev/null +++ b/engine/src/renderer/vertex_array.rs @@ -0,0 +1,62 @@ +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 } + } + + pub fn draw(&self, primitive_kind: PrimitiveKind, start_index: u32, index_cnt: u32) + { + self.bind(); + + 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 bind(&self) + { + unsafe { gl::BindVertexArray(self.array) } + } +} + +impl Drop for VertexArray +{ + fn drop(&mut self) + { + unsafe { + gl::DeleteVertexArrays(1, &self.array); + } + } +} + +pub enum PrimitiveKind +{ + Triangles, +} + +impl PrimitiveKind +{ + fn into_gl(self) -> gl::types::GLenum + { + match self { + Self::Triangles => gl::TRIANGLES, + } + } +} diff --git a/engine/src/renderer/vertex_buffers.rs b/engine/src/renderer/vertex_buffers.rs new file mode 100644 index 0000000..a6d1a2c --- /dev/null +++ b/engine/src/renderer/vertex_buffers.rs @@ -0,0 +1,83 @@ +use std::mem::size_of_val; + +pub struct VertexBuffers +{ + buffers: [gl::types::GLuint; CNT], +} + +impl VertexBuffers +{ + pub fn new() -> Self + { + let mut buffers = [gl::types::GLuint::default(); CNT]; + + unsafe { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + gl::GenBuffers(CNT as gl::types::GLsizei, buffers.as_mut_ptr()); + }; + + Self { buffers } + } + + pub fn store( + &self, + buffer_index: usize, + vertices: &[f32], + usage: BufferUsage, + ) -> Option<()> + { + let buffer = *self.buffers.get(buffer_index)?; + + unsafe { + gl::BindBuffer(gl::ARRAY_BUFFER, buffer); + } + + 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(), + ); + } + + Some(()) + } +} + +impl Drop for VertexBuffers +{ + fn drop(&mut self) + { + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + unsafe { + gl::DeleteBuffers(CNT as gl::types::GLsizei, self.buffers.as_ptr()); + } + } +} + +#[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, + } + } +} diff --git a/engine/src/shader.rs b/engine/src/shader.rs new file mode 100644 index 0000000..0bbca77 --- /dev/null +++ b/engine/src/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/vertex.glsl b/engine/vertex.glsl new file mode 100644 index 0000000..c74ea10 --- /dev/null +++ b/engine/vertex.glsl @@ -0,0 +1,7 @@ +#version 330 core +layout (location = 0) in vec3 aPos; + +void main() +{ + gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); +} -- cgit v1.2.3-18-g5258