diff options
Diffstat (limited to 'engine/src')
-rw-r--r-- | engine/src/camera/fly.rs | 4 | ||||
-rw-r--r-- | engine/src/file_format/wavefront/mtl.rs | 11 | ||||
-rw-r--r-- | engine/src/input.rs | 7 | ||||
-rw-r--r-- | engine/src/lib.rs | 2 | ||||
-rw-r--r-- | engine/src/opengl/glsl.rs (renamed from engine/src/shader_preprocessor.rs) | 535 | ||||
-rw-r--r-- | engine/src/opengl/mod.rs | 23 | ||||
-rw-r--r-- | engine/src/opengl/shader.rs | 17 | ||||
-rw-r--r-- | engine/src/opengl/texture.rs | 6 | ||||
-rw-r--r-- | engine/src/performance.rs | 16 | ||||
-rw-r--r-- | engine/src/renderer/opengl.rs | 159 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/fragment.glsl | 73 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/light.glsl | 133 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/vertex.glsl | 24 | ||||
-rw-r--r-- | engine/src/renderer/opengl/glsl/vertex_data.glsl | 11 | ||||
-rw-r--r-- | engine/src/shader.rs | 186 | ||||
-rw-r--r-- | engine/src/texture.rs | 5 | ||||
-rw-r--r-- | engine/src/window.rs | 402 |
17 files changed, 1030 insertions, 584 deletions
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index b6ba7aa..1333360 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -2,12 +2,11 @@ use ecs::component::local::Local; use ecs::sole::Single; use ecs::system::{Into, System}; use ecs::{Component, Query}; -use glfw::window::{Key, KeyState}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; use crate::event::Update as UpdateEvent; -use crate::input::{Cursor, CursorFlags, Keys}; +use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::Position; use crate::util::builder; use crate::vector::{Vec2, Vec3}; @@ -87,7 +86,6 @@ fn update( { for (mut camera, mut camera_pos, mut fly_camera, _) in &camera_query { if cursor.has_moved && cursor_flags.is_first_move.flag { - #[cfg(feature = "debug")] tracing::debug!("First cursor move"); cursor_state.last_pos = cursor.position; diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index ef6e894..d90dbcf 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -44,7 +44,6 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error> .filter(|(_, statement)| matches!(statement.keyword, Keyword::Newmtl)) .count(); - #[cfg(feature = "debug")] tracing::debug!("Material count: {material_cnt}"); statements_to_materials(statements, material_cnt) @@ -93,7 +92,7 @@ pub enum Error }, } -#[cfg_attr(feature = "debug", tracing::instrument(skip_all))] +#[tracing::instrument(skip_all)] fn statements_to_materials( statements: impl IntoIterator<Item = (usize, Statement<Keyword>)>, material_cnt: usize, @@ -110,7 +109,6 @@ fn statements_to_materials( for (line_no, statement) in statements { if statement.keyword == Keyword::Newmtl { if curr_material.ready { - #[cfg(feature = "debug")] tracing::debug!("Building material"); let material = curr_material.material_builder.clone().build(); @@ -135,7 +133,6 @@ fn statements_to_materials( Keyword::Ka => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding ambient color"); curr_material.material_builder = @@ -144,7 +141,6 @@ fn statements_to_materials( Keyword::Kd => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding diffuse color"); curr_material.material_builder = @@ -153,7 +149,6 @@ fn statements_to_materials( Keyword::Ks => { let color = get_color_from_statement(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding specular color"); curr_material.material_builder = @@ -172,7 +167,6 @@ fn statements_to_materials( let texture = Texture::open(Path::new(texture_file_path))?; - #[cfg(feature = "debug")] tracing::debug!("Adding ambient map"); let texture_id = texture.id(); @@ -185,7 +179,6 @@ fn statements_to_materials( Keyword::MapKd => { let texture = get_map_from_texture(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding diffuse map"); let texture_id = texture.id(); @@ -198,7 +191,6 @@ fn statements_to_materials( Keyword::MapKs => { let texture = get_map_from_texture(&statement, line_no)?; - #[cfg(feature = "debug")] tracing::debug!("Adding specular map"); let texture_id = texture.id(); @@ -213,7 +205,6 @@ fn statements_to_materials( } if curr_material.ready { - #[cfg(feature = "debug")] tracing::debug!("Building last material"); let material = curr_material.material_builder.build(); diff --git a/engine/src/input.rs b/engine/src/input.rs index f4166f6..e847702 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -68,10 +68,6 @@ impl Keys pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState) { - if matches!(new_key_state, KeyState::Repeat) { - return; - } - let Some(key_data) = self.map.get_mut(&key) else { unreachable!(); }; @@ -194,7 +190,6 @@ fn initialize( let cursor_flags_weak_ref = cursor_flags.to_weak_ref(); window.set_focus_callback(move |is_focused| { - #[cfg(feature = "debug")] tracing::trace!("Window is focused: {is_focused}"); let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world"); @@ -209,7 +204,6 @@ fn maybe_clear_cursor_is_first_move( ) { if cursor_flags.is_first_move.pending_clear { - #[cfg(feature = "debug")] tracing::trace!("Clearing is_first_move"); // This flag was set for the whole previous tick so it can be cleared now @@ -219,7 +213,6 @@ fn maybe_clear_cursor_is_first_move( } if cursor.has_moved && cursor_flags.is_first_move.flag { - #[cfg(feature = "debug")] tracing::trace!("Setting flag to clear is_first_move next tick"); // Make this system clear is_first_move the next time it runs diff --git a/engine/src/lib.rs b/engine/src/lib.rs index abf26f5..07b2f8d 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -21,7 +21,6 @@ use crate::event::{ }; mod opengl; -mod shader_preprocessor; mod util; pub mod camera; @@ -38,7 +37,6 @@ pub mod mesh; pub mod performance; pub mod projection; pub mod renderer; -pub mod shader; pub mod texture; pub mod transform; pub mod vertex; diff --git a/engine/src/shader_preprocessor.rs b/engine/src/opengl/glsl.rs index 70696ac..6fd5638 100644 --- a/engine/src/shader_preprocessor.rs +++ b/engine/src/opengl/glsl.rs @@ -1,183 +1,181 @@ +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use std::string::FromUtf8Error; const PREINCLUDE_DIRECTIVE: &str = "#preinclude"; -/// Preprocessor for shaders written in the OpenGL Shading Language. -pub struct ShaderPreprocessor +pub fn preprocess<'content>( + shader_content: impl Into<Cow<'content, str>>, + read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>, +) -> Result<Cow<'content, str>, PreprocessingError> { - read_file: fn(&Path) -> Result<Vec<u8>, std::io::Error>, - base_dir: PathBuf, + do_preprocess(shader_content, SpanPath::Original, read_file) } -impl ShaderPreprocessor +fn do_preprocess<'content>( + shader_content: impl Into<Cow<'content, str>>, + shader_path: SpanPath<'_>, + read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>, +) -> Result<Cow<'content, str>, PreprocessingError> { - pub fn new(base_dir: PathBuf) -> Self - { - Self { - read_file: |path| std::fs::read(path), - base_dir, - } - } + let shader_content = shader_content.into(); - pub fn preprocess( - &self, - shader_content: String, - shader_file_path: &Path, - ) -> Result<String, Error> - { - let mut preincludes = shader_content - .match_indices(PREINCLUDE_DIRECTIVE) - .peekable(); + let mut preincludes = shader_content + .match_indices(PREINCLUDE_DIRECTIVE) + .peekable(); - if preincludes.peek().is_none() { - // Shader content contains no preincludes - return Ok(shader_content); - }; + if preincludes.peek().is_none() { + // Shader content contains no preincludes + return Ok(shader_content.into()); + }; - let mut preprocessed = shader_content.clone(); + let mut preprocessed = shader_content.to_string(); - let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE); + let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE); - let mut last_start = 0; - let mut span_line_offset = 0; + let mut last_start = 0; + let mut span_line_offset = 0; - while let Some(preinclude_start) = curr { - let replacement_job = self.handle_preinclude( - &preprocessed, - shader_file_path, - preinclude_start, - span_line_offset, - )?; + while let Some(preinclude_start) = curr { + let replacement_job = handle_preinclude( + &preprocessed, + &shader_path, + preinclude_start, + span_line_offset, + )?; - let path = replacement_job.path.clone(); + let path = replacement_job.path.clone(); - let mut included = String::from_utf8( - (self.read_file)(&self.base_dir.join(replacement_job.path.clone())) - .map_err(|err| Error::ReadIncludedShaderFailed { - source: err, - path: replacement_job.path.clone(), - })?, - ) - .map_err(|err| Error::IncludedShaderInvalidUtf8 { - source: err, - path: path.clone(), + let mut included = + String::from_utf8(read_file(&replacement_job.path).map_err(|err| { + PreprocessingError::ReadIncludedShaderFailed { + source: err, + path: replacement_job.path.clone(), + } + })?) + .map_err(|err| { + PreprocessingError::IncludedShaderInvalidUtf8 { + source: err, + path: path.clone(), + } })?; - if let Some(first_line) = included.lines().next() { - if first_line.starts_with("#version") { - included = included - .chars() - .skip_while(|character| *character != '\n') - .collect(); - } + if let Some(first_line) = included.lines().next() { + if first_line.starts_with("#version") { + included = included + .chars() + .skip_while(|character| *character != '\n') + .collect(); } - - let included_preprocessed = - self.preprocess(included, &replacement_job.path)?; - - let start = replacement_job.start_index; - let end = replacement_job.end_index; - - preprocessed.replace_range(start..end, &included_preprocessed); - - curr = preprocessed[last_start + 1..] - .find(PREINCLUDE_DIRECTIVE) - .map(|index| index + 1); - - last_start = preinclude_start + included_preprocessed.len(); - - span_line_offset += included_preprocessed.lines().count(); } - Ok(preprocessed) - } + let included_preprocessed = do_preprocess( + &included, + SpanPath::Path(replacement_job.path.as_path().into()), + read_file, + )?; - fn handle_preinclude( - &self, - shader_content: &str, - shader_file_path: &Path, - preinclude_start_index: usize, - span_line_offset: usize, - ) -> Result<ReplacementJob, Error> - { - let expect_token = |token: char, index: usize| { - let token_found = shader_content.chars().nth(index).ok_or_else(|| { - Error::ExpectedToken { - expected: token, - span: Span::new( - shader_content, - &self.base_dir.join(shader_file_path), - index, - span_line_offset, - preinclude_start_index, - ), - } - })?; + let start = replacement_job.start_index; + let end = replacement_job.end_index; - if token_found != token { - return Err(Error::InvalidToken { - expected: token, - found: token_found, - span: Span::new( - shader_content, - &self.base_dir.join(shader_file_path), - index, - span_line_offset, - preinclude_start_index, - ), - }); - } + preprocessed.replace_range(start..end, &included_preprocessed); - Ok(()) - }; + curr = preprocessed[last_start + 1..] + .find(PREINCLUDE_DIRECTIVE) + .map(|index| index + 1); - let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len(); - let quote_open_index = space_index + 1; + last_start = preinclude_start + included_preprocessed.len(); - expect_token(' ', space_index)?; - expect_token('"', quote_open_index)?; + span_line_offset += included_preprocessed.lines().count(); + } - let buf = shader_content[quote_open_index + 1..] - .chars() - .take_while(|character| *character != '"') - .map(|character| character as u8) - .collect::<Vec<_>>(); + Ok(preprocessed.into()) +} - if buf.is_empty() { - return Err(Error::ExpectedToken { - expected: '"', +fn handle_preinclude( + shader_content: &str, + shader_path: &SpanPath<'_>, + preinclude_start_index: usize, + span_line_offset: usize, +) -> Result<ReplacementJob, PreprocessingError> +{ + let expect_token = |token: char, index: usize| { + let token_found = shader_content.chars().nth(index).ok_or_else(|| { + PreprocessingError::ExpectedToken { + expected: token, span: Span::new( shader_content, - &self.base_dir.join(shader_file_path), - shader_content.len() - 1, + shader_path.to_owned(), + index, span_line_offset, preinclude_start_index, ), - }); - } - - let path_len = buf.len(); + } + })?; - let path = PathBuf::from(String::from_utf8(buf).map_err(|err| { - Error::PreincludePathInvalidUtf8 { - source: err, + if token_found != token { + return Err(PreprocessingError::InvalidToken { + expected: token, + found: token_found, span: Span::new( shader_content, - &self.base_dir.join(shader_file_path), - quote_open_index + 1, + shader_path.to_owned(), + index, span_line_offset, preinclude_start_index, ), - } - })?); + }); + } - Ok(ReplacementJob { - start_index: preinclude_start_index, - end_index: quote_open_index + 1 + path_len + 1, - path, - }) + Ok(()) + }; + + let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len(); + let quote_open_index = space_index + 1; + + expect_token(' ', space_index)?; + expect_token('"', quote_open_index)?; + + let buf = shader_content[quote_open_index + 1..] + .chars() + .take_while(|character| *character != '"') + .map(|character| character as u8) + .collect::<Vec<_>>(); + + if buf.is_empty() { + return Err(PreprocessingError::ExpectedToken { + expected: '"', + span: Span::new( + shader_content, + shader_path.to_owned(), + shader_content.len() - 1, + span_line_offset, + preinclude_start_index, + ), + }); } + + let path_len = buf.len(); + + let path = PathBuf::from(String::from_utf8(buf).map_err(|err| { + PreprocessingError::PreincludePathInvalidUtf8 { + source: err, + span: Span::new( + shader_content, + shader_path.to_owned(), + quote_open_index + 1, + span_line_offset, + preinclude_start_index, + ), + } + })?); + + Ok(ReplacementJob { + start_index: preinclude_start_index, + end_index: quote_open_index + 1 + path_len + 1, + path, + }) } struct ReplacementJob @@ -187,15 +185,14 @@ struct ReplacementJob path: PathBuf, } -/// Shader preprocessing error. #[derive(Debug, thiserror::Error)] -pub enum Error +pub enum PreprocessingError { #[error( "Invalid token at line {}, column {} of {}. Expected '{}', found '{}'", span.line, span.column, - span.file.display(), + span.path, expected, found )] @@ -211,7 +208,7 @@ pub enum Error expected, span.line, span.column, - span.file.display(), + span.path )] ExpectedToken { @@ -222,7 +219,7 @@ pub enum Error "Preinclude path at line {}, column {} of {} is invalid UTF-8", span.line, span.column, - span.file.display(), + span.path )] PreincludePathInvalidUtf8 { @@ -249,18 +246,19 @@ pub enum Error } #[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] pub struct Span { - line: usize, - column: usize, - file: PathBuf, + pub line: usize, + pub column: usize, + pub path: SpanPath<'static>, } impl Span { fn new( file_content: &str, - file_path: &Path, + path: SpanPath<'static>, char_index: usize, line_offset: usize, line_start_index: usize, @@ -272,7 +270,49 @@ impl Span Self { line, column: char_index - line_start_index + 1, - file: file_path.to_path_buf(), + path, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SpanPath<'a> +{ + Original, + Path(Cow<'a, Path>), +} + +impl<'a> SpanPath<'a> +{ + fn to_owned(&self) -> SpanPath<'static> + { + match self { + Self::Original => SpanPath::Original, + Self::Path(path) => SpanPath::Path(Cow::Owned(path.to_path_buf().into())), + } + } +} + +impl<'a> Display for SpanPath<'a> +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + match self { + Self::Original => write!(formatter, "original file"), + Self::Path(path) => write!(formatter, "file {}", path.display()), + } + } +} + +impl<'a, PathLike> PartialEq<PathLike> for SpanPath<'a> +where + PathLike: AsRef<Path>, +{ + fn eq(&self, other: &PathLike) -> bool + { + match self { + Self::Original => false, + Self::Path(path) => path == other.as_ref(), } } } @@ -290,21 +330,17 @@ fn find_line_of_index(text: &str, index: usize) -> usize mod tests { use std::ffi::OsStr; - use std::path::{Path, PathBuf}; + use std::path::Path; - use super::{Error, ShaderPreprocessor}; + use super::{preprocess, PreprocessingError}; + use crate::opengl::glsl::SpanPath; #[test] fn preprocess_no_directives_is_same() { assert_eq!( - ShaderPreprocessor { - read_file: |_| { unreachable!() }, - base_dir: PathBuf::new() - } - .preprocess("#version 330 core\n".to_string(), Path::new("foo.glsl"),) - .unwrap(), - "#version 330 core\n".to_string() + preprocess("#version 330 core\n", &|_| { unreachable!() }).unwrap(), + "#version 330 core\n" ); } @@ -312,20 +348,15 @@ mod tests fn preprocess_with_directives_works() { assert_eq!( - ShaderPreprocessor { - read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) }, - base_dir: PathBuf::new() - } - .preprocess( + preprocess( concat!( "#version 330 core\n", "\n", "#preinclude \"foo.glsl\"\n", "\n", "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + ), + &|_| { Ok(b"out vec4 FragColor;".to_vec()) } ) .unwrap(), concat!( @@ -338,11 +369,7 @@ mod tests ); assert_eq!( - ShaderPreprocessor { - read_file: |_| { Ok(b"out vec4 FragColor;".to_vec()) }, - base_dir: PathBuf::new() - } - .preprocess( + preprocess( concat!( "#version 330 core\n", "\n", @@ -351,9 +378,8 @@ mod tests "in vec3 in_frag_color;\n", "\n", "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + ), + &|_| { Ok(b"out vec4 FragColor;".to_vec()) } ) .unwrap(), concat!( @@ -368,8 +394,19 @@ mod tests ); assert_eq!( - ShaderPreprocessor { - read_file: |path| { + preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "#preinclude \"foo.glsl\"\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(b"out vec4 FragColor;".to_vec()) } else { @@ -381,22 +418,6 @@ mod tests .to_vec()) } }, - base_dir: PathBuf::new() - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "#preinclude \"foo.glsl\"\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ) .unwrap(), concat!( @@ -415,15 +436,9 @@ mod tests } #[test] - fn preprocess_invalid_shader_does_not_work() + fn preprocess_invalid_directive_does_not_work() { - let path = Path::new("foo.glsl"); - - let res = ShaderPreprocessor { - read_file: |_| Ok(b"out vec4 FragColor;".to_vec()), - base_dir: PathBuf::new(), - } - .preprocess( + let res = preprocess( concat!( "#version 330 core\n", "\n", @@ -431,12 +446,11 @@ mod tests "#preinclude foo.glsl\"\n", "\n", "void main() {}", - ) - .to_string(), - path, + ), + &|_| Ok(b"out vec4 FragColor;".to_vec()), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -446,16 +460,25 @@ mod tests assert_eq!(found, 'f'); assert_eq!(span.line, 3); assert_eq!(span.column, 13); - assert_eq!(span.file, path); + assert_eq!(span.path, SpanPath::Original); } #[test] fn preprocess_error_has_correct_span() { - let path = Path::new("foo.glsl"); - - let res = ShaderPreprocessor { - read_file: |path| { + let res = preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "#preinclude \"foo.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( "out vec4 FragColor;\n", @@ -464,29 +487,25 @@ mod tests ) .as_bytes() .to_vec()) + } else if path == OsStr::new("foo.glsl") { + Ok(concat!( + "uniform sampler2D input_texture;\n", + "\n", + // Missing space before first " + "#preinclude\"shared_types.glsl\"\n", + ) + .as_bytes() + .to_vec()) } else { - Ok(b"uniform sampler2D input_texture;".to_vec()) + panic!(concat!( + "Expected read function to be called with ", + "either path bar.glsl or foo.glsl" + )); } }, - base_dir: PathBuf::new(), - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "#preinclude\"foo.glsl\"\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -494,17 +513,26 @@ mod tests assert_eq!(expected, ' '); assert_eq!(found, '"'); - assert_eq!(span.line, 7); + assert_eq!(span.line, 3); assert_eq!(span.column, 12); - assert_eq!(span.file, path); + assert_eq!(span.path, SpanPath::Path(Path::new("foo.glsl").into())); } #[test] fn preprocess_included_shader_with_include_works() { assert_eq!( - ShaderPreprocessor { - read_file: |path| { + preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( "#preinclude \"foo.glsl\"\n", @@ -521,21 +549,7 @@ mod tests .as_bytes() .to_vec()) } - }, - base_dir: PathBuf::new() - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), + } ) .unwrap(), concat!( @@ -556,8 +570,17 @@ mod tests #[test] fn preprocess_included_shader_with_include_error_span_is_correct() { - let res = ShaderPreprocessor { - read_file: |path| { + let res = preprocess( + concat!( + "#version 330 core\n", + "\n", + "#preinclude \"bar.glsl\"\n", + "\n", + "in vec3 in_frag_color;\n", + "\n", + "void main() {}", + ), + &|path| { if path == OsStr::new("bar.glsl") { Ok(concat!( // ' instead of " @@ -576,23 +599,9 @@ mod tests .to_vec()) } }, - base_dir: PathBuf::new(), - } - .preprocess( - concat!( - "#version 330 core\n", - "\n", - "#preinclude \"bar.glsl\"\n", - "\n", - "in vec3 in_frag_color;\n", - "\n", - "void main() {}", - ) - .to_string(), - Path::new("foo.glsl"), ); - let Err(Error::InvalidToken { expected, found, span }) = res else { + let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else { panic!( "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}" ); @@ -602,6 +611,6 @@ mod tests assert_eq!(found, '\''); assert_eq!(span.line, 1); assert_eq!(span.column, 13); - assert_eq!(span.file, Path::new("bar.glsl")); + assert_eq!(span.path, Path::new("bar.glsl")); } } diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index 0b1bb8a..53e0120 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -1,16 +1,17 @@ use bitflags::bitflags; +use gl::types::GLint; use crate::data_types::dimens::Dimens; use crate::vector::Vec2; pub mod buffer; +pub mod glsl; pub mod shader; pub mod texture; pub mod vertex_array; mod util; -#[cfg(feature = "debug")] pub mod debug; pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) @@ -47,6 +48,17 @@ pub fn enable(capacity: Capability) } } +pub fn get_context_flags() -> ContextFlags +{ + let mut context_flags: GLint = 0; + + unsafe { + gl::GetIntegerv(gl::CONTEXT_FLAGS as u32, &mut context_flags); + } + + ContextFlags::from_bits_truncate(context_flags as u32) +} + bitflags! { #[derive(Debug, Clone, Copy)] pub struct BufferClearMask: u32 { @@ -105,3 +117,12 @@ impl From<crate::draw_flags::PolygonModeFace> for PolygonModeFace } } } + +bitflags! { +#[derive(Debug, Clone, Copy)] +pub struct ContextFlags: u32 { + const FORWARD_COMPATIBLE = gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT; + const DEBUG = gl::CONTEXT_FLAG_DEBUG_BIT; + const ROBUST_ACCESS = gl::CONTEXT_FLAG_ROBUST_ACCESS_BIT; +} +} diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs index 070897e..36dc1a4 100644 --- a/engine/src/opengl/shader.rs +++ b/engine/src/opengl/shader.rs @@ -2,7 +2,6 @@ use std::ffi::CStr; use std::ptr::null_mut; use crate::matrix::Matrix; -use crate::shader::Kind; use crate::vector::Vec3; #[derive(Debug)] @@ -20,7 +19,7 @@ impl Shader Self { shader } } - pub fn set_source(&self, source: &str) -> Result<(), Error> + pub fn set_source(&mut self, source: &str) -> Result<(), Error> { if !source.is_ascii() { return Err(Error::SourceNotAscii); @@ -39,7 +38,7 @@ impl Shader Ok(()) } - pub fn compile(&self) -> Result<(), Error> + pub fn compile(&mut self) -> Result<(), Error> { unsafe { gl::CompileShader(self.shader); @@ -90,6 +89,14 @@ impl Drop for Shader } } +/// Shader kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Kind +{ + Vertex, + Fragment, +} + impl Kind { fn into_gl(self) -> gl::types::GLenum @@ -117,14 +124,14 @@ impl Program Self { program } } - pub fn attach(&self, shader: &Shader) + pub fn attach(&mut self, shader: &Shader) { unsafe { gl::AttachShader(self.program, shader.shader); } } - pub fn link(&self) -> Result<(), Error> + pub fn link(&mut self) -> Result<(), Error> { unsafe { gl::LinkProgram(self.program); diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs index 074ade7..52c8554 100644 --- a/engine/src/opengl/texture.rs +++ b/engine/src/opengl/texture.rs @@ -1,5 +1,5 @@ use crate::data_types::dimens::Dimens; -use crate::texture::{Id, Properties}; +use crate::texture::Properties; #[derive(Debug)] pub struct Texture @@ -224,8 +224,8 @@ macro_rules! texture_unit_enum { } } - pub fn from_texture_id(texture_id: Id) -> Option<Self> { - match texture_id.into_inner() { + pub fn from_num(num: usize) -> Option<Self> { + match num { #( N => Some(Self::No~N), )* diff --git a/engine/src/performance.rs b/engine/src/performance.rs index ffc5c27..3ec8994 100644 --- a/engine/src/performance.rs +++ b/engine/src/performance.rs @@ -21,20 +21,6 @@ impl ecs::extension::Extension for Extension } } -#[cfg(feature = "debug")] -macro_rules! log_perf { - ($($tt: tt)*) => { - tracing::info!($($tt)*); - }; -} - -#[cfg(not(feature = "debug"))] -macro_rules! log_perf { - ($($tt: tt)*) => { - println!($($tt)*); - }; -} - fn log_perf(mut state: Local<State>) { let Some(last_time) = state.last_time else { @@ -46,7 +32,7 @@ fn log_perf(mut state: Local<State>) state.last_time = Some(time_now); - log_perf!( + tracing::info!( "Frame time: {}us", time_now.duration_since(last_time).as_micros() ); diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index a353c6a..c036cc0 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::ffi::{c_void, CString}; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::ops::Deref; +use std::path::Path; use std::process::abort; use ecs::actions::Actions; @@ -22,10 +24,22 @@ use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::mesh::Mesh; use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; -#[cfg(feature = "debug")] -use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType}; +use crate::opengl::debug::{ + enable_debug_output, + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + MessageSeverity, + MessageSource, + MessageType, +}; +use crate::opengl::glsl::{ + preprocess as glsl_preprocess, + PreprocessingError as GlslPreprocessingError, +}; use crate::opengl::shader::{ Error as GlShaderError, + Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; @@ -39,9 +53,15 @@ use crate::opengl::vertex_array::{ PrimitiveKind, VertexArray, }; -use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability}; +use crate::opengl::{ + clear_buffers, + enable, + get_context_flags as get_opengl_context_flags, + BufferClearMask, + Capability, + ContextFlags, +}; use crate::projection::{new_perspective_matrix, Projection}; -use crate::shader::Program as ShaderProgram; use crate::texture::{Id as TextureId, Texture}; use crate::transform::{Position, Scale}; use crate::util::NeverDrop; @@ -51,7 +71,6 @@ use crate::window::Window; type RenderableEntity = ( Mesh, - ShaderProgram, Material, Option<MaterialFlags>, Option<Position>, @@ -61,6 +80,7 @@ type RenderableEntity = ( ); #[derive(Debug, Default)] +#[non_exhaustive] pub struct Extension {} impl ecs::extension::Extension for Extension @@ -95,8 +115,9 @@ fn initialize(window: Single<Window>) } }); - #[cfg(feature = "debug")] - initialize_debug(); + if get_opengl_context_flags().contains(ContextFlags::DEBUG) { + initialize_debug(); + } let window_size = window.size().expect("Failed to get window size"); @@ -123,7 +144,6 @@ fn render( ) { let Some((camera, camera_pos, _)) = camera_query.iter().next() else { - #[cfg(feature = "debug")] tracing::warn!("No current camera. Nothing will be rendered"); return; }; @@ -136,34 +156,24 @@ fn render( let directional_lights = directional_lights.iter().collect::<Vec<_>>(); let GlobalGlObjects { - shader_programs: gl_shader_programs, + shader_program, textures: gl_textures, } = &mut *gl_objects; + let shader_program = + shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); + clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); for ( entity_index, - ( - mesh, - shader_program, - material, - material_flags, - position, - scale, - draw_flags, - gl_objects, - ), + (mesh, material, material_flags, position, scale, draw_flags, gl_objects), ) in query.iter().enumerate() { let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); - let shader_program = gl_shader_programs - .entry(shader_program.u64_hash()) - .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap()); - let new_gl_objects; let gl_objects = if let Some(gl_objects) = gl_objects.as_deref() { @@ -207,15 +217,12 @@ fn render( &camera_pos, ); - for texture in &material.textures { + for (index, texture) in material.textures.iter().enumerate() { let gl_texture = gl_textures .entry(texture.id()) .or_insert_with(|| create_gl_texture(texture)); - let texture_unit = - TextureUnit::from_texture_id(texture.id()).unwrap_or_else(|| { - panic!("Texture id {} is a invalid texture unit", texture.id()); - }); + let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); set_active_texture_unit(texture_unit); @@ -247,7 +254,7 @@ fn render( #[derive(Debug, Default, Component)] struct GlobalGlObjects { - shader_programs: HashMap<u64, GlShaderProgram>, + shader_program: Option<GlShaderProgram>, textures: HashMap<TextureId, GlTexture>, } @@ -256,20 +263,11 @@ fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) crate::opengl::set_viewport(position, size); } -#[cfg(feature = "debug")] fn initialize_debug() { - use crate::opengl::debug::{ - enable_debug_output, - set_debug_message_callback, - set_debug_message_control, - MessageIdsAction, - }; - enable_debug_output(); set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); } @@ -299,34 +297,68 @@ fn create_gl_texture(texture: &Texture) -> GlTexture gl_texture } -fn create_gl_shader_program( - shader_program: &ShaderProgram, -) -> Result<GlShaderProgram, GlShaderError> +const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); +const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl"); + +const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl"); +const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl"); + +fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError> { - let gl_shaders = shader_program - .shaders() - .iter() - .map(|shader| { - let gl_shader = GlShader::new(shader.kind()); + let mut vertex_shader = GlShader::new(ShaderKind::Vertex); - gl_shader.set_source(shader.source())?; - gl_shader.compile()?; + vertex_shader.set_source(&*glsl_preprocess( + VERTEX_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; - Ok(gl_shader) - }) - .collect::<Result<Vec<_>, _>>()?; + vertex_shader.compile()?; - let gl_shader_program = GlShaderProgram::new(); + let mut fragment_shader = GlShader::new(ShaderKind::Fragment); - for gl_shader in &gl_shaders { - gl_shader_program.attach(gl_shader); - } + fragment_shader.set_source(&*glsl_preprocess( + FRAGMENT_GLSL_SHADER_SRC, + &get_glsl_shader_content, + )?)?; + + fragment_shader.compile()?; + + let mut gl_shader_program = GlShaderProgram::new(); + + gl_shader_program.attach(&vertex_shader); + gl_shader_program.attach(&fragment_shader); gl_shader_program.link()?; Ok(gl_shader_program) } +#[derive(Debug, thiserror::Error)] +enum CreateShaderError +{ + #[error(transparent)] + ShaderError(#[from] GlShaderError), + + #[error(transparent)] + PreprocessingError(#[from] GlslPreprocessingError), +} + +fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> +{ + if path == Path::new("vertex_data.glsl") { + return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + if path == Path::new("light.glsl") { + return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec()); + } + + Err(IoError::new( + IoErrorKind::NotFound, + format!("Content for shader file {} not found", path.display()), + )) +} + #[derive(Debug, Component)] struct GlObjects { @@ -340,10 +372,9 @@ struct GlObjects impl GlObjects { - #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + #[tracing::instrument(skip_all)] fn new(mesh: &Mesh) -> Self { - #[cfg(feature = "debug")] tracing::trace!( "Creating vertex array, vertex buffer{}", if mesh.indices().is_some() { @@ -532,22 +563,29 @@ fn apply_light<PointLightHolder>( gl_shader_program .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into()); + let texture_map = material + .textures + .iter() + .enumerate() + .map(|(index, texture)| (texture.id(), index)) + .collect::<HashMap<_, _>>(); + #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.ambient_map", - material.ambient_map.into_inner() as i32, + *texture_map.get(&material.ambient_map).unwrap() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.diffuse_map", - material.diffuse_map.into_inner() as i32, + *texture_map.get(&material.diffuse_map).unwrap() as i32, ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform_1i( c"material.specular_map", - material.specular_map.into_inner() as i32, + *texture_map.get(&material.specular_map).unwrap() as i32, ); gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess); @@ -658,7 +696,6 @@ fn create_view(camera: &Camera, camera_pos: &Position) -> Matrix<f32, 4, 4> view } -#[cfg(feature = "debug")] #[tracing::instrument(skip_all)] fn opengl_debug_message_cb( source: MessageSource, diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl new file mode 100644 index 0000000..5bf5ff2 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/fragment.glsl @@ -0,0 +1,73 @@ +#version 330 core + +#preinclude "light.glsl" +#preinclude "vertex_data.glsl" + +#define MAX_LIGHT_CNT 64 + +out vec4 FragColor; + +in VertexData vertex_data; + +uniform vec3 view_pos; +uniform sampler2D input_texture; +uniform Material material; + +uniform PointLight point_lights[MAX_LIGHT_CNT]; +uniform int point_light_cnt; + +uniform DirectionalLight directional_lights[MAX_LIGHT_CNT]; +uniform int directional_light_cnt; + +void main() +{ + vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords); + + vec3 directional_light_sum = vec3(0.0, 0.0, 0.0); + + for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) { + CalculatedLight calculated_dir_light; + + calc_light( + // Negated since we want the light to point from the light direction + normalize(-directional_lights[dl_index].direction), + directional_lights[dl_index].phong, + vertex_data, + view_pos, + material, + calculated_dir_light + ); + + directional_light_sum += + calculated_dir_light.diffuse + calculated_dir_light.specular; + } + + vec3 point_light_sum = vec3(0.0, 0.0, 0.0); + + for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) { + vec3 light_direction = + normalize(point_lights[pl_index].position - vertex_data.world_space_pos); + + CalculatedLight calculated_point_light; + + calc_light( + light_direction, + point_lights[pl_index].phong, + vertex_data, + view_pos, + material, + calculated_point_light + ); + + float attenuation = + calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos); + + calculated_point_light.diffuse *= attenuation; + calculated_point_light.specular *= attenuation; + + point_light_sum += + calculated_point_light.diffuse + calculated_point_light.specular; + } + + FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0); +} diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl new file mode 100644 index 0000000..1bc23a4 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/light.glsl @@ -0,0 +1,133 @@ +#version 330 core + +#ifndef LIGHT_GLSL +#define LIGHT_GLSL + +#preinclude "vertex_data.glsl" + +struct Material +{ + vec3 ambient; + vec3 diffuse; + vec3 specular; + sampler2D ambient_map; + sampler2D diffuse_map; + sampler2D specular_map; + float shininess; +}; + +struct LightPhong +{ + vec3 diffuse; + vec3 specular; +}; + +struct AttenuationProperties +{ + float constant; + float linear; + float quadratic; +}; + +struct PointLight +{ + LightPhong phong; + vec3 position; + AttenuationProperties attenuation_props; +}; + +struct DirectionalLight +{ + LightPhong phong; + vec3 direction; +}; + +struct CalculatedLight +{ + vec3 diffuse; + vec3 specular; +}; + +vec3 calc_ambient_light(in Material material, in vec2 texture_coords) +{ + return vec3(texture(material.ambient_map, texture_coords)) * material.ambient; +} + +vec3 calc_diffuse_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec2 texture_coords +) +{ + float diff = max(dot(norm, light_dir), 0.0); + + return light_phong.diffuse * ( + diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse) + ); +} + +vec3 calc_specular_light( + in Material material, + in LightPhong light_phong, + in vec3 light_dir, + in vec3 norm, + in vec3 view_pos, + in vec3 frag_pos, + in vec2 texture_coords +) +{ + vec3 view_direction = normalize(view_pos - frag_pos); + + vec3 reflect_direction = reflect(-light_dir, norm); + + float spec = + pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess); + + return light_phong.specular * ( + spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) + ); +} + +float calc_attenuation(in PointLight point_light, in vec3 position) +{ + float light_distance = length(point_light.position - position); + + return 1.0 / (point_light.attenuation_props.constant + + point_light.attenuation_props.linear + * light_distance + point_light.attenuation_props.quadratic + * pow(light_distance, 2)); +} + +void calc_light( + in vec3 light_direction, + in LightPhong light_phong, + in VertexData vertex_data, + in vec3 view_pos, + in Material material, + out CalculatedLight calculated_light +) +{ + vec3 norm = normalize(vertex_data.world_space_normal); + + calculated_light.diffuse = calc_diffuse_light( + material, + light_phong, + light_direction, + norm, + vertex_data.texture_coords + ); + + calculated_light.specular = calc_specular_light( + material, + light_phong, + light_direction, + norm, + view_pos, + vertex_data.world_space_pos, + vertex_data.texture_coords + ); +} + +#endif diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl new file mode 100644 index 0000000..b57caa6 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex.glsl @@ -0,0 +1,24 @@ +#version 330 core + +#preinclude "vertex_data.glsl" + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texture_coords; +layout (location = 2) in vec3 normal; + +out VertexData vertex_data; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(pos, 1.0); + + vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0)); + vertex_data.texture_coords = texture_coords; + + // TODO: Do this using CPU for performance increase + vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal; +} diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl new file mode 100644 index 0000000..486d445 --- /dev/null +++ b/engine/src/renderer/opengl/glsl/vertex_data.glsl @@ -0,0 +1,11 @@ +#ifndef VERTEX_DATA_GLSL +#define VERTEX_DATA_GLSL + +struct VertexData +{ + vec2 texture_coords; + vec3 world_space_pos; + vec3 world_space_normal; +}; + +#endif diff --git a/engine/src/shader.rs b/engine/src/shader.rs deleted file mode 100644 index 89f7b7c..0000000 --- a/engine/src/shader.rs +++ /dev/null @@ -1,186 +0,0 @@ -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::shader_preprocessor::{Error as ShaderPreprocessorError, ShaderPreprocessor}; - -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<Shader>, -} - -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<Self, Error> - { - 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<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, -} diff --git a/engine/src/texture.rs b/engine/src/texture.rs index f82b59d..16c1941 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -181,11 +181,6 @@ impl Id { Self { id } } - - pub(crate) fn into_inner(self) -> u32 - { - self.id - } } impl Display for Id diff --git a/engine/src/window.rs b/engine/src/window.rs index ccc1b8d..ad239a1 100644 --- a/engine/src/window.rs +++ b/engine/src/window.rs @@ -1,31 +1,19 @@ use std::borrow::Cow; use std::ffi::{CStr, CString}; +use bitflags::bitflags; use ecs::actions::Actions; use ecs::extension::Collector as ExtensionCollector; use ecs::sole::Single; use ecs::Sole; +use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue}; use glfw::WindowSize; +use util_macros::VariantArr; use crate::data_types::dimens::Dimens; use crate::event::{Conclude as ConcludeEvent, Start as StartEvent}; use crate::vector::Vec2; -mod reexports -{ - pub use glfw::window::{ - CursorMode, - Hint as CreationHint, - HintValue as CreationHintValue, - InputMode, - Key, - KeyModifiers, - KeyState, - }; -} - -pub use reexports::*; - #[derive(Debug, Sole)] /// Has to be dropped last since it holds the OpenGL context. #[sole(drop_last)] @@ -53,7 +41,9 @@ impl Window enabled: bool, ) -> Result<(), Error> { - Ok(self.inner.set_input_mode(input_mode, enabled)?) + Ok(self + .inner + .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?) } /// Sets the cursor mode. @@ -62,7 +52,9 @@ impl Window /// If a platform error occurs. pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error> { - Ok(self.inner.set_cursor_mode(cursor_mode)?) + Ok(self + .inner + .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?) } /// Returns whether or not the window should close. Will return true when the user has @@ -155,7 +147,19 @@ impl Window callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static, ) { - self.inner.set_key_callback(callback); + self.inner + .set_key_callback(move |key, scancode, key_state, key_modifiers| { + let Some(key_state) = KeyState::from_glfw_key_state(key_state) else { + return; + }; + + callback( + Key::from_glfw_key(key), + scancode, + key_state, + KeyModifiers::from_bits_truncate(key_modifiers.bits()), + ) + }); } /// Sets the window's cursor position callback. @@ -188,10 +192,26 @@ pub struct Builder impl Builder { - #[must_use] - pub fn creation_hint(mut self, hint: CreationHint, value: CreationHintValue) -> Self + /// Sets whether the OpenGL context should be created in debug mode, which may + /// provide additional error and diagnostic reporting functionality. + pub fn opengl_debug_context(mut self, enabled: bool) -> Self { - self.inner = self.inner.hint(hint, value); + self.inner = self.inner.hint( + WindowCreationHint::OpenGLDebugContext, + WindowCreationHintValue::Bool(enabled), + ); + + self + } + + /// Set the desired number of samples to use for multisampling. Zero disables + /// multisampling. + pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self + { + self.inner = self.inner.hint( + WindowCreationHint::Samples, + WindowCreationHintValue::Number(sample_count as i32), + ); self } @@ -204,8 +224,8 @@ impl Builder pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error> { let builder = self.inner.clone().hint( - CreationHint::OpenGLDebugContext, - CreationHintValue::Bool(cfg!(feature = "debug")), + WindowCreationHint::OpenGLDebugContext, + WindowCreationHintValue::Bool(true), ); let window = builder.create( @@ -220,6 +240,342 @@ impl Builder } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)] +#[variant_arr(name = KEYS)] +pub enum Key +{ + Space, + Apostrophe, + Comma, + Minus, + Period, + Slash, + Digit0, + Digit1, + Digit2, + Digit3, + Digit4, + Digit5, + Digit6, + Digit7, + Digit8, + Digit9, + Semicolon, + Equal, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + LeftBracket, + Backslash, + RightBracket, + GraveAccent, + World1, + World2, + Escape, + Enter, + Tab, + Backspace, + Insert, + Delete, + Right, + Left, + Down, + Up, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + NumLock, + PrintScreen, + Pause, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + Kp0, + Kp1, + Kp2, + Kp3, + Kp4, + Kp5, + Kp6, + Kp7, + Kp8, + Kp9, + KpDecimal, + KpDivide, + KpMultiply, + KpSubtract, + KpAdd, + KpEnter, + KpEqual, + LeftShift, + LeftControl, + LeftAlt, + LeftSuper, + RightShift, + RightControl, + RightAlt, + RightSuper, + Menu, +} + +impl Key +{ + fn from_glfw_key(glfw_key: glfw::window::Key) -> Self + { + match glfw_key { + glfw::window::Key::Space => Self::Space, + glfw::window::Key::Apostrophe => Self::Apostrophe, + glfw::window::Key::Comma => Self::Comma, + glfw::window::Key::Minus => Self::Minus, + glfw::window::Key::Period => Self::Period, + glfw::window::Key::Slash => Self::Slash, + glfw::window::Key::Digit0 => Self::Digit0, + glfw::window::Key::Digit1 => Self::Digit1, + glfw::window::Key::Digit2 => Self::Digit2, + glfw::window::Key::Digit3 => Self::Digit3, + glfw::window::Key::Digit4 => Self::Digit4, + glfw::window::Key::Digit5 => Self::Digit5, + glfw::window::Key::Digit6 => Self::Digit6, + glfw::window::Key::Digit7 => Self::Digit7, + glfw::window::Key::Digit8 => Self::Digit8, + glfw::window::Key::Digit9 => Self::Digit9, + glfw::window::Key::Semicolon => Self::Semicolon, + glfw::window::Key::Equal => Self::Equal, + glfw::window::Key::A => Self::A, + glfw::window::Key::B => Self::B, + glfw::window::Key::C => Self::C, + glfw::window::Key::D => Self::D, + glfw::window::Key::E => Self::E, + glfw::window::Key::F => Self::F, + glfw::window::Key::G => Self::G, + glfw::window::Key::H => Self::H, + glfw::window::Key::I => Self::I, + glfw::window::Key::J => Self::J, + glfw::window::Key::K => Self::K, + glfw::window::Key::L => Self::L, + glfw::window::Key::M => Self::M, + glfw::window::Key::N => Self::N, + glfw::window::Key::O => Self::O, + glfw::window::Key::P => Self::P, + glfw::window::Key::Q => Self::Q, + glfw::window::Key::R => Self::R, + glfw::window::Key::S => Self::S, + glfw::window::Key::T => Self::T, + glfw::window::Key::U => Self::U, + glfw::window::Key::V => Self::V, + glfw::window::Key::W => Self::W, + glfw::window::Key::X => Self::X, + glfw::window::Key::Y => Self::Y, + glfw::window::Key::Z => Self::Z, + glfw::window::Key::LeftBracket => Self::LeftBracket, + glfw::window::Key::Backslash => Self::Backslash, + glfw::window::Key::RightBracket => Self::RightBracket, + glfw::window::Key::GraveAccent => Self::GraveAccent, + glfw::window::Key::World1 => Self::World1, + glfw::window::Key::World2 => Self::World2, + glfw::window::Key::Escape => Self::Escape, + glfw::window::Key::Enter => Self::Enter, + glfw::window::Key::Tab => Self::Tab, + glfw::window::Key::Backspace => Self::Backspace, + glfw::window::Key::Insert => Self::Insert, + glfw::window::Key::Delete => Self::Delete, + glfw::window::Key::Right => Self::Right, + glfw::window::Key::Left => Self::Left, + glfw::window::Key::Down => Self::Down, + glfw::window::Key::Up => Self::Up, + glfw::window::Key::PageUp => Self::PageUp, + glfw::window::Key::PageDown => Self::PageDown, + glfw::window::Key::Home => Self::Home, + glfw::window::Key::End => Self::End, + glfw::window::Key::CapsLock => Self::CapsLock, + glfw::window::Key::ScrollLock => Self::ScrollLock, + glfw::window::Key::NumLock => Self::NumLock, + glfw::window::Key::PrintScreen => Self::PrintScreen, + glfw::window::Key::Pause => Self::Pause, + glfw::window::Key::F1 => Self::F1, + glfw::window::Key::F2 => Self::F2, + glfw::window::Key::F3 => Self::F3, + glfw::window::Key::F4 => Self::F4, + glfw::window::Key::F5 => Self::F5, + glfw::window::Key::F6 => Self::F6, + glfw::window::Key::F7 => Self::F7, + glfw::window::Key::F8 => Self::F8, + glfw::window::Key::F9 => Self::F9, + glfw::window::Key::F10 => Self::F10, + glfw::window::Key::F11 => Self::F11, + glfw::window::Key::F12 => Self::F12, + glfw::window::Key::F13 => Self::F13, + glfw::window::Key::F14 => Self::F14, + glfw::window::Key::F15 => Self::F15, + glfw::window::Key::F16 => Self::F16, + glfw::window::Key::F17 => Self::F17, + glfw::window::Key::F18 => Self::F18, + glfw::window::Key::F19 => Self::F19, + glfw::window::Key::F20 => Self::F20, + glfw::window::Key::F21 => Self::F21, + glfw::window::Key::F22 => Self::F22, + glfw::window::Key::F23 => Self::F23, + glfw::window::Key::F24 => Self::F24, + glfw::window::Key::F25 => Self::F25, + glfw::window::Key::Kp0 => Self::Kp0, + glfw::window::Key::Kp1 => Self::Kp1, + glfw::window::Key::Kp2 => Self::Kp2, + glfw::window::Key::Kp3 => Self::Kp3, + glfw::window::Key::Kp4 => Self::Kp4, + glfw::window::Key::Kp5 => Self::Kp5, + glfw::window::Key::Kp6 => Self::Kp6, + glfw::window::Key::Kp7 => Self::Kp7, + glfw::window::Key::Kp8 => Self::Kp8, + glfw::window::Key::Kp9 => Self::Kp9, + glfw::window::Key::KpDecimal => Self::KpDecimal, + glfw::window::Key::KpDivide => Self::KpDivide, + glfw::window::Key::KpMultiply => Self::KpMultiply, + glfw::window::Key::KpSubtract => Self::KpSubtract, + glfw::window::Key::KpAdd => Self::KpAdd, + glfw::window::Key::KpEnter => Self::KpEnter, + glfw::window::Key::KpEqual => Self::KpEqual, + glfw::window::Key::LeftShift => Self::LeftShift, + glfw::window::Key::LeftControl => Self::LeftControl, + glfw::window::Key::LeftAlt => Self::LeftAlt, + glfw::window::Key::LeftSuper => Self::LeftSuper, + glfw::window::Key::RightShift => Self::RightShift, + glfw::window::Key::RightControl => Self::RightControl, + glfw::window::Key::RightAlt => Self::RightAlt, + glfw::window::Key::RightSuper => Self::RightSuper, + glfw::window::Key::Menu => Self::Menu, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum KeyState +{ + Pressed, + Released, +} + +impl KeyState +{ + fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option<Self> + { + match glfw_key_state { + glfw::window::KeyState::Pressed => Some(Self::Pressed), + glfw::window::KeyState::Released => Some(Self::Released), + glfw::window::KeyState::Repeat => None, + } + } +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct KeyModifiers: i32 { + const SHIFT = glfw::window::KeyModifiers::SHIFT.bits(); + const CONTROL = glfw::window::KeyModifiers::CONTROL.bits(); + const ALT = glfw::window::KeyModifiers::ALT.bits(); + const SUPER = glfw::window::KeyModifiers::SUPER.bits(); + const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits(); + const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits(); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum CursorMode +{ + /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. + Disabled, + + /// Makes the cursor invisible when it is over the content area of the window but + /// does not restrict the cursor from leaving. + Hidden, + + /// Makes the cursor visible and behaving normally. + Normal, +} + +impl CursorMode +{ + fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode + { + match self { + Self::Disabled => glfw::window::CursorMode::Disabled, + Self::Hidden => glfw::window::CursorMode::Hidden, + Self::Normal => glfw::window::CursorMode::Normal, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InputMode +{ + /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be + /// enabled if available. + /// + /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It + /// is not affected by the scaling and acceleration applied to the motion of the + /// desktop cursor. That processing is suitable for a cursor while raw motion is + /// better for controlling for example a 3D camera. Because of this, raw mouse motion + /// is only provided when the cursor is disabled. + RawMouseMotion, +} + +impl InputMode +{ + fn to_glfw_input_mode(self) -> glfw::window::InputMode + { + match self { + Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion, + } + } +} + #[derive(Debug)] pub struct Extension { |