use std::any::type_name; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; 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, DebugInfoLevel as SlangDebugInfoLevel, EntryPoint as SlangEntryPoint, GlobalSession as SlangGlobalSession, Module as SlangModule, ParameterCategory as SlangParameterCategory, ScalarType as SlangScalarType, Session as SlangSession, TypeKind as SlangTypeKind, }; use crate::asset::{ Assets, Event as AssetEvent, 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, POST_UPDATE as POST_UPDATE_PHASE}; use crate::ecs::sole::Single; use crate::ecs::{declare_entity, pair, Component, Sole}; use crate::mesh::vertex_buffer::VertexLabel; pub mod cursor; pub mod default; pub const STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION: &str = "STD_POSITION"; pub const STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL: &str = "STD_NORMAL"; pub const STD_VERTEX_INPUT_SEMANTIC_NAME_UV: &str = "STD_UV"; pub const STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR: &str = "STD_COLOR"; #[derive(Debug, Clone, Component)] pub struct Shader { pub asset_handle: AssetHandle, } /// 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, } 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 { pub fn entry_points(&self) -> impl ExactSizeIterator + use<'_> { self.inner .entry_points() .map(|entry_point| EntryPoint { inner: entry_point }) } pub fn get_entry_point(&self, entry_point: &str) -> Option { let entry_point = self.inner.find_entry_point_by_name(entry_point)?; Some(EntryPoint { inner: entry_point }) } pub fn file_path(&self) -> &str { self.inner.file_path() } } pub struct EntryPoint { inner: SlangEntryPoint, } impl EntryPoint { pub fn function(&self) -> FunctionReflection<'_> { FunctionReflection { inner: self.inner.function_reflection(), } } } pub struct FunctionReflection<'a> { inner: &'a shader_slang::reflection::Function, } impl<'a> FunctionReflection<'a> { pub fn name(&self) -> Option<&str> { self.inner.name() } } pub struct EntryPointReflection<'a> { inner: &'a shader_slang::reflection::EntryPoint, } impl<'a> EntryPointReflection<'a> { pub fn name(&self) -> Option<&str> { self.inner.name() } pub fn name_override(&self) -> Option<&str> { self.inner.name_override() } pub fn stage(&self) -> Stage { Stage::from_slang_stage(self.inner.stage()) } pub fn parameters(&self) -> impl ExactSizeIterator> { self.inner .parameters() .map(|param| VariableLayout { inner: param }) } pub fn var_layout(&self) -> Option> { Some(VariableLayout { inner: self.inner.var_layout()? }) } } #[derive(Clone)] pub struct Program { inner: SlangComponentType, metadata: ProgramMetadata, } impl Program { pub fn into_linked(self) -> Result { let linked_program = self.inner.link()?; Ok(Program { inner: linked_program, metadata: self.metadata, }) } pub fn metadata(&self) -> &ProgramMetadata { &self.metadata } pub fn get_entry_point_code(&self, entry_point_index: u32) -> Result { let blob = self.inner.entry_point_code(entry_point_index.into(), 0)?; Ok(Blob { inner: blob }) } pub fn reflection(&self, target: u32) -> Result, Error> { let reflection = self.inner.layout(target as i64)?; Ok(ProgramReflection { inner: reflection }) } } impl Debug for Program { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter .debug_struct(type_name::()) .finish_non_exhaustive() } } pub struct ProgramReflection<'a> { inner: &'a shader_slang::reflection::Shader, } impl<'a> ProgramReflection<'a> { pub fn get_entry_point_by_index(&self, index: u32) -> Option> { Some(EntryPointReflection { inner: self.inner.entry_point_by_index(index)?, }) } pub fn get_entry_point_by_name(&self, name: &str) -> Option> { Some(EntryPointReflection { inner: self.inner.find_entry_point_by_name(name)?, }) } pub fn entry_points( &self, ) -> impl ExactSizeIterator> + use<'a> { self.inner .entry_points() .map(|entry_point| EntryPointReflection { inner: entry_point }) } pub fn global_params_type_layout(&self) -> Option> { Some(TypeLayout { inner: self.inner.global_params_type_layout()?, }) } pub fn global_params_var_layout(&self) -> Option> { Some(VariableLayout { inner: self.inner.global_params_var_layout()?, }) } pub fn get_type(&self, name: &str) -> Option> { Some(TypeReflection { inner: self.inner.find_type_by_name(name)?, }) } pub fn get_type_layout(&self, ty: &TypeReflection<'a>) -> Option> { Some(TypeLayout { inner: self .inner .type_layout(&ty.inner, shader_slang::LayoutRules::Default)?, }) } } #[derive(Clone, Copy)] pub struct VariableLayout<'a> { inner: &'a shader_slang::reflection::VariableLayout, } impl<'a> VariableLayout<'a> { pub fn name(&self) -> Option<&'a str> { self.inner.name() } pub fn semantic_name(&self) -> Option<&str> { self.inner.semantic_name() } pub fn binding_index(&self) -> u32 { self.inner .offset(shader_slang::ParameterCategory::DescriptorTableSlot) as u32 // self.inner.binding_index() } pub fn varying_input_offset(&self) -> Option { if !self .inner .categories() .any(|category| category == SlangParameterCategory::VaryingInput) { return None; } Some(self.inner.offset(SlangParameterCategory::VaryingInput)) } pub fn binding_space(&self) -> u32 { self.inner.binding_space() } pub fn semantic_index(&self) -> usize { self.inner.semantic_index() } pub fn offset(&self) -> usize { self.inner.offset(shader_slang::ParameterCategory::Uniform) } pub fn ty(&self) -> Option> { self.inner.ty().map(|ty| TypeReflection { inner: ty }) } pub fn type_layout(&self) -> Option> { Some(TypeLayout { inner: self.inner.type_layout()? }) } } #[derive(Clone, Copy)] pub struct TypeLayout<'a> { inner: &'a shader_slang::reflection::TypeLayout, } impl<'a> TypeLayout<'a> { pub fn kind(&self) -> TypeKind { TypeKind::from_slang_type_kind(self.inner.kind()) } pub fn scalar_type(&self) -> Option { Some(ScalarType::from_slang_scalar_type( self.inner.scalar_type()?, )) } pub fn resource_shape(&self) -> Option { Some(ResourceShape::from_bits_retain( self.inner.resource_shape()? as u32, )) } pub fn get_field_by_name(&self, name: &str) -> Option> { let index = self.inner.find_field_index_by_name(name); if index < 0 { return None; } let index = u32::try_from(index.cast_unsigned()).expect("Should not happend"); let field = self.inner.field_by_index(index)?; Some(VariableLayout { inner: field }) } pub fn parameter_category(&self) -> ParameterCategory { ParameterCategory::from_slang_parameter_category(self.inner.parameter_category()) } pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64 { self.inner.binding_range_descriptor_set_index(index) } pub fn get_field_binding_range_offset_by_name(&self, name: &str) -> Option { let field_index = self.inner.find_field_index_by_name(name); if field_index < 0 { return None; } let field_binding_range_offset = self.inner.field_binding_range_offset(field_index); if field_binding_range_offset < 0 { return None; } Some(field_binding_range_offset.cast_unsigned()) } pub fn ty(&self) -> Option> { self.inner.ty().map(|ty| TypeReflection { inner: ty }) } pub fn fields(&self) -> FieldIter<'a> { FieldIter { type_layout: self.clone(), cnt: self.field_cnt(), index: 0, } } pub fn field_cnt(&self) -> u32 { self.inner.field_count() } pub fn element_cnt(&self) -> Option { self.inner.element_count() } pub fn row_cnt(&self) -> Option { self.inner.row_count() } pub fn column_cnt(&self) -> Option { self.inner.column_count() } pub fn element_type_layout(&self) -> Option> { self.inner .element_type_layout() .map(|type_layout| TypeLayout { inner: type_layout }) } pub fn element_var_layout(&self) -> Option> { self.inner .element_var_layout() .map(|var_layout| VariableLayout { inner: var_layout }) } pub fn container_var_layout(&self) -> Option> { self.inner .container_var_layout() .map(|var_layout| VariableLayout { inner: var_layout }) } pub fn uniform_size(&self) -> Option { // tracing::debug!( // "uniform_size: {:?} categories: {:?}", // self.inner.name(), // self.inner.categories().collect::>(), // ); if !self .inner .categories() .any(|category| category == SlangParameterCategory::Uniform) { return None; } // let category = self.inner.categories().next().unwrap(); // println!( // "AARGH size Category: {category:?} Category count: {}", // self.inner.category_count() // ); // Some(self.inner.size(category)) Some(self.inner.size(SlangParameterCategory::Uniform)) } pub fn stride(&self) -> usize { self.inner.stride(self.inner.categories().next().unwrap()) } } pub struct FieldIter<'a> { type_layout: TypeLayout<'a>, cnt: u32, index: u32, } impl<'a> Iterator for FieldIter<'a> { type Item = VariableLayout<'a>; fn next(&mut self) -> Option { if self.index == self.cnt { return None; } let Some(field) = self.type_layout.inner.field_by_index(self.index) else { unreachable!(); }; self.index += 1; Some(VariableLayout { inner: field }) } fn size_hint(&self) -> (usize, Option) { let len = (self.cnt - self.index) as usize; (len, Some(len)) } } impl ExactSizeIterator for FieldIter<'_> { fn len(&self) -> usize { (self.cnt - self.index) as usize } } impl DoubleEndedIterator for FieldIter<'_> { fn next_back(&mut self) -> Option { if self.cnt == 0 || self.index == self.cnt - 1 { return None; } let Some(field) = self.type_layout.inner.field_by_index(self.cnt - 1) else { unreachable!(); }; self.cnt -= 1; Some(VariableLayout { inner: field }) } } pub struct TypeReflection<'a> { inner: &'a shader_slang::reflection::Type, } impl TypeReflection<'_> { pub fn kind(&self) -> TypeKind { TypeKind::from_slang_type_kind(self.inner.kind()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum TypeKind { None, Struct, Enum, Array, Matrix, Vector, Scalar, ConstantBuffer, Resource, SamplerState, TextureBuffer, ShaderStorageBuffer, ParameterBlock, GenericTypeParameter, Interface, OutputStream, MeshOutput, Specialized, Feedback, Pointer, DynamicResource, Count, } impl TypeKind { fn from_slang_type_kind(type_kind: SlangTypeKind) -> Self { match type_kind { SlangTypeKind::None => Self::None, SlangTypeKind::Struct => Self::Struct, SlangTypeKind::Enum => Self::Enum, SlangTypeKind::Array => Self::Array, SlangTypeKind::Matrix => Self::Matrix, SlangTypeKind::Vector => Self::Vector, SlangTypeKind::Scalar => Self::Scalar, SlangTypeKind::ConstantBuffer => Self::ConstantBuffer, SlangTypeKind::Resource => Self::Resource, SlangTypeKind::SamplerState => Self::SamplerState, SlangTypeKind::TextureBuffer => Self::TextureBuffer, SlangTypeKind::ShaderStorageBuffer => Self::ShaderStorageBuffer, SlangTypeKind::ParameterBlock => Self::ParameterBlock, SlangTypeKind::GenericTypeParameter => Self::GenericTypeParameter, SlangTypeKind::Interface => Self::Interface, SlangTypeKind::OutputStream => Self::OutputStream, SlangTypeKind::MeshOutput => Self::MeshOutput, SlangTypeKind::Specialized => Self::Specialized, SlangTypeKind::Feedback => Self::Feedback, SlangTypeKind::Pointer => Self::Pointer, SlangTypeKind::DynamicResource => Self::DynamicResource, SlangTypeKind::Count => Self::Count, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum ScalarType { None, Void, Bool, Int32, Uint32, Int64, Uint64, Float16, Float32, Float64, Int8, Uint8, Int16, Uint16, Intptr, Uintptr, Bfloat16, FloatE4m3, FloatE5m2, } impl ScalarType { fn from_slang_scalar_type(scalar_type: SlangScalarType) -> Self { match scalar_type { SlangScalarType::None => Self::None, SlangScalarType::Void => Self::Void, SlangScalarType::Bool => Self::Bool, SlangScalarType::Int32 => Self::Int32, SlangScalarType::Uint32 => Self::Uint32, SlangScalarType::Int64 => Self::Int64, SlangScalarType::Uint64 => Self::Uint64, SlangScalarType::Float16 => Self::Float16, SlangScalarType::Float32 => Self::Float32, SlangScalarType::Float64 => Self::Float64, SlangScalarType::Int8 => Self::Int8, SlangScalarType::Uint8 => Self::Uint8, SlangScalarType::Int16 => Self::Int16, SlangScalarType::Uint16 => Self::Uint16, SlangScalarType::Intptr => Self::Intptr, SlangScalarType::Uintptr => Self::Uintptr, SlangScalarType::Bfloat16 => Self::Bfloat16, SlangScalarType::FloatE4m3 => Self::FloatE4m3, SlangScalarType::FloatE5m2 => Self::FloatE5m2, #[allow(unreachable_patterns)] _ => unimplemented!("conversion from slang scalar type"), } } } bitflags! { #[derive(Debug, Copy, Clone)] pub struct ResourceShape: u32 { const BASE = shader_slang::ResourceShape::SlangResourceBaseShapeMask as u32; const NONE = shader_slang::ResourceShape::SlangResourceNone as u32; const TEXTURE_1D = shader_slang::ResourceShape::SlangTexture1d as u32; const TEXTURE_2D = shader_slang::ResourceShape::SlangTexture2d as u32; const TEXTURE_3D = shader_slang::ResourceShape::SlangTexture3d as u32; const TEXTURE_CUBE = shader_slang::ResourceShape::SlangTextureCube as u32; const TEXTURE_BUFFER = shader_slang::ResourceShape::SlangTextureBuffer as u32; const STRUCTURED_BUFFER = shader_slang::ResourceShape::SlangStructuredBuffer as u32; const BYTE_ADDRESS_BUFFER = shader_slang::ResourceShape::SlangByteAddressBuffer as u32; const UNKNOWN = shader_slang::ResourceShape::SlangResourceUnknown as u32; const ACCELERATION_STRUCTURE = shader_slang::ResourceShape::SlangAccelerationStructure as u32; const TEXTURE_SUBPASS = shader_slang::ResourceShape::SlangTextureSubpass as u32; const EXT_SHAPE = shader_slang::ResourceShape::SlangResourceExtShapeMask as u32; const TEXTURE_FEEDBACK_FLAG = shader_slang::ResourceShape::SlangTextureFeedbackFlag as u32; const TEXTURE_SHADOW_FLAG = shader_slang::ResourceShape::SlangTextureShadowFlag as u32; const TEXTURE_ARRAY_FLAG = shader_slang::ResourceShape::SlangTextureArrayFlag as u32; const TEXTURE_MULTISAMPLE_FLAG = shader_slang::ResourceShape::SlangTextureMultisampleFlag as u32; const TEXTURE_COMBINED_FLAG = shader_slang::ResourceShape::SlangTextureCombinedFlag as u32; const TEXTURE_1D_ARRAY = shader_slang::ResourceShape::SlangTexture1dArray as u32; const TEXTURE_2D_ARRAY = shader_slang::ResourceShape::SlangTexture2dArray as u32; const TEXTURE_CUBE_ARRAY = shader_slang::ResourceShape::SlangTextureCubeArray as u32; const TEXTURE_2D_MULTISAMPLE = shader_slang::ResourceShape::SlangTexture2dMultisample as u32; const TEXTURE_2D_MULTISAMPLE_ARRAY = shader_slang::ResourceShape::SlangTexture2dMultisampleArray as u32; const TEXTURE_SUBPASS_MULTISAMPLE = shader_slang::ResourceShape::SlangTextureSubpassMultisample as u32; } } #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum ParameterCategory { None, Mixed, ConstantBuffer, ShaderResource, UnorderedAccess, VaryingInput, VaryingOutput, SamplerState, Uniform, DescriptorTableSlot, SpecializationConstant, PushConstantBuffer, RegisterSpace, Generic, RayPayload, HitAttributes, CallablePayload, ShaderRecord, ExistentialTypeParam, ExistentialObjectParam, SubElementRegisterSpace, Subpass, MetalArgumentBufferElement, MetalAttribute, MetalPayload, Count, } impl ParameterCategory { fn from_slang_parameter_category(parameter_category: SlangParameterCategory) -> Self { match parameter_category { SlangParameterCategory::None => Self::None, SlangParameterCategory::Mixed => Self::Mixed, SlangParameterCategory::ConstantBuffer => Self::ConstantBuffer, SlangParameterCategory::ShaderResource => Self::ShaderResource, SlangParameterCategory::UnorderedAccess => Self::UnorderedAccess, SlangParameterCategory::VaryingInput => Self::VaryingInput, SlangParameterCategory::VaryingOutput => Self::VaryingOutput, SlangParameterCategory::SamplerState => Self::SamplerState, SlangParameterCategory::Uniform => Self::Uniform, SlangParameterCategory::DescriptorTableSlot => Self::DescriptorTableSlot, SlangParameterCategory::SpecializationConstant => { Self::SpecializationConstant } SlangParameterCategory::PushConstantBuffer => Self::PushConstantBuffer, SlangParameterCategory::RegisterSpace => Self::RegisterSpace, SlangParameterCategory::Generic => Self::Generic, SlangParameterCategory::RayPayload => Self::RayPayload, SlangParameterCategory::HitAttributes => Self::HitAttributes, SlangParameterCategory::CallablePayload => Self::CallablePayload, SlangParameterCategory::ShaderRecord => Self::ShaderRecord, SlangParameterCategory::ExistentialTypeParam => Self::ExistentialTypeParam, SlangParameterCategory::ExistentialObjectParam => { Self::ExistentialObjectParam } SlangParameterCategory::SubElementRegisterSpace => { Self::SubElementRegisterSpace } SlangParameterCategory::Subpass => Self::Subpass, SlangParameterCategory::MetalArgumentBufferElement => { Self::MetalArgumentBufferElement } SlangParameterCategory::MetalAttribute => Self::MetalAttribute, SlangParameterCategory::MetalPayload => Self::MetalPayload, SlangParameterCategory::Count => Self::Count, } } } pub struct Blob { inner: SlangBlob, } impl Blob { pub fn as_bytes(&self) -> &[u8] { self.inner.as_slice() } pub fn as_str(&self) -> Result<&str, Utf8Error> { self.inner.as_str() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub enum Stage { None, Vertex, Hull, Domain, Geometry, Fragment, Compute, RayGeneration, Intersection, AnyHit, ClosestHit, Miss, Callable, Mesh, Amplification, Dispatch, Count, } impl Stage { fn from_slang_stage(stage: shader_slang::Stage) -> Self { match stage { shader_slang::Stage::None => Self::None, shader_slang::Stage::Vertex => Self::Vertex, shader_slang::Stage::Hull => Self::Hull, shader_slang::Stage::Domain => Self::Domain, shader_slang::Stage::Geometry => Self::Geometry, shader_slang::Stage::Fragment => Self::Fragment, shader_slang::Stage::Compute => Self::Compute, shader_slang::Stage::RayGeneration => Self::RayGeneration, shader_slang::Stage::Intersection => Self::Intersection, shader_slang::Stage::AnyHit => Self::AnyHit, shader_slang::Stage::ClosestHit => Self::ClosestHit, shader_slang::Stage::Miss => Self::Miss, shader_slang::Stage::Callable => Self::Callable, shader_slang::Stage::Mesh => Self::Mesh, shader_slang::Stage::Amplification => Self::Amplification, shader_slang::Stage::Dispatch => Self::Dispatch, shader_slang::Stage::Count => Self::Count, } } } 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: SlangSession, modules: HashMap, programs: HashMap, } impl Context { pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module> { self.modules.get(asset_id) } 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 { 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::, _>>()?; let components = entry_points .into_iter() .map(|entry_point| SlangComponentType::from(entry_point.inner)) .chain([SlangComponentType::from(module.inner)]) .collect::>(); let program = self .session .create_composite_component_type(&components) .map_err(|err| ComposeProgramError::Other(Error(err)))?; let vertex_desc = if link_entrypoints.contains(EntrypointFlags::VERTEX) { // TODO: Do not have hard coded target here let Ok(program_reflection) = program.layout(0) else { unreachable!(); }; let program_reflection = ProgramReflection { inner: program_reflection }; Some(VertexDescription::new( &program_reflection .get_entry_point_by_name("vertex_main") .expect("Not possible"), )?) } else { None }; Ok(Program { inner: program, metadata: ProgramMetadata { vertex_desc }, }) } } #[derive(Debug, Clone)] #[non_exhaustive] pub struct ProgramMetadata { /// If the program has a entry point in the vertex stage, this field will contain a /// description of the vertex type passed to the entry point. pub vertex_desc: Option, } #[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()))] pub fn new( vs_entrypoint: &EntryPointReflection<'_>, ) -> Result { if vs_entrypoint.stage() != Stage::Vertex { return Err(VertexDescriptionError::EntrypointNotInVertexStage); } let inputs = VarInputDfsIter::new(vs_entrypoint).inspect(|var_input| { if var_input.type_layout.kind() != 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("") ); }); let mut seen_inputs = HashSet::::new(); Ok(Self { inputs: inputs .filter(|var_input| var_input.type_layout.kind() != TypeKind::Struct) .map(|var_input| { let name = var_input.var_layout.name().unwrap_or(""); let semantic_name = var_input.var_layout.semantic_name().ok_or_else(|| { VertexDescriptionError::VertexInputMissingSemanticName { name: name.to_owned(), } })?; let semantic_name = VertexInputSemName::from_semantic_name(semantic_name); if seen_inputs.contains(&semantic_name) { return Err( VertexDescriptionError::VertexInputHasOccupiedSemanticName { name: name.to_owned(), }, ); } let scalar_type = match ( var_input.type_layout.kind(), var_input.type_layout.scalar_type(), ) { (TypeKind::Scalar, Some(scalar_type)) => scalar_type, (TypeKind::Vector, Some(scalar_type)) => scalar_type, _ => { return Err( VertexDescriptionError::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::, _>>()? .into(), }) } } #[derive(Debug)] #[non_exhaustive] pub struct VertexInputDescription { pub semantic_name: VertexInputSemName, pub index: usize, pub type_kind: TypeKind, pub scalar_type: ScalarType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum VertexInputSemName { Position, Normal, Uv, Color, Other(Box), } impl VertexInputSemName { fn from_semantic_name(semantic_name: &str) -> Self { match semantic_name { STD_VERTEX_INPUT_SEMANTIC_NAME_POSITION => Self::Position, STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL => Self::Normal, STD_VERTEX_INPUT_SEMANTIC_NAME_UV => Self::Uv, STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR => Self::Color, _ => Self::Other(semantic_name.to_lowercase().into_boxed_str()), } } pub fn matches_vertex_label(&self, vertex_label: &VertexLabel) -> bool { match (self, vertex_label) { (Self::Position, VertexLabel::Position) | (Self::Normal, VertexLabel::Normal) | (Self::Uv, VertexLabel::Uv) | (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_VERTEX_INPUT_SEMANTIC_NAME_POSITION) } Self::Normal => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_NORMAL), Self::Uv => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_UV), Self::Color => formatter.write_str(STD_VERTEX_INPUT_SEMANTIC_NAME_COLOR), Self::Other(other) => { for character in other.chars() { formatter.write_char(character.to_ascii_uppercase())?; } Ok(()) } } } } #[derive(Debug, thiserror::Error)] pub enum VertexDescriptionError { #[error("Entrypoint is not in vertex stage")] EntrypointNotInVertexStage, #[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("Shader module does not have a '{entrypoint_name}' entry point")] EntrypointNotFoundError { entrypoint_name: &'static str }, #[error("Failed to create vertex description")] VertexDescriptionCreationFailed(#[from] VertexDescriptionError), #[error(transparent)] Other(Error), } #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(#[from] shader_slang::Error); struct VarInputDfsIter<'a> { stack: Vec<(VariableLayout<'a>, TypeLayout<'a>, usize)>, } impl<'a> VarInputDfsIter<'a> { fn new(entry_point: &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 { 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() == 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: VariableLayout<'a>, type_layout: TypeLayout<'a>, index: usize, } fn import_slang_asset( asset_submitter: &mut AssetSubmitter<'_>, file_path: &Path, settings: Option<&'_ Settings>, ) -> Result<(), ImportError> { 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(|settings| settings.link_entrypoints) .unwrap_or_default(), }); Ok(()) } #[derive(Debug, thiserror::Error)] enum ImportError { #[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; }; let session_options = shader_slang::CompilerOptions::default() .optimization(shader_slang::OptimizationLevel::None) .matrix_layout_row(true) .debug_information(SlangDebugInfoLevel::Maximal) .no_mangle(true); let target_desc = shader_slang::TargetDesc::default() .format(shader_slang::CompileTarget::Glsl) // .format(shader_slang::CompileTarget::Spirv) .profile(global_session.find_profile("glsl_330")); // .profile(global_session.find_profile("spirv_1_5")); let targets = [target_desc]; let session_desc = shader_slang::SessionDesc::default() .targets(&targets) .search_paths(&[""]) .options(&session_options); let Some(session) = global_session.create_session(&session_desc) else { tracing::error!("Failed to create shader-slang session"); return; }; collector .add_sole(Context { _global_session: global_session, session, modules: HashMap::new(), programs: HashMap::new(), }) .ok(); collector.spawn_declared_entity(&IMPORT_SHADERS_PHASE); collector.add_system(*INIT_PHASE, initialize); collector.add_system(*IMPORT_SHADERS_PHASE, load_modules); collector.add_system(*INIT_PHASE, crate::rendering::shader::default::initialize); collector.add_system( *POST_UPDATE_PHASE, crate::rendering::shader::default::enqueue_set_shader_bindings, ); } fn initialize(mut assets: Single) -> Result<(), crate::Error> { let assets = assets.get_mut()?; assets.set_importer::<_, _>(["slang"], import_slang_asset); Ok(()) } #[tracing::instrument(skip_all)] fn load_modules( mut context: Single, assets: Single, ) -> Result<(), crate::Error> { let Ok(context) = context.get_mut() else { unreachable!(); }; let assets = assets.get()?; for AssetEvent::Stored(asset_id, asset_label) in assets.events().last_tick_events() { let asset_handle = AssetHandle::::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(&context.session, module_source) { Ok(module) => module, Err(err) => { tracing::error!("Failed to load shader module: {err}"); continue; } }; context.modules.insert(*asset_id, module.clone()); if !module_source.link_entrypoints.is_empty() { assert!(context.programs.get(asset_id).is_none()); 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 { 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 }) }