diff options
| -rw-r--r-- | Cargo.lock | 24 | ||||
| -rw-r--r-- | engine/Cargo.toml | 2 | ||||
| -rw-r--r-- | engine/src/rendering/backend/opengl.rs | 44 | ||||
| -rw-r--r-- | engine/src/rendering/main_pipeline.rs | 4 | ||||
| -rw-r--r-- | engine/src/rendering/shader.rs | 236 | ||||
| -rw-r--r-- | engine/src/rendering/shader/reflection.rs | 4 | ||||
| -rw-r--r-- | engine/src/ui/dear_imgui.rs | 2 |
7 files changed, 201 insertions, 115 deletions
@@ -647,6 +647,7 @@ dependencies = [ "raw-window-handle", "rayon", "safer-ffi", + "safer_owning_ref", "seq-macro", "serde", "serde_json", @@ -1286,6 +1287,12 @@ dependencies = [ ] [[package]] +name = "maybe-dangling" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59dbb09ed53f8e4f314e353dc6c1853ae5b4c480a668a422657804a544ea9f65" + +[[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2128,6 +2135,16 @@ dependencies = [ ] [[package]] +name = "safer_owning_ref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1202383da87cfa1ab63f548bbb12e1dc22c256f885a7c30c4e433be49e6c1ba" +dependencies = [ + "maybe-dangling", + "stable_deref_trait", +] + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2226,6 +2243,7 @@ dependencies = [ "bitflags 2.13.2", "build-rs", "shader-slang-sys", + "stable_deref_trait", ] [[package]] @@ -2365,6 +2383,12 @@ dependencies = [ ] [[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/engine/Cargo.toml b/engine/Cargo.toml index d91c43a..62a526d 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -18,6 +18,7 @@ circular-buffer = "2.0.0" intmap = "3.1.3" hashbrown = "0.15.2" rayon = "1.12.0" +safer_owning_ref = "=0.5.1" engine-macros = { workspace = true } engine-ecs = { workspace = true } engine-reflection = { workspace = true } @@ -43,6 +44,7 @@ features = ["derive"] [dependencies.shader-slang] git = "https://github.com/HampusMat/slang-rs" branch = "fixes-and-features" +features = ["stable_deref_trait"] [dependencies.dear-imgui-rs] git = "https://github.com/HampusMat/dear-imgui-rs" diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs index 0a58417..aad9b6c 100644 --- a/engine/src/rendering/backend/opengl.rs +++ b/engine/src/rendering/backend/opengl.rs @@ -124,7 +124,6 @@ use crate::rendering::shader::{ Context as ShaderContext, ContextSessionInitOpts as ShaderContextSessionInitOpts, DebugInfoLevel as ShaderDebugInfoLevel, - EntrypointFlags as ShaderEntrypointFlags, Error as ShaderError, LineDirectiveMode as ShaderLineDirectiveMode, OptimizationLevel as ShaderOptimizationLevel, @@ -953,8 +952,8 @@ fn handle_commands( info: ShaderInfo { vertex_desc: if shader_program .metadata() - .link_entrypoints - .contains(ShaderEntrypointFlags::VERTEX) + .vertex_stage_entry_point_idx + .is_some() { Some(shader_program.create_vertex_desc( backend_properties.shader_target_id, @@ -1869,26 +1868,13 @@ fn create_and_init_gl_shader_program( { let gl_shader_program = GlShaderProgram::new(current_context); - let shader_program_reflection = shader_program - .reflection(backend_properties.shader_target_id) - .expect("Not possible"); - - if shader_program - .metadata() - .link_entrypoints - .contains(ShaderEntrypointFlags::VERTEX) + if let Some(vertex_stage_entry_point_idx) = + shader_program.metadata().vertex_stage_entry_point_idx { - let Some(vs_entry_point_index) = shader_program_reflection - .entry_points() - .position(|entry_point| entry_point.stage() == ShaderStage::Vertex) - else { - unreachable!(); - }; - let gl_vertex_shader = create_and_init_gl_shader( current_context, shader_program, - vs_entry_point_index, + vertex_stage_entry_point_idx, GlShaderKind::Vertex, backend_properties, )?; @@ -1896,22 +1882,13 @@ fn create_and_init_gl_shader_program( gl_shader_program.attach(current_context, &gl_vertex_shader); } - if shader_program - .metadata() - .link_entrypoints - .contains(ShaderEntrypointFlags::FRAGMENT) + if let Some(fragment_stage_entry_point_idx) = + shader_program.metadata().fragment_stage_entry_point_idx { - let Some(fs_entry_point_index) = shader_program_reflection - .entry_points() - .position(|entry_point| entry_point.stage() == ShaderStage::Fragment) - else { - unreachable!(); - }; - let gl_fragment_shader = create_and_init_gl_shader( current_context, shader_program, - fs_entry_point_index, + fragment_stage_entry_point_idx, GlShaderKind::Fragment, backend_properties, )?; @@ -1927,14 +1904,11 @@ fn create_and_init_gl_shader_program( fn create_and_init_gl_shader( current_context: &MaybeCurrentContextWithFns, shader_program: &ShaderProgram, - entry_point_index: usize, + entry_point_index: u32, gl_shader_kind: GlShaderKind, backend_properties: &BackendProperties, ) -> Result<GlShader, GlShaderCreationError> { - let entry_point_index = u32::try_from(entry_point_index) - .expect("Shader entry point index does not fit in 32-bit unsigned int"); - let entry_point_code = shader_program .get_entry_point_code(entry_point_index, backend_properties.shader_target_id) .map_err(|err| GlShaderCreationError::GetShaderEntryPointCodeFailed { diff --git a/engine/src/rendering/main_pipeline.rs b/engine/src/rendering/main_pipeline.rs index 754b507..7caf625 100644 --- a/engine/src/rendering/main_pipeline.rs +++ b/engine/src/rendering/main_pipeline.rs @@ -401,7 +401,7 @@ fn get_or_create_shaders<'shader_ctx>( name: "main_3d_shader.slang".into(), file_path: Path::new("@engine/main_3d_shader").into(), source: include_str!("../../res/main_3d_shader.slang").into(), - link_entrypoints: ShaderEntrypointFlags::VERTEX + link_entry_points: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }, ); @@ -418,7 +418,7 @@ fn get_or_create_shaders<'shader_ctx>( name: "sky_box_shader.slang".into(), file_path: Path::new("@engine/sky_box_shader").into(), source: include_str!("../../res/sky_box_shader.slang").into(), - link_entrypoints: ShaderEntrypointFlags::VERTEX + link_entry_points: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }, ); diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs index 2a2ac7a..05fc212 100644 --- a/engine/src/rendering/shader.rs +++ b/engine/src/rendering/shader.rs @@ -5,8 +5,9 @@ use std::path::Path; use std::str::Utf8Error; use std::sync::Arc; -use bitflags::{bitflags, bitflags_match}; +use bitflags::{bitflags, Flags as _}; use ecs::phase::INIT as INIT_PHASE; +use owning_ref::OwningRef; use shader_slang::{ Blob as SlangBlob, ComponentType as SlangComponentType, @@ -33,6 +34,7 @@ use crate::ecs::phase::Phase; use crate::ecs::sole::Single; use crate::ecs::{declare_entity, pair, Sole}; use crate::mesh::vertex_buffer::VertexLabel; +use crate::rendering::shader::reflection::Stage; pub mod cursor; pub mod reflection; @@ -49,7 +51,7 @@ pub struct ModuleSource pub name: Cow<'static, str>, pub file_path: Cow<'static, Path>, pub source: Cow<'static, str>, - pub link_entrypoints: EntrypointFlags, + pub link_entry_points: EntrypointFlags, } impl Asset for ModuleSource @@ -171,23 +173,23 @@ impl Program target_id: CompileTargetId, ) -> Result<VertexDescription, VertexDescCreationError> { - if !self - .metadata - .link_entrypoints - .contains(EntrypointFlags::VERTEX) - { + let Some(vertex_stage_entry_point_idx) = + self.metadata.vertex_stage_entry_point_idx + else { return Err(VertexDescCreationError::NoVertexStageEntrypointInProgram); - } + }; let program_reflection = self .reflection(target_id) .map_err(VertexDescCreationError::GetProgramReflectionFailed)?; - VertexDescription::new( - &program_reflection - .get_entry_point_by_name("vertex_main") - .expect("Not possible"), - ) + let Some(vertex_stage_entry_point) = + program_reflection.get_entry_point_by_index(vertex_stage_entry_point_idx) + else { + unreachable!(); + }; + + VertexDescription::new(&vertex_stage_entry_point) } } @@ -301,51 +303,130 @@ impl Context self.programs.get(asset_id) } + pub fn load_module( + &self, + module_source: &ModuleSource, + ) -> Result<Module, LoadModuleError> + { + let Some(session) = self.session.as_ref() else { + return Err(LoadModuleError::ContextSessionNotInitialized); + }; + + let module = session + .load_module_from_source_string( + &module_source.name, + &module_source.file_path.to_string_lossy(), + &module_source.source, + ) + .map_err(Error)?; + + Ok(Module { inner: module }) + } + #[tracing::instrument(skip_all)] - pub fn compose_into_program( + pub fn compose_program( &self, - module: Module, - link_entrypoints: EntrypointFlags, + modules: &[Module], + link_entry_points: EntrypointFlags, + target_id: CompileTargetId, ) -> Result<Program, ComposeProgramError> { let Some(session) = self.session.as_ref() else { return Err(ComposeProgramError::ContextSessionNotInitialized); }; - let entry_points = link_entrypoints + let all_entry_points = modules .iter() - .filter_map(|entrypoint_flag| { - let entrypoint_name = bitflags_match!(entrypoint_flag, { - EntrypointFlags::VERTEX => Some("vertex_main"), - EntrypointFlags::FRAGMENT => Some("fragment_main"), - _ => None - })?; - - let Some(entry_point) = module.get_entry_point(entrypoint_name) else { - return Some(Err(ComposeProgramError::EntrypointNotFoundError { - entrypoint_name, - })); - }; - - Some(Ok(entry_point)) + .flat_map(Module::entry_points) + .enumerate() + .map(|(entry_point_index, entry_point)| { + Ok::<_, Error>(( + entry_point_index, + OwningRef::new(SlangObjectRc::<SlangComponentType>::from( + entry_point.inner, + )) + .try_map(|entry_point| { + entry_point.layout(i64::from(target_id.index)) + })?, + )) }) .collect::<Result<Vec<_>, _>>()?; - let components = entry_points - .into_iter() - .map(|entry_point| { - SlangObjectRc::<SlangComponentType>::from(entry_point.inner) - }) - .chain([SlangObjectRc::<SlangComponentType>::from(module.inner)]) - .collect::<Vec<_>>(); + let mut program_metadata = ProgramMetadata::default(); + + let mut components = Vec::with_capacity( + EntrypointFlags::iter_defined_names().count() + modules.len(), + ); + + components.extend(modules.iter().map(|module| { + SlangObjectRc::<SlangComponentType>::from(module.inner.clone()) + })); + + if link_entry_points.contains(EntrypointFlags::VERTEX) { + let mut entry_point_iter = + all_entry_points.iter().filter(|(_, entry_point)| { + Stage::from_slang( + entry_point.entry_point_by_index(0).unwrap().stage(), + ) == Stage::Vertex + }); + + let (vertex_stage_entry_point_idx, vertex_stage_entry_point) = + entry_point_iter + .next() + .ok_or(ComposeProgramError::NoEntryPointForStage(Stage::Vertex)) + .cloned()?; + + if entry_point_iter.next().is_some() { + return Err(ComposeProgramError::MultipleEntryPointsForStage( + Stage::Vertex, + )); + } + + // This is fine. It is very unlikely there are more than 2^32 entry points + program_metadata.vertex_stage_entry_point_idx = + Some(u32::try_from(vertex_stage_entry_point_idx).expect( + "Shader's vertex stage entry point index would overflow a u32", + )); + + components.push(vertex_stage_entry_point.as_owner().clone()); + } + + if link_entry_points.contains(EntrypointFlags::FRAGMENT) { + let mut entry_point_iter = + all_entry_points.iter().filter(|(_, entry_point)| { + Stage::from_slang( + entry_point.entry_point_by_index(0).unwrap().stage(), + ) == Stage::Fragment + }); + + let (fragment_stage_entry_point_idx, fragment_stage_entry_point) = + entry_point_iter + .next() + .ok_or(ComposeProgramError::NoEntryPointForStage(Stage::Fragment)) + .cloned()?; + + if entry_point_iter.next().is_some() { + return Err(ComposeProgramError::MultipleEntryPointsForStage( + Stage::Fragment, + )); + } + + // This is fine. It is very unlikely there are more than 2^32 entry points + program_metadata.fragment_stage_entry_point_idx = + Some(u32::try_from(fragment_stage_entry_point_idx).expect( + "Shader's fragment stage entry point index would overflow a u32", + )); + + components.push(fragment_stage_entry_point.as_owner().clone()); + } let program = session .create_composite_component_type(&components) - .map_err(|err| ComposeProgramError::Other(Error(err)))?; + .map_err(Error)?; Ok(Program { inner: program, - metadata: ProgramMetadata { link_entrypoints }, + metadata: program_metadata, }) } } @@ -524,11 +605,12 @@ impl CompileTargetFormat } } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct ProgramMetadata { - pub link_entrypoints: EntrypointFlags, + pub vertex_stage_entry_point_idx: Option<u32>, + pub fragment_stage_entry_point_idx: Option<u32>, } #[derive(Debug, Clone)] @@ -728,17 +810,14 @@ pub enum ComposeProgramError #[error("A shader context session has not been initialized")] ContextSessionNotInitialized, - #[error("Shader module does not have a '{entrypoint_name}' entry point")] - EntrypointNotFoundError - { - entrypoint_name: &'static str - }, + #[error("Multiple entry points found for shader stage {0:?}")] + MultipleEntryPointsForStage(Stage), - #[error("Failed to create vertex description")] - VertexDescriptionCreationFailed(#[from] VertexDescCreationError), + #[error("No entry point was found for shader stage {0:?}")] + NoEntryPointForStage(Stage), #[error(transparent)] - Other(Error), + Other(#[from] Error), } #[derive(Debug, thiserror::Error)] @@ -755,6 +834,16 @@ pub enum ContextSessionInitError } #[derive(Debug, thiserror::Error)] +pub enum LoadModuleError +{ + #[error("A shader context session has not been initialized")] + ContextSessionNotInitialized, + + #[error(transparent)] + Other(#[from] Error), +} + +#[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(#[from] shader_slang::Error); @@ -850,7 +939,7 @@ fn import( source: std::fs::read_to_string(file_path) .map_err(ImportError::ReadFileFailed)? .into(), - link_entrypoints: settings.map_or_default(|settings| settings.link_entrypoints), + link_entry_points: settings.map_or_default(|settings| settings.link_entrypoints), }); Ok(()) @@ -914,6 +1003,7 @@ fn load_modules( mut context: Single<Context>, assets: Single<Assets>, asset_events: Single<AssetEvents>, + rendering_backend_properties: Single<crate::rendering::BackendProperties>, ) -> Result<(), crate::Error> { let Ok(context) = context.get_mut() else { @@ -923,7 +1013,7 @@ fn load_modules( let assets = assets.get()?; let asset_events = asset_events.get()?; - let Some(session) = context.session.as_ref() else { + let Ok(rendering_backend_properties) = rendering_backend_properties.get() else { return Ok(()); }; @@ -938,25 +1028,35 @@ fn load_modules( unreachable!(); }; - tracing::debug!(asset_label=?asset_label, "Loading shader module"); + tracing::debug!(%asset_label, "Loading shader module"); - let module = match load_module(session, module_source) { + let module = match context.load_module(module_source) { Ok(module) => module, Err(err) => { - tracing::error!("Failed to load shader module: {err}"); + tracing::error!(%asset_label, "Failed to load shader module: {err}"); continue; } }; - if !module_source.link_entrypoints.is_empty() { - assert!(!context.programs.contains_key(asset_id)); + if !module_source.link_entry_points.is_empty() { + if context.programs.contains_key(asset_id) { + tracing::debug!( + %asset_label, + "Removing old shader program associated with asset" + ); - let shader_program = match context - .compose_into_program(module, module_source.link_entrypoints) - { + context.programs.remove(asset_id); + } + + let shader_program = match context.compose_program( + &[module], + module_source.link_entry_points, + rendering_backend_properties.shader_target_id, + ) { Ok(shader_program) => shader_program, Err(err) => { tracing::error!( + %asset_label, "Failed to compose shader into program: {:#}", crate::Error::new(err) ); @@ -967,7 +1067,7 @@ fn load_modules( let linked_shader_program = match shader_program.into_linked() { Ok(linked_shader_program) => linked_shader_program, Err(err) => { - tracing::error!("Failed to link shader: {err}"); + tracing::error!(%asset_label, "Failed to link shader: {err}"); continue; } }; @@ -978,17 +1078,3 @@ fn load_modules( Ok(()) } - -fn load_module( - session: &SlangSession, - module_source: &ModuleSource, -) -> Result<Module, Error> -{ - let module = session.load_module_from_source_string( - &module_source.name, - &module_source.file_path.to_string_lossy(), - &module_source.source, - )?; - - Ok(Module { inner: module }) -} diff --git a/engine/src/rendering/shader/reflection.rs b/engine/src/rendering/shader/reflection.rs index 10bff54..cc16ec6 100644 --- a/engine/src/rendering/shader/reflection.rs +++ b/engine/src/rendering/shader/reflection.rs @@ -36,7 +36,7 @@ impl<'a> EntryPointReflection<'a> #[must_use] pub fn stage(&self) -> Stage { - Stage::from_slang_stage(self.inner.stage()) + Stage::from_slang(self.inner.stage()) } #[must_use] @@ -722,7 +722,7 @@ pub enum Stage impl Stage { - fn from_slang_stage(stage: shader_slang::Stage) -> Self + pub(super) fn from_slang(stage: shader_slang::Stage) -> Self { match stage { shader_slang::Stage::None => Self::None, diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs index b3a102b..7822c2f 100644 --- a/engine/src/ui/dear_imgui.rs +++ b/engine/src/ui/dear_imgui.rs @@ -359,7 +359,7 @@ fn initialize_context( name: "imgui_shader.slang".into(), file_path: Path::new("@engine/imgui_shader").into(), source: include_str!("../../res/imgui_shader.slang").into(), - link_entrypoints: ShaderEntrypointFlags::VERTEX + link_entry_points: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }, ); |
