summaryrefslogtreecommitdiff
path: root/engine/src/shader.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/shader.rs')
-rw-r--r--engine/src/shader.rs156
1 files changed, 156 insertions, 0 deletions
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,
+}