diff options
Diffstat (limited to 'engine')
-rw-r--r-- | engine/src/lib.rs | 8 | ||||
-rw-r--r-- | engine/src/object.rs | 116 | ||||
-rw-r--r-- | engine/src/opengl/shader.rs | 11 | ||||
-rw-r--r-- | engine/src/renderer/mod.rs | 102 | ||||
-rw-r--r-- | engine/src/shader.rs | 156 |
5 files changed, 272 insertions, 121 deletions
diff --git a/engine/src/lib.rs b/engine/src/lib.rs index fada83f..a1d9e2f 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -25,6 +25,7 @@ pub mod material; pub mod math; pub mod mesh; pub mod object; +pub mod shader; pub mod texture; pub mod vertex; @@ -140,6 +141,10 @@ where /// Will return `Err` if updating the window fails. pub fn start(&mut self, mut func: impl FnMut(&mut Self)) -> Result<(), Error> { + self.renderer + .create_shader_programs(self.objects.values().map(Object::shader)) + .map_err(Error::ShaderCreationFailed)?; + let mut prev_frame_start: Option<Instant> = None; while !self.window.should_close() { @@ -288,6 +293,9 @@ pub enum Error #[error("Rendering failed")] RenderingFailed(#[source] renderer::Error), + + #[error("Failed to create shaders")] + ShaderCreationFailed(#[source] renderer::Error), } #[derive(Debug)] diff --git a/engine/src/object.rs b/engine/src/object.rs index 5391cf5..0fd049c 100644 --- a/engine/src/object.rs +++ b/engine/src/object.rs @@ -1,27 +1,24 @@ use std::collections::HashMap; -use std::fs::read_to_string; -use std::io::Error as IoError; -use std::path::{Path, PathBuf}; +use std::path::Path; use crate::material::Material; use crate::mesh::Mesh; -use crate::opengl::shader::{ +use crate::shader::{ Error as ShaderError, Kind as ShaderKind, Program as ShaderProgram, Shader, }; -use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; use crate::texture::{Id as TextureId, Texture}; use crate::transform::Transform; use crate::vector::Vec3; use crate::vertex::Vertex; -const SHADER_DIR: &str = "engine"; - const VERTEX_SHADER_FILE: &str = "vertex.glsl"; const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; +const SHADER_DIR: &str = "engine"; + #[derive(Debug)] pub struct Object { @@ -162,58 +159,23 @@ impl Builder /// - Linking the shader program fails pub fn build(self, id: Id) -> Result<Object, Error> { - let vertex_shader_file = Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE); - - let fragment_shader_file = Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE); - - let vertex_shader_content = - read_to_string(&vertex_shader_file).map_err(|err| { - Error::ReadShaderFailed { - source: err, - shader_file: vertex_shader_file.clone(), - } - })?; - - let fragment_shader_content = - read_to_string(&fragment_shader_file).map_err(|err| { - Error::ReadShaderFailed { - source: err, - shader_file: fragment_shader_file.clone(), - } - })?; - - let shader_preprocessor = ShaderPreprocessor::new(PathBuf::from(SHADER_DIR)); - - let vertex_shader_content_preprocessed = shader_preprocessor - .preprocess(vertex_shader_content, &vertex_shader_file) - .map_err(|err| Error::PreprocessShaderFailed { - source: err, - shader_file: vertex_shader_file, - })?; - - let frag_shader_content_preprocessed = shader_preprocessor - .preprocess(fragment_shader_content, &fragment_shader_file) - .map_err(|err| Error::PreprocessShaderFailed { - source: err, - shader_file: fragment_shader_file, - })?; - - let vertex_shader = - Self::create_shader(ShaderKind::Vertex, &vertex_shader_content_preprocessed) - .map_err(Error::CreateVertexShaderFailed)?; - - let fragment_shader = - Self::create_shader(ShaderKind::Fragment, &frag_shader_content_preprocessed) - .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 mut shader_program = ShaderProgram::new(); + + shader_program.push_shader( + Shader::read_shader_file( + ShaderKind::Vertex, + &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE), + )? + .preprocess()?, + ); + + shader_program.push_shader( + Shader::read_shader_file( + ShaderKind::Fragment, + &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE), + )? + .preprocess()?, + ); let mesh = Mesh::new(self.vertices, self.indices); @@ -228,16 +190,6 @@ impl Builder material: self.material.ok_or(Error::NoMaterial)?, }) } - - fn create_shader(kind: ShaderKind, source: &str) -> Result<Shader, ShaderError> - { - let shader = Shader::new(kind); - - shader.set_source(source)?; - shader.compile()?; - - Ok(shader) - } } impl Default for Builder @@ -252,30 +204,8 @@ impl Default for Builder #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Failed to create vertex shader")] - CreateVertexShaderFailed(#[source] ShaderError), - - #[error("Failed to create fragment shader")] - CreateFragmentShaderFailed(#[source] ShaderError), - - #[error("Failed to link shader program")] - LinkShaderProgramFailed(#[source] ShaderError), - - #[error("Failed to read shader {}", shader_file.display())] - ReadShaderFailed - { - #[source] - source: IoError, - shader_file: PathBuf, - }, - - #[error("Failed to preprocess shader {}", shader_file.display())] - PreprocessShaderFailed - { - #[source] - source: ShaderPreprocessorError, - shader_file: PathBuf, - }, + #[error(transparent)] + ShaderError(#[from] ShaderError), #[error("Object builder has no material")] NoMaterial, diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs index 224a9bc..2312adc 100644 --- a/engine/src/opengl/shader.rs +++ b/engine/src/opengl/shader.rs @@ -3,6 +3,7 @@ use std::ptr::null_mut; use crate::matrix::Matrix; use crate::opengl::currently_bound::CurrentlyBound; +use crate::shader::Kind; use crate::vector::Vec3; #[derive(Debug)] @@ -90,14 +91,6 @@ impl Drop for Shader } } -/// Shader kind. -#[derive(Debug, Clone, Copy)] -pub enum Kind -{ - Vertex, - Fragment, -} - impl Kind { fn into_gl(self) -> gl::types::GLenum @@ -110,7 +103,7 @@ impl Kind } /// Shader program -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct Program { program: gl::types::GLuint, diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index c72e1ce..64d5e3c 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::ffi::{c_void, CString}; use std::process::abort; @@ -18,11 +19,16 @@ use crate::opengl::buffer::{ use crate::opengl::currently_bound::CurrentlyBound; #[cfg(feature = "debug")] use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; -use crate::opengl::shader::Program as ShaderProgram; +use crate::opengl::shader::{ + Error as GlShaderError, + Program as GlShaderProgram, + Shader as GlShader, +}; use crate::opengl::texture::{set_active_texture_unit, TextureUnit}; use crate::opengl::vertex_array::{PrimitiveKind, VertexArray}; use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; use crate::projection::new_perspective; +use crate::shader::Program as ShaderProgram; use crate::vector::{Vec2, Vec3}; use crate::vertex::Vertex; @@ -30,6 +36,7 @@ use crate::vertex::Vertex; pub struct Renderer<CameraT> { camera: CameraT, + shader_programs: HashMap<u64, GlShaderProgram>, } impl<CameraT> Renderer<CameraT> @@ -63,7 +70,44 @@ where enable(Capability::DepthTest); - Ok(Self { camera }) + Ok(Self { + camera, + shader_programs: HashMap::new(), + }) + } + + pub fn create_shader_programs<'program>( + &mut self, + programs: impl IntoIterator<Item = &'program ShaderProgram>, + ) -> Result<(), Error> + { + for program in programs { + let gl_shaders = program + .shaders() + .iter() + .map(|shader| { + let gl_shader = GlShader::new(shader.kind()); + + gl_shader.set_source(shader.source())?; + gl_shader.compile()?; + + Ok::<_, Error>(gl_shader) + }) + .collect::<Result<Vec<_>, _>>()?; + + let gl_shader_program = GlShaderProgram::new(); + + for gl_shader in &gl_shaders { + gl_shader_program.attach(gl_shader); + } + + gl_shader_program.link()?; + + self.shader_programs + .insert(program.u64_hash(), gl_shader_program); + } + + Ok(()) } pub fn render<'obj>( @@ -76,15 +120,27 @@ where clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for obj in objects { - obj.shader().activate(|shader_program_curr_bound| { + let shader_program = self + .shader_programs + .get(&obj.shader().u64_hash()) + .ok_or(Error::MissingShaderProgram)?; + + shader_program.activate(|shader_program_curr_bound| { apply_transformation_matrices( obj, + shader_program, &self.camera, window_size, &shader_program_curr_bound, ); - apply_light(obj, light_source, &self.camera, &shader_program_curr_bound); + apply_light( + obj, + shader_program, + light_source, + &self.camera, + &shader_program_curr_bound, + ); for (texture_id, texture) in obj.textures() { let texture_unit = TextureUnit::from_texture_id(*texture_id) @@ -97,7 +153,7 @@ where Self::draw_object(obj); - Ok(()) + Ok::<_, Error>(()) })?; } @@ -220,16 +276,23 @@ pub enum Error #[error("Texture ID is a invalid texture unit")] TextureIdIsInvalidTextureUnit, + + #[error(transparent)] + GlShader(#[from] GlShaderError), + + #[error("No shader program object was found for object")] + MissingShaderProgram, } fn apply_transformation_matrices( object: &Object, + gl_shader_program: &GlShaderProgram, camera: &impl Camera, window_size: &WindowSize, - shader_program_curr_bound: &CurrentlyBound<ShaderProgram>, + shader_program_curr_bound: &CurrentlyBound<GlShaderProgram>, ) { - object.shader().set_uniform_matrix_4fv( + gl_shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("model"), &object.transform().as_matrix(), @@ -237,7 +300,7 @@ fn apply_transformation_matrices( let view = create_view(camera); - object.shader().set_uniform_matrix_4fv( + gl_shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("view"), &view, @@ -251,7 +314,7 @@ fn apply_transformation_matrices( 0.1, ); - object.shader().set_uniform_matrix_4fv( + gl_shader_program.set_uniform_matrix_4fv( shader_program_curr_bound, cstr!("projection"), &projection, @@ -260,12 +323,13 @@ fn apply_transformation_matrices( fn apply_light( obj: &Object, + gl_shader_program: &GlShaderProgram, light_source: Option<&LightSource>, camera: &impl Camera, - shader_program_curr_bound: &CurrentlyBound<ShaderProgram>, + shader_program_curr_bound: &CurrentlyBound<GlShaderProgram>, ) { - obj.shader().set_uniform_vec_3fv( + gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light.position"), &light_source.map_or_else(Vec3::default, |light_source| { @@ -273,7 +337,7 @@ fn apply_light( }), ); - obj.shader().set_uniform_vec_3fv( + gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light.ambient"), &light_source @@ -283,7 +347,7 @@ fn apply_light( .into(), ); - obj.shader().set_uniform_vec_3fv( + gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light.diffuse"), &light_source @@ -293,7 +357,7 @@ fn apply_light( .into(), ); - obj.shader().set_uniform_vec_3fv( + gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("light.specular"), &light_source @@ -304,33 +368,33 @@ fn apply_light( ); #[allow(clippy::cast_possible_wrap)] - obj.shader().set_uniform_1i( + gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.ambient"), obj.material().ambient_map().into_inner() as i32, ); #[allow(clippy::cast_possible_wrap)] - obj.shader().set_uniform_1i( + gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.diffuse"), obj.material().diffuse_map().into_inner() as i32, ); #[allow(clippy::cast_possible_wrap)] - obj.shader().set_uniform_1i( + gl_shader_program.set_uniform_1i( shader_program_curr_bound, cstr!("material.specular"), obj.material().specular_map().into_inner() as i32, ); - obj.shader().set_uniform_1fv( + gl_shader_program.set_uniform_1fv( shader_program_curr_bound, cstr!("material.shininess"), obj.material().shininess(), ); - obj.shader().set_uniform_vec_3fv( + gl_shader_program.set_uniform_vec_3fv( shader_program_curr_bound, cstr!("view_pos"), &camera.position(), diff --git a/engine/src/shader.rs b/engine/src/shader.rs new file mode 100644 index 0000000..428e5a8 --- /dev/null +++ b/engine/src/shader.rs @@ -0,0 +1,156 @@ +use std::collections::hash_map::DefaultHasher; +use std::fs::read_to_string; +use std::hash::{Hash, Hasher}; +use std::path::{Path, PathBuf}; + +use crate::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; + +/// Shader program +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct Program +{ + shaders: Vec<Shader>, +} + +impl Program +{ + #[must_use] + pub fn new() -> Self + { + Self { shaders: Vec::new() } + } + + pub fn push_shader(&mut self, shader: Shader) + { + self.shaders.push(shader); + } + + pub fn append_shaders(&mut self, shaders: impl IntoIterator<Item = Shader>) + { + self.shaders.extend(shaders); + } + + #[must_use] + pub fn shaders(&self) -> &[Shader] + { + &self.shaders + } + + pub(crate) fn u64_hash(&self) -> u64 + { + let mut hasher = DefaultHasher::new(); + + self.hash(&mut hasher); + + hasher.finish() + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Shader +{ + kind: Kind, + source: String, + file: PathBuf, +} + +impl Shader +{ + /// Reads a shader from the specified source file. + /// + /// # Errors + /// Will return `Err` if: + /// - Reading the file fails + /// - The shader source is not ASCII + pub fn read_shader_file(kind: Kind, shader_file: &Path) -> Result<Self, Error> + { + let source = read_to_string(shader_file).map_err(|err| Error::ReadFailed { + source: err, + shader_file: shader_file.to_path_buf(), + })?; + + if !source.is_ascii() { + return Err(Error::SourceNotAscii); + } + + Ok(Self { + kind, + source, + file: shader_file.to_path_buf(), + }) + } + + /// Preprocesses the shaders. + /// + /// # Errors + /// Returns `Err` if preprocessing fails. + pub fn preprocess(self) -> Result<Self, Error> + { + let shader_preprocessor = ShaderPreprocessor::new( + self.file + .parent() + .ok_or(Error::SourcePathHasNoParent)? + .to_path_buf(), + ); + + let source_preprocessed = shader_preprocessor + .preprocess(self.source, &self.file) + .map_err(|err| Error::PreprocessFailed { + source: err, + shader_file: self.file.clone(), + })?; + + Ok(Self { + kind: self.kind, + source: source_preprocessed, + file: self.file.clone(), + }) + } + + #[must_use] + pub fn kind(&self) -> Kind + { + self.kind + } + + #[must_use] + pub fn source(&self) -> &str + { + &self.source + } +} + +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Kind +{ + Vertex, + Fragment, +} + +/// Shader error +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to read shader {}", shader_file.display())] + ReadFailed + { + #[source] + source: std::io::Error, + shader_file: PathBuf, + }, + + #[error("Shader source is not ASCII")] + SourceNotAscii, + + #[error("Failed to preprocess shader {}", shader_file.display())] + PreprocessFailed + { + #[source] + source: ShaderPreprocessorError, + shader_file: PathBuf, + }, + + #[error("Shader source path has no parent")] + SourcePathHasNoParent, +} |