summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2024-02-19 23:06:17 +0100
committerHampusM <hampus@hampusmat.com>2024-02-19 23:06:17 +0100
commit88d6ae3e4854c5fb4b37f75a29aba4f13dcfb382 (patch)
tree033431187e1f6fc4aa4f36a7f46490af5ef338ce
parente8c6c096b2068f4ea71b021bf02f56d266ed671c (diff)
refactor(engine): create shaders on start
-rw-r--r--engine/src/lib.rs8
-rw-r--r--engine/src/object.rs116
-rw-r--r--engine/src/opengl/shader.rs11
-rw-r--r--engine/src/renderer/mod.rs102
-rw-r--r--engine/src/shader.rs156
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,
+}