diff options
Diffstat (limited to 'engine/src/rendering/shader.rs')
| -rw-r--r-- | engine/src/rendering/shader.rs | 991 |
1 files changed, 991 insertions, 0 deletions
diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs new file mode 100644 index 0000000..3fdc2c1 --- /dev/null +++ b/engine/src/rendering/shader.rs @@ -0,0 +1,991 @@ +use std::any::type_name; +use std::borrow::Cow; +use std::fmt::{Debug, Display, Write}; +use std::path::Path; +use std::str::Utf8Error; +use std::sync::Arc; + +use bitflags::{bitflags, bitflags_match}; +use ecs::phase::INIT as INIT_PHASE; +use shader_slang::{ + Blob as SlangBlob, + ComponentType as SlangComponentType, + EntryPoint as SlangEntryPoint, + GlobalSession as SlangGlobalSession, + Module as SlangModule, + Session as SlangSession, +}; + +use crate::asset::{ + Asset, + Assets, + Event as AssetEvent, + Events as AssetEvents, + Handle as AssetHandle, + Id as AssetId, + Submitter as AssetSubmitter, + HANDLE_ASSETS_PHASE, +}; +use crate::builder; +use crate::ecs::pair::ChildOf; +use crate::ecs::phase::Phase; +use crate::ecs::sole::Single; +use crate::ecs::{declare_entity, pair, Sole}; +use crate::mesh::vertex_buffer::VertexLabel; + +pub mod cursor; +pub mod reflection; + +pub const STD_VERT_IN_SEM_NAME_POSITION: &str = "STD_POSITION"; +pub const STD_VERT_IN_SEM_NAME_NORMAL: &str = "STD_NORMAL"; +pub const STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT: &str = "STD_UV_FROM_TOP_LEFT"; +pub const STD_VERT_IN_SEM_NAME_COLOR: &str = "STD_COLOR"; + +/// Shader module. +#[derive(Debug)] +pub struct ModuleSource +{ + pub name: Cow<'static, str>, + pub file_path: Cow<'static, Path>, + pub source: Cow<'static, str>, + pub link_entrypoints: EntrypointFlags, +} + +impl Asset for ModuleSource +{ + fn visit_dependencies<'this>( + &'this self, + _func: &mut dyn FnMut(crate::asset::Dependency<'this>), + ) + { + } +} + +bitflags! { + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] + pub struct EntrypointFlags: usize + { + const FRAGMENT = 1 << 0; + const VERTEX = 1 << 1; + } +} + +#[derive(Clone)] +pub struct Module +{ + inner: SlangModule, +} + +impl Module +{ + #[must_use] + pub fn entry_points(&self) -> impl ExactSizeIterator<Item = EntryPoint> + use<'_> + { + self.inner + .entry_points() + .map(|entry_point| EntryPoint { inner: entry_point }) + } + + #[must_use] + pub fn get_entry_point(&self, entry_point: &str) -> Option<EntryPoint> + { + let entry_point = self.inner.find_entry_point_by_name(entry_point)?; + + Some(EntryPoint { inner: entry_point }) + } + + #[must_use] + pub fn file_path(&self) -> &str + { + self.inner.file_path() + } +} + +pub struct EntryPoint +{ + inner: SlangEntryPoint, +} + +impl EntryPoint +{ + #[must_use] + pub fn function_reflection(&self) -> self::reflection::Function<'_> + { + self::reflection::Function { + inner: self.inner.function_reflection(), + } + } +} + +#[derive(Clone)] +pub struct Program +{ + inner: SlangComponentType, + metadata: ProgramMetadata, +} + +impl Program +{ + pub fn into_linked(self) -> Result<Program, Error> + { + let linked_program = self.inner.link()?; + + Ok(Program { + inner: linked_program, + metadata: self.metadata, + }) + } + + #[must_use] + pub fn metadata(&self) -> &ProgramMetadata + { + &self.metadata + } + + pub fn get_entry_point_code( + &self, + entry_point_index: u32, + target_id: CompileTargetId, + ) -> Result<Blob, Error> + { + let blob = self + .inner + .entry_point_code(entry_point_index.into(), i64::from(target_id.index))?; + + Ok(Blob { inner: blob }) + } + + pub fn reflection( + &self, + target_id: CompileTargetId, + ) -> Result<self::reflection::Program<'_>, Error> + { + let reflection = self.inner.layout(i64::from(target_id.index))?; + + Ok(self::reflection::Program { inner: reflection }) + } + + pub fn create_vertex_desc( + &self, + target_id: CompileTargetId, + ) -> Result<VertexDescription, VertexDescCreationError> + { + if !self + .metadata + .link_entrypoints + .contains(EntrypointFlags::VERTEX) + { + 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"), + ) + } +} + +impl Debug for Program +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter + .debug_struct(type_name::<Self>()) + .finish_non_exhaustive() + } +} + +pub struct Blob +{ + inner: SlangBlob, +} + +impl Blob +{ + #[must_use] + pub fn as_bytes(&self) -> &[u8] + { + self.inner.as_slice() + } + + #[allow(clippy::missing_errors_doc)] + pub fn as_str(&self) -> Result<&str, Utf8Error> + { + self.inner.as_str() + } +} + +builder! { +#[builder(name = SettingsBuilder, derives=(Debug))] +#[derive(Debug)] +#[non_exhaustive] +pub struct Settings +{ + link_entrypoints: EntrypointFlags, +} +} + +#[derive(Sole)] +pub struct Context +{ + global_session: SlangGlobalSession, + session: Option<SlangSession>, + programs: hashbrown::HashMap<AssetId, Program>, +} + +impl Context +{ + #[allow(clippy::missing_errors_doc)] + pub fn init_session( + &mut self, + opts: &ContextSessionInitOpts, + compile_targets: &[&CompileTarget], + ) -> Result<Vec<CompileTargetId>, ContextSessionInitError> + { + if self.session.is_some() { + return Err(ContextSessionInitError::AlreadyInitialized); + } + + if compile_targets.len() > u32::MAX as usize { + return Err(ContextSessionInitError::TooManyCompileTargets); + } + + let session_options = shader_slang::CompilerOptions::default() + .matrix_layout_row(true) // Matrices must be row major + .optimization(opts.optimization_level.into_slang()) + .debug_information(opts.debug_info_level.into_slang()) + .line_directive_mode(opts.line_directive_mode.into_slang()) + .no_mangle(opts.no_mangle); + + let (compile_target_ids, target_descs) = compile_targets + .iter() + .enumerate() + .map(|(index, compile_target)| { + // The length of compile_targets is not greater than u32::MAX + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + ( + CompileTargetId { index: index as u32 }, + shader_slang::TargetDesc::default() + .format(compile_target.format.into_slang()) + // For some reason, the compiler options need to be specified + // for each target as well. Otherwise, they are not used + .options(&session_options), + ) + }) + .collect::<(Vec<_>, Vec<_>)>(); + + let session_desc = shader_slang::SessionDesc::default() + .targets(&target_descs) + .search_paths([""]) + .options(&session_options); + + let Some(session) = self.global_session.create_session(&session_desc) else { + tracing::error!("Failed to create shader-slang session"); + return Err(ContextSessionInitError::Internal); + }; + + self.session = Some(session); + + Ok(compile_target_ids) + } + + #[must_use] + pub fn get_program(&self, asset_id: &AssetId) -> Option<&Program> + { + self.programs.get(asset_id) + } + + #[tracing::instrument(skip_all, fields(module_file = module.file_path()))] + pub fn compose_into_program( + &self, + module: Module, + link_entrypoints: EntrypointFlags, + ) -> Result<Program, ComposeProgramError> + { + let Some(session) = self.session.as_ref() else { + return Err(ComposeProgramError::ContextSessionNotInitialized); + }; + + let entry_points = link_entrypoints + .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)) + }) + .collect::<Result<Vec<_>, _>>()?; + + let components = entry_points + .into_iter() + .map(|entry_point| SlangComponentType::from(entry_point.inner)) + .chain([SlangComponentType::from(module.inner)]) + .collect::<Vec<_>>(); + + let program = session + .create_composite_component_type(&components) + .map_err(|err| ComposeProgramError::Other(Error(err)))?; + + Ok(Program { + inner: program, + metadata: ProgramMetadata { link_entrypoints }, + }) + } +} + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ContextSessionInitOpts +{ + pub optimization_level: OptimizationLevel, + pub debug_info_level: DebugInfoLevel, + pub line_directive_mode: LineDirectiveMode, + pub no_mangle: bool, +} + +impl ContextSessionInitOpts +{ + pub fn optimization_level(mut self, optimization_level: OptimizationLevel) -> Self + { + self.optimization_level = optimization_level; + self + } + + pub fn debug_info_level(mut self, debug_info_level: DebugInfoLevel) -> Self + { + self.debug_info_level = debug_info_level; + self + } + + pub fn line_directive_mode(mut self, line_directive_mode: LineDirectiveMode) -> Self + { + self.line_directive_mode = line_directive_mode; + self + } + + pub fn no_mangle(mut self, no_mangle: bool) -> Self + { + self.no_mangle = no_mangle; + self + } +} + +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub enum OptimizationLevel +{ + /// Don't optimize at all. + None, + + /// Default optimization level: balance code quality and compilation time. + #[default] + Default, + + /// Optimize aggressively. + High, + + /// Include optimizations that may take a very long time, or may involve severe + /// space-vs-speed tradeoffs + Maximal, +} + +impl OptimizationLevel +{ + fn into_slang(self) -> shader_slang::OptimizationLevel + { + match self { + Self::None => shader_slang::OptimizationLevel::None, + Self::Default => shader_slang::OptimizationLevel::Default, + Self::High => shader_slang::OptimizationLevel::High, + Self::Maximal => shader_slang::OptimizationLevel::Maximal, + } + } +} + +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub enum DebugInfoLevel +{ + /// Don't emit debug information at all. + #[default] + None, + + /// Emit as little debug information as possible, while still supporting stack + /// trackers. + Minimal, + + /// Emit whatever is the standard level of debug information for each target. + Standard, + + /// Emit as much debug information as possible for each target. + Maximal, +} + +impl DebugInfoLevel +{ + fn into_slang(self) -> shader_slang::DebugInfoLevel + { + match self { + Self::None => shader_slang::DebugInfoLevel::None, + Self::Minimal => shader_slang::DebugInfoLevel::Minimal, + Self::Standard => shader_slang::DebugInfoLevel::Standard, + Self::Maximal => shader_slang::DebugInfoLevel::Maximal, + } + } +} + +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub enum LineDirectiveMode +{ + /// Don't emit line directives at all. + #[default] + None, + + /// Emit standard C-style `#line` directives. + Standard, + + /// Emit GLSL-style directives with file *number* instead of name + Glsl, + + /// Use a source map to track line mappings (ie no #line will appear in emitting + /// source) + SourceMap, +} + +impl LineDirectiveMode +{ + fn into_slang(self) -> shader_slang::LineDirectiveMode + { + match self { + Self::None => shader_slang::LineDirectiveMode::None, + Self::Standard => shader_slang::LineDirectiveMode::Standard, + Self::Glsl => shader_slang::LineDirectiveMode::Glsl, + Self::SourceMap => shader_slang::LineDirectiveMode::SourceMap, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CompileTargetId +{ + index: u32, +} + +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct CompileTarget +{ + pub format: CompileTargetFormat, +} + +impl CompileTarget +{ + #[must_use] + pub fn format(mut self, format: CompileTargetFormat) -> Self + { + self.format = format; + self + } +} + +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub enum CompileTargetFormat +{ + #[default] + Glsl, +} + +impl CompileTargetFormat +{ + fn into_slang(self) -> shader_slang::CompileTarget + { + match self { + Self::Glsl => shader_slang::CompileTarget::Glsl, + } + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct ProgramMetadata +{ + pub link_entrypoints: EntrypointFlags, +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct VertexDescription +{ + pub inputs: Arc<[VertexInputDescription]>, +} + +impl VertexDescription +{ + #[tracing::instrument(skip_all, fields(vs_entry_point_name = vs_entrypoint.name()))] + fn new( + vs_entrypoint: &self::reflection::EntryPointReflection<'_>, + ) -> Result<Self, VertexDescCreationError> + { + let inputs = VarInputDfsIter::new(vs_entrypoint).inspect(|var_input| { + if var_input.type_layout.kind() != self::reflection::TypeKind::Struct { + return; + } + + let Some(semantic_name) = var_input.var_layout.semantic_name() else { + return; + }; + + tracing::warn!( + "Semantic name '{}' of '{}' will be inherited by it's fields", + semantic_name, + var_input.var_layout.name().unwrap_or("<unnamed>") + ); + }); + + let mut seen_inputs = hashbrown::HashSet::<VertexInputSemName>::new(); + + Ok(Self { + inputs: inputs + .filter(|var_input| { + var_input.type_layout.kind() != self::reflection::TypeKind::Struct + }) + .map(|var_input| { + let name = var_input.var_layout.name().unwrap_or("<unnamed>"); + + let semantic_name = + var_input.var_layout.semantic_name().ok_or_else(|| { + VertexDescCreationError::VertexInputMissingSemanticName { + name: name.to_owned(), + } + })?; + + let semantic_name = + VertexInputSemName::from_semantic_name(semantic_name); + + if seen_inputs.contains(&semantic_name) { + return Err( + VertexDescCreationError::VertexInputHasOccupiedSemanticName { + name: name.to_owned(), + }, + ); + } + + let ( + self::reflection::TypeKind::Scalar + | self::reflection::TypeKind::Vector, + Some(scalar_type), + ) = ( + var_input.type_layout.kind(), + var_input.type_layout.scalar_type(), + ) + else { + return Err( + VertexDescCreationError::UnsupportedVertexInputType { + name: name.to_owned(), + }, + ); + }; + + seen_inputs.insert(semantic_name.clone()); + + Ok(VertexInputDescription { + semantic_name, + index: var_input.index, + type_kind: var_input.type_layout.kind(), + scalar_type, + }) + }) + .collect::<Result<Vec<_>, _>>()? + .into(), + }) + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub struct VertexInputDescription +{ + pub semantic_name: VertexInputSemName, + pub index: usize, + pub type_kind: self::reflection::TypeKind, + pub scalar_type: self::reflection::ScalarType, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum VertexInputSemName +{ + Position, + Normal, + UvFromTopLeft, + Color, + Other(Box<str>), +} + +impl VertexInputSemName +{ + fn from_semantic_name(semantic_name: &str) -> Self + { + match semantic_name { + STD_VERT_IN_SEM_NAME_POSITION => Self::Position, + STD_VERT_IN_SEM_NAME_NORMAL => Self::Normal, + STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT => Self::UvFromTopLeft, + STD_VERT_IN_SEM_NAME_COLOR => Self::Color, + _ => Self::Other(semantic_name.to_lowercase().into_boxed_str()), + } + } + + #[must_use] + pub fn matches_vertex_label(&self, vertex_label: &VertexLabel) -> bool + { + match (self, vertex_label) { + (Self::Position, VertexLabel::Position) + | (Self::Normal, VertexLabel::Normal) + | (Self::UvFromTopLeft, VertexLabel::UvFromTopLeft) + | (Self::Color, VertexLabel::Color) => true, + (Self::Other(other), VertexLabel::Other(other_vertex_label)) => { + **other == *other_vertex_label + } + _ => false, + } + } +} + +impl Display for VertexInputSemName +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + match self { + Self::Position => formatter.write_str(STD_VERT_IN_SEM_NAME_POSITION), + Self::Normal => formatter.write_str(STD_VERT_IN_SEM_NAME_NORMAL), + Self::UvFromTopLeft => { + formatter.write_str(STD_VERT_IN_SEM_NAME_UV_FROM_TOP_LEFT) + } + Self::Color => formatter.write_str(STD_VERT_IN_SEM_NAME_COLOR), + Self::Other(other) => { + for character in other.chars() { + formatter.write_char(character.to_ascii_uppercase())?; + } + + Ok(()) + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum VertexDescCreationError +{ + #[error("Shader program has no entry point in vertex stage")] + NoVertexStageEntrypointInProgram, + + #[error("Failed to get shader program reflection")] + GetProgramReflectionFailed(#[source] Error), + + #[error("Type of vertex input '{name}' is not supported")] + UnsupportedVertexInputType + { + name: String + }, + + #[error("Vertex input '{name}' is missing a semantic name")] + VertexInputMissingSemanticName + { + name: String + }, + + #[error( + "Vertex input '{name}' has a semantic name already used by another vertex input" + )] + VertexInputHasOccupiedSemanticName + { + name: String + }, +} + +#[derive(Debug, thiserror::Error)] +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("Failed to create vertex description")] + VertexDescriptionCreationFailed(#[from] VertexDescCreationError), + + #[error(transparent)] + Other(Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum ContextSessionInitError +{ + #[error("A shader context session has already been initialized")] + AlreadyInitialized, + + #[error("Too many compile targets. Count would overflow a u32")] + TooManyCompileTargets, + + #[error("An internal error occurred")] + Internal, +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] shader_slang::Error); + +struct VarInputDfsIter<'a> +{ + stack: Vec<( + self::reflection::VariableLayout<'a>, + self::reflection::TypeLayout<'a>, + usize, + )>, +} + +impl<'a> VarInputDfsIter<'a> +{ + fn new(entry_point: &self::reflection::EntryPointReflection<'a>) -> Self + { + Self { + stack: entry_point + .parameters() + .map(|param| { + let Some(param_type_layout) = param.type_layout() else { + // I do not know in which cases type_layout can return None + unimplemented!(); + }; + + (param, param_type_layout, 0) + }) + .collect(), + } + } +} + +impl<'a> Iterator for VarInputDfsIter<'a> +{ + type Item = VarInput<'a>; + + fn next(&mut self) -> Option<Self::Item> + { + let (var_layout, type_layout, index) = loop { + let (var_layout, type_layout, acc_offset) = self.stack.pop()?; + + if let Some(offset) = var_layout.varying_input_offset() { + break (var_layout, type_layout, acc_offset + offset); + } + }; + + if type_layout.kind() == self::reflection::TypeKind::Struct { + self.stack.extend(type_layout.fields().map(|field| { + let Some(field_type_layout) = field.type_layout() else { + // I do not know in which cases type_layout can return + // None + unimplemented!(); + }; + + (field, field_type_layout, index) + })); + } + + Some(VarInput { var_layout, type_layout, index }) + } +} + +struct VarInput<'a> +{ + var_layout: self::reflection::VariableLayout<'a>, + type_layout: self::reflection::TypeLayout<'a>, + index: usize, +} + +fn import( + asset_submitter: &mut AssetSubmitter<'_>, + file_path: &Path, + settings: Option<&'_ Settings>, +) -> Result<(), ImportError> +{ + if file_path.extension().is_none_or(|ext| ext != "slang") { + return Err(ImportError::UnsupportedFileType); + } + + let file_name = file_path + .file_name() + .ok_or(ImportError::NoPathFileName)? + .to_str() + .ok_or(ImportError::PathFileNameNotUtf8)?; + + let file_path_canonicalized = file_path + .canonicalize() + .map_err(ImportError::CanonicalizePathFailed)?; + + asset_submitter.submit_store(ModuleSource { + name: file_name.to_owned().into(), + file_path: file_path_canonicalized.into(), + source: std::fs::read_to_string(file_path) + .map_err(ImportError::ReadFileFailed)? + .into(), + link_entrypoints: settings.map_or_default(|settings| settings.link_entrypoints), + }); + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +enum ImportError +{ + #[error("File type is unsupported")] + UnsupportedFileType, + + #[error("Failed to read file")] + ReadFileFailed(#[source] std::io::Error), + + #[error("Asset path does not have a file name")] + NoPathFileName, + + #[error("Asset path file name is not valid UTF8")] + PathFileNameNotUtf8, + + #[error("Failed to canonicalize asset path")] + CanonicalizePathFailed(#[source] std::io::Error), +} + +declare_entity! { +pub IMPORT_SHADERS_PHASE: (Phase, pair!(ChildOf, { *HANDLE_ASSETS_PHASE })); +} + +pub(super) fn prepare(collector: &mut crate::ecs::extension::Collector<'_>) +{ + let Some(global_session) = SlangGlobalSession::new() else { + tracing::error!("Unable to create global shader-slang session"); + return; + }; + + collector + .add_sole(Context { + global_session, + session: None, + programs: hashbrown::HashMap::new(), + }) + .ok(); + + collector.spawn_declared_entity(&IMPORT_SHADERS_PHASE); + + collector.add_system(*INIT_PHASE, initialize); + collector.add_system(*IMPORT_SHADERS_PHASE, load_modules); +} + +fn initialize(mut assets: Single<Assets>) -> Result<(), crate::Error> +{ + let assets = assets.get_mut()?; + + assets.set_importer::<ModuleSource, Settings, ImportError>(import); + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn load_modules( + mut context: Single<Context>, + assets: Single<Assets>, + asset_events: Single<AssetEvents>, +) -> Result<(), crate::Error> +{ + let Ok(context) = context.get_mut() else { + unreachable!(); + }; + + let assets = assets.get()?; + let asset_events = asset_events.get()?; + + let Some(session) = context.session.as_ref() else { + return Ok(()); + }; + + for AssetEvent::Loaded(asset_id, asset_label) in asset_events.iter_last_tick() { + let asset_handle = AssetHandle::<ModuleSource>::from_id(*asset_id); + + if !assets.is_loaded_and_has_type(&asset_handle) { + continue; + } + + let Some(module_source) = assets.get(&asset_handle) else { + unreachable!(); + }; + + tracing::debug!(asset_label=?asset_label, "Loading shader module"); + + let module = match load_module(session, module_source) { + Ok(module) => module, + Err(err) => { + tracing::error!("Failed to load shader module: {err}"); + continue; + } + }; + + if !module_source.link_entrypoints.is_empty() { + assert!(!context.programs.contains_key(asset_id)); + + let shader_program = match context + .compose_into_program(module, module_source.link_entrypoints) + { + Ok(shader_program) => shader_program, + Err(err) => { + tracing::error!( + "Failed to compose shader into program: {:#}", + crate::Error::new(err) + ); + continue; + } + }; + + 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}"); + continue; + } + }; + + context.programs.insert(*asset_id, linked_shader_program); + } + } + + 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 }) +} |
