use std::collections::hash_map::DefaultHasher; use std::fs::read_to_string; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use ecs::Component; use crate::opengl::glsl::{ preprocess as glsl_preprocess, PreprocessingError as GlslPreprocessingError, }; const VERTEX_SHADER_FILE: &str = "vertex.glsl"; const FRAGMENT_SHADER_FILE: &str = "fragment.glsl"; const SHADER_DIR: &str = "engine"; /// Shader program #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Component)] pub struct Program { shaders: Vec, } impl Program { /// Creates a new shader program with the default shaders. /// /// # Errors /// Returns `Err` if: /// - Reading a default shader file Fails /// - Preprocessing a shader fails. pub fn new() -> Result { let mut program = Self { shaders: Vec::new() }; program.push_shader( Shader::read_shader_file( Kind::Vertex, &Path::new(SHADER_DIR).join(VERTEX_SHADER_FILE), )? .preprocess()?, ); program.push_shader( Shader::read_shader_file( Kind::Fragment, &Path::new(SHADER_DIR).join(FRAGMENT_SHADER_FILE), )? .preprocess()?, ); Ok(program) } pub fn push_shader(&mut self, shader: Shader) { self.shaders.push(shader); } pub fn append_shaders(&mut self, shaders: impl IntoIterator) { 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 { 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 { let parent_dir = self .file .parent() .ok_or(Error::SourcePathHasNoParent)? .to_path_buf(); let source_preprocessed = glsl_preprocess(self.source, &|path| std::fs::read(parent_dir.join(path))) .map_err(|err| Error::PreprocessFailed { source: err, shader_file: self.file.clone(), })?; Ok(Self { kind: self.kind, source: source_preprocessed.into_owned(), 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: GlslPreprocessingError, shader_file: PathBuf, }, #[error("Shader source path has no parent")] SourcePathHasNoParent, }