diff options
Diffstat (limited to 'engine/src/rendering/shader.rs')
| -rw-r--r-- | engine/src/rendering/shader.rs | 1096 |
1 files changed, 314 insertions, 782 deletions
diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs index 3ba3a41..6a52b0a 100644 --- a/engine/src/rendering/shader.rs +++ b/engine/src/rendering/shader.rs @@ -1,6 +1,5 @@ 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; @@ -11,14 +10,10 @@ 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::{ @@ -39,6 +34,7 @@ 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"; @@ -113,68 +109,14 @@ pub struct EntryPoint impl EntryPoint { #[must_use] - pub fn function(&self) -> FunctionReflection<'_> + pub fn function_reflection(&self) -> self::reflection::Function<'_> { - FunctionReflection { + self::reflection::Function { inner: self.inner.function_reflection(), } } } -pub struct FunctionReflection<'a> -{ - inner: &'a shader_slang::reflection::Function, -} - -impl FunctionReflection<'_> -{ - #[must_use] - pub fn name(&self) -> Option<&str> - { - self.inner.name() - } -} - -pub struct EntryPointReflection<'a> -{ - inner: &'a shader_slang::reflection::EntryPoint, -} - -impl<'a> EntryPointReflection<'a> -{ - #[must_use] - pub fn name(&self) -> Option<&str> - { - self.inner.name() - } - - #[must_use] - pub fn name_override(&self) -> Option<&str> - { - self.inner.name_override() - } - - #[must_use] - pub fn stage(&self) -> Stage - { - Stage::from_slang_stage(self.inner.stage()) - } - - #[must_use] - pub fn parameters(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>> - { - self.inner - .parameters() - .map(|param| VariableLayout { inner: param }) - } - - #[must_use] - pub fn var_layout(&self) -> Option<VariableLayout<'a>> - { - Some(VariableLayout { inner: self.inner.var_layout()? }) - } -} - #[derive(Clone)] pub struct Program { @@ -200,18 +142,51 @@ impl Program &self.metadata } - pub fn get_entry_point_code(&self, entry_point_index: u32) -> Result<Blob, Error> + 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(), 0)?; + 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: u32) -> Result<ProgramReflection<'_>, Error> + pub fn reflection( + &self, + target_id: CompileTargetId, + ) -> Result<self::reflection::Program<'_>, Error> { - let reflection = self.inner.layout(i64::from(target))?; + let reflection = self.inner.layout(i64::from(target_id.index))?; - Ok(ProgramReflection { inner: reflection }) + 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"), + ) } } @@ -225,778 +200,331 @@ impl Debug for Program } } -pub struct ProgramReflection<'a> +pub struct Blob { - inner: &'a shader_slang::reflection::Shader, + inner: SlangBlob, } -impl<'a> ProgramReflection<'a> +impl Blob { #[must_use] - pub fn get_entry_point_by_index(&self, index: u32) - -> Option<EntryPointReflection<'a>> - { - Some(EntryPointReflection { - inner: self.inner.entry_point_by_index(index)?, - }) - } - - #[must_use] - pub fn get_entry_point_by_name(&self, name: &str) - -> Option<EntryPointReflection<'a>> - { - Some(EntryPointReflection { - inner: self.inner.find_entry_point_by_name(name)?, - }) - } - - #[must_use] - pub fn entry_points( - &self, - ) -> impl ExactSizeIterator<Item = EntryPointReflection<'a>> + use<'a> - { - self.inner - .entry_points() - .map(|entry_point| EntryPointReflection { inner: entry_point }) - } - - #[must_use] - pub fn global_params_type_layout(&self) -> Option<TypeLayout<'a>> - { - Some(TypeLayout { - inner: self.inner.global_params_type_layout()?, - }) - } - - #[must_use] - pub fn global_params_var_layout(&self) -> Option<VariableLayout<'a>> - { - Some(VariableLayout { - inner: self.inner.global_params_var_layout()?, - }) - } - - #[must_use] - pub fn get_type(&self, name: &str) -> Option<TypeReflection<'a>> + pub fn as_bytes(&self) -> &[u8] { - Some(TypeReflection { - inner: self.inner.find_type_by_name(name)?, - }) + self.inner.as_slice() } - #[must_use] - pub fn get_type_layout(&self, ty: &TypeReflection<'a>) -> Option<TypeLayout<'a>> + #[allow(clippy::missing_errors_doc)] + pub fn as_str(&self) -> Result<&str, Utf8Error> { - Some(TypeLayout { - inner: self - .inner - .type_layout(ty.inner, shader_slang::LayoutRules::Default)?, - }) + self.inner.as_str() } } -#[derive(Clone, Copy)] -pub struct VariableLayout<'a> +builder! { +#[builder(name = SettingsBuilder, derives=(Debug))] +#[derive(Debug)] +#[non_exhaustive] +pub struct Settings { - inner: &'a shader_slang::reflection::VariableLayout, + link_entrypoints: EntrypointFlags, } - -impl<'a> VariableLayout<'a> -{ - #[must_use] - pub fn name(&self) -> Option<&'a str> - { - self.inner.name() - } - - #[must_use] - pub fn semantic_name(&self) -> Option<&str> - { - self.inner.semantic_name() - } - - #[must_use] - pub fn binding_index(&self) -> u32 - { - self.inner - .offset(shader_slang::ParameterCategory::DescriptorTableSlot) as u32 - - // self.inner.binding_index() - } - - #[must_use] - pub fn varying_input_offset(&self) -> Option<usize> - { - if !self - .inner - .categories() - .any(|category| category == SlangParameterCategory::VaryingInput) - { - return None; - } - - Some(self.inner.offset(SlangParameterCategory::VaryingInput)) - } - - #[must_use] - pub fn binding_space(&self) -> u32 - { - self.inner.binding_space() - } - - #[must_use] - pub fn semantic_index(&self) -> usize - { - self.inner.semantic_index() - } - - #[must_use] - pub fn offset(&self) -> usize - { - self.inner.offset(shader_slang::ParameterCategory::Uniform) - } - - #[must_use] - pub fn ty(&self) -> Option<TypeReflection<'a>> - { - self.inner.ty().map(|ty| TypeReflection { inner: ty }) - } - - #[must_use] - pub fn type_layout(&self) -> Option<TypeLayout<'a>> - { - Some(TypeLayout { inner: self.inner.type_layout()? }) - } } -#[derive(Clone, Copy)] -pub struct TypeLayout<'a> +#[derive(Sole)] +pub struct Context { - inner: &'a shader_slang::reflection::TypeLayout, + global_session: SlangGlobalSession, + session: Option<SlangSession>, + modules: hashbrown::HashMap<AssetId, Module>, + programs: hashbrown::HashMap<AssetId, Program>, } -impl<'a> TypeLayout<'a> +impl Context { - #[must_use] - pub fn kind(&self) -> TypeKind + #[allow(clippy::missing_errors_doc)] + pub fn init_session( + &mut self, + opts: &ContextSessionInitOpts, + compile_targets: &[&CompileTarget], + ) -> Result<Vec<CompileTargetId>, ContextSessionInitError> { - TypeKind::from_slang_type_kind(self.inner.kind()) - } - - #[must_use] - pub fn scalar_type(&self) -> Option<ScalarType> - { - Some(ScalarType::from_slang_scalar_type( - self.inner.scalar_type()?, - )) - } - - #[must_use] - pub fn resource_shape(&self) -> Option<ResourceShape> - { - Some(ResourceShape::from_bits_retain( - self.inner.resource_shape()? as u32, - )) - } - - #[must_use] - pub fn get_field_by_name(&self, name: &str) -> Option<VariableLayout<'a>> - { - let index = self.inner.find_field_index_by_name(name); - - if index < 0 { - return None; + if self.session.is_some() { + return Err(ContextSessionInitError::AlreadyInitialized); } - let index = u32::try_from(index.cast_unsigned()).expect("Should not happend"); - - let field = self.inner.field_by_index(index)?; - - Some(VariableLayout { inner: field }) - } - - #[must_use] - pub fn parameter_category(&self) -> ParameterCategory - { - ParameterCategory::from_slang_parameter_category(self.inner.parameter_category()) - } - - #[must_use] - pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64 - { - self.inner.binding_range_descriptor_set_index(index) - } - - #[must_use] - pub fn get_field_binding_range_offset_by_name(&self, name: &str) -> Option<u64> - { - 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; + if compile_targets.len() > u32::MAX as usize { + return Err(ContextSessionInitError::TooManyCompileTargets); } - Some(field_binding_range_offset.cast_unsigned()) - } - - #[must_use] - pub fn ty(&self) -> Option<TypeReflection<'a>> - { - self.inner.ty().map(|ty| TypeReflection { inner: ty }) - } - - #[must_use] - pub fn fields(&self) -> FieldIter<'a> - { - FieldIter { - type_layout: *self, - cnt: self.field_cnt(), - index: 0, - } - } + 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); - #[must_use] - pub fn field_cnt(&self) -> u32 - { - self.inner.field_count() - } + 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<_>)>(); - #[must_use] - pub fn element_cnt(&self) -> Option<usize> - { - self.inner.element_count() - } + let session_desc = shader_slang::SessionDesc::default() + .targets(&target_descs) + .search_paths([""]) + .options(&session_options); - #[must_use] - pub fn row_cnt(&self) -> Option<u32> - { - self.inner.row_count() - } + let Some(session) = self.global_session.create_session(&session_desc) else { + tracing::error!("Failed to create shader-slang session"); + return Err(ContextSessionInitError::Internal); + }; - #[must_use] - pub fn column_cnt(&self) -> Option<u32> - { - self.inner.column_count() - } + self.session = Some(session); - #[must_use] - pub fn element_type_layout(&self) -> Option<TypeLayout<'a>> - { - self.inner - .element_type_layout() - .map(|type_layout| TypeLayout { inner: type_layout }) + Ok(compile_target_ids) } #[must_use] - pub fn element_var_layout(&self) -> Option<VariableLayout<'a>> + pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module> { - self.inner - .element_var_layout() - .map(|var_layout| VariableLayout { inner: var_layout }) + self.modules.get(asset_id) } #[must_use] - pub fn container_var_layout(&self) -> Option<VariableLayout<'a>> + pub fn get_program(&self, asset_id: &AssetId) -> Option<&Program> { - self.inner - .container_var_layout() - .map(|var_layout| VariableLayout { inner: var_layout }) + self.programs.get(asset_id) } - #[must_use] - pub fn uniform_size(&self) -> Option<usize> + #[tracing::instrument(skip_all, fields(module_file = module.file_path()))] + pub fn compose_into_program( + &self, + module: Module, + link_entrypoints: EntrypointFlags, + ) -> Result<Program, ComposeProgramError> { - // tracing::debug!( - // "uniform_size: {:?} categories: {:?}", - // self.inner.name(), - // self.inner.categories().collect::<Vec<_>>(), - // ); + let Some(session) = self.session.as_ref() else { + return Err(ComposeProgramError::ContextSessionNotInitialized); + }; - if !self - .inner - .categories() - .any(|category| category == SlangParameterCategory::Uniform) - { - return None; - } + 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 category = self.inner.categories().next().unwrap(); + let Some(entry_point) = module.get_entry_point(entrypoint_name) else { + return Some(Err(ComposeProgramError::EntrypointNotFoundError { + entrypoint_name, + })); + }; - // println!( - // "AARGH size Category: {category:?} Category count: {}", - // self.inner.category_count() - // ); + Some(Ok(entry_point)) + }) + .collect::<Result<Vec<_>, _>>()?; - // Some(self.inner.size(category)) + let components = entry_points + .into_iter() + .map(|entry_point| SlangComponentType::from(entry_point.inner)) + .chain([SlangComponentType::from(module.inner)]) + .collect::<Vec<_>>(); - Some(self.inner.size(SlangParameterCategory::Uniform)) - } + let program = session + .create_composite_component_type(&components) + .map_err(|err| ComposeProgramError::Other(Error(err)))?; - #[must_use] - pub fn stride(&self) -> usize - { - self.inner.stride(self.inner.categories().next().unwrap()) + Ok(Program { + inner: program, + metadata: ProgramMetadata { link_entrypoints }, + }) } } -pub struct FieldIter<'a> +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ContextSessionInitOpts { - type_layout: TypeLayout<'a>, - cnt: u32, - index: u32, + pub optimization_level: OptimizationLevel, + pub debug_info_level: DebugInfoLevel, + pub line_directive_mode: LineDirectiveMode, + pub no_mangle: bool, } -impl<'a> Iterator for FieldIter<'a> +impl ContextSessionInitOpts { - type Item = VariableLayout<'a>; - - fn next(&mut self) -> Option<Self::Item> + pub fn optimization_level(mut self, optimization_level: OptimizationLevel) -> Self { - 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 }) + self.optimization_level = optimization_level; + self } - fn size_hint(&self) -> (usize, Option<usize>) + pub fn debug_info_level(mut self, debug_info_level: DebugInfoLevel) -> Self { - let len = (self.cnt - self.index) as usize; + self.debug_info_level = debug_info_level; + self + } - (len, Some(len)) + pub fn line_directive_mode(mut self, line_directive_mode: LineDirectiveMode) -> Self + { + self.line_directive_mode = line_directive_mode; + self } -} -impl ExactSizeIterator for FieldIter<'_> -{ - fn len(&self) -> usize + pub fn no_mangle(mut self, no_mangle: bool) -> Self { - (self.cnt - self.index) as usize + self.no_mangle = no_mangle; + self } } -impl DoubleEndedIterator for FieldIter<'_> +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub enum OptimizationLevel { - fn next_back(&mut self) -> Option<Self::Item> - { - if self.cnt == 0 || self.index == self.cnt - 1 { - return None; - } + /// Don't optimize at all. + None, - let Some(field) = self.type_layout.inner.field_by_index(self.cnt - 1) else { - unreachable!(); - }; + /// Default optimization level: balance code quality and compilation time. + #[default] + Default, - self.cnt -= 1; + /// Optimize aggressively. + High, - Some(VariableLayout { inner: field }) - } + /// Include optimizations that may take a very long time, or may involve severe + /// space-vs-speed tradeoffs + Maximal, } -pub struct TypeReflection<'a> +impl OptimizationLevel { - inner: &'a shader_slang::reflection::Type, -} - -impl TypeReflection<'_> -{ - #[must_use] - pub fn kind(&self) -> TypeKind + fn into_slang(self) -> shader_slang::OptimizationLevel { - TypeKind::from_slang_type_kind(self.inner.kind()) + 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, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] #[non_exhaustive] -pub enum TypeKind +pub enum DebugInfoLevel { + /// Don't emit debug information at all. + #[default] None, - Struct, - Enum, - Array, - Matrix, - Vector, - Scalar, - ConstantBuffer, - Resource, - SamplerState, - TextureBuffer, - ShaderStorageBuffer, - ParameterBlock, - GenericTypeParameter, - Interface, - OutputStream, - MeshOutput, - Specialized, - Feedback, - Pointer, - DynamicResource, - Count, + + /// 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 TypeKind +impl DebugInfoLevel { - fn from_slang_type_kind(type_kind: SlangTypeKind) -> Self + fn into_slang(self) -> shader_slang::DebugInfoLevel { - 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, + 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, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] #[non_exhaustive] -pub enum ScalarType +pub enum LineDirectiveMode { + /// Don't emit line directives at all. + #[default] 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"), - } - } -} + /// Emit standard C-style `#line` directives. + Standard, -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; -} -} + /// Emit GLSL-style directives with file *number* instead of name + Glsl, -#[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, + /// Use a source map to track line mappings (ie no #line will appear in emitting + /// source) + SourceMap, } -impl ParameterCategory +impl LineDirectiveMode { - fn from_slang_parameter_category(parameter_category: SlangParameterCategory) -> Self + fn into_slang(self) -> shader_slang::LineDirectiveMode { - 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, + 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, } } } -pub struct Blob -{ - inner: SlangBlob, -} - -impl Blob +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CompileTargetId { - #[must_use] - pub fn as_bytes(&self) -> &[u8] - { - self.inner.as_slice() - } - - pub fn as_str(&self) -> Result<&str, Utf8Error> - { - self.inner.as_str() - } + index: u32, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] -pub enum Stage +pub struct CompileTarget { - None, - Vertex, - Hull, - Domain, - Geometry, - Fragment, - Compute, - RayGeneration, - Intersection, - AnyHit, - ClosestHit, - Miss, - Callable, - Mesh, - Amplification, - Dispatch, - Count, + pub format: CompileTargetFormat, } -impl Stage +impl CompileTarget { - fn from_slang_stage(stage: shader_slang::Stage) -> Self + #[must_use] + pub fn format(mut self, format: CompileTargetFormat) -> 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, - } + self.format = format; + self } } -builder! { -#[builder(name = SettingsBuilder, derives=(Debug))] -#[derive(Debug)] +#[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] -pub struct Settings -{ - link_entrypoints: EntrypointFlags, -} -} - -#[derive(Sole)] -pub struct Context +pub enum CompileTargetFormat { - _global_session: SlangGlobalSession, - session: SlangSession, - modules: HashMap<AssetId, Module>, - programs: HashMap<AssetId, Program>, + #[default] + Glsl, } -impl Context +impl CompileTargetFormat { - #[must_use] - pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module> + fn into_slang(self) -> shader_slang::CompileTarget { - self.modules.get(asset_id) - } - - #[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 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 = 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 }, - }) + match self { + Self::Glsl => shader_slang::CompileTarget::Glsl, + } } } @@ -1004,9 +532,7 @@ impl Context #[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<VertexDescription>, + pub link_entrypoints: EntrypointFlags, } #[derive(Debug, Clone)] @@ -1019,16 +545,12 @@ pub struct VertexDescription impl VertexDescription { #[tracing::instrument(skip_all, fields(vs_entry_point_name = vs_entrypoint.name()))] - pub fn new( - vs_entrypoint: &EntryPointReflection<'_>, - ) -> Result<Self, VertexDescriptionError> + fn new( + vs_entrypoint: &self::reflection::EntryPointReflection<'_>, + ) -> Result<Self, VertexDescCreationError> { - 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 { + if var_input.type_layout.kind() != self::reflection::TypeKind::Struct { return; } @@ -1043,17 +565,19 @@ impl VertexDescription ); }); - let mut seen_inputs = HashSet::<VertexInputSemName>::new(); + let mut seen_inputs = hashbrown::HashSet::<VertexInputSemName>::new(); Ok(Self { inputs: inputs - .filter(|var_input| var_input.type_layout.kind() != TypeKind::Struct) + .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(|| { - VertexDescriptionError::VertexInputMissingSemanticName { + VertexDescCreationError::VertexInputMissingSemanticName { name: name.to_owned(), } })?; @@ -1063,19 +587,26 @@ impl VertexDescription if seen_inputs.contains(&semantic_name) { return Err( - VertexDescriptionError::VertexInputHasOccupiedSemanticName { + VertexDescCreationError::VertexInputHasOccupiedSemanticName { name: name.to_owned(), }, ); } - let (TypeKind::Scalar | TypeKind::Vector, Some(scalar_type)) = ( + 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(VertexDescriptionError::UnsupportedVertexInputType { - name: name.to_owned(), - }); + ) + else { + return Err( + VertexDescCreationError::UnsupportedVertexInputType { + name: name.to_owned(), + }, + ); }; seen_inputs.insert(semantic_name.clone()); @@ -1099,8 +630,8 @@ pub struct VertexInputDescription { pub semantic_name: VertexInputSemName, pub index: usize, - pub type_kind: TypeKind, - pub scalar_type: ScalarType, + pub type_kind: self::reflection::TypeKind, + pub scalar_type: self::reflection::ScalarType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1166,10 +697,13 @@ impl Display for VertexInputSemName } #[derive(Debug, thiserror::Error)] -pub enum VertexDescriptionError +pub enum VertexDescCreationError { - #[error("Entrypoint is not in vertex stage")] - EntrypointNotInVertexStage, + #[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 @@ -1195,6 +729,9 @@ pub enum VertexDescriptionError #[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 { @@ -1202,24 +739,41 @@ pub enum ComposeProgramError }, #[error("Failed to create vertex description")] - VertexDescriptionCreationFailed(#[from] VertexDescriptionError), + 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<(VariableLayout<'a>, TypeLayout<'a>, usize)>, + stack: Vec<( + self::reflection::VariableLayout<'a>, + self::reflection::TypeLayout<'a>, + usize, + )>, } impl<'a> VarInputDfsIter<'a> { - fn new(entry_point: &EntryPointReflection<'a>) -> Self + fn new(entry_point: &self::reflection::EntryPointReflection<'a>) -> Self { Self { stack: entry_point @@ -1251,7 +805,7 @@ impl<'a> Iterator for VarInputDfsIter<'a> } }; - if type_layout.kind() == TypeKind::Struct { + 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 @@ -1269,8 +823,8 @@ impl<'a> Iterator for VarInputDfsIter<'a> struct VarInput<'a> { - var_layout: VariableLayout<'a>, - type_layout: TypeLayout<'a>, + var_layout: self::reflection::VariableLayout<'a>, + type_layout: self::reflection::TypeLayout<'a>, index: usize, } @@ -1300,9 +854,7 @@ fn import( source: std::fs::read_to_string(file_path) .map_err(ImportError::ReadFileFailed)? .into(), - link_entrypoints: settings - .map(|settings| settings.link_entrypoints) - .unwrap_or_default(), + link_entrypoints: settings.map_or_default(|settings| settings.link_entrypoints), }); Ok(()) @@ -1338,36 +890,12 @@ pub(super) fn prepare(collector: &mut crate::ecs::extension::Collector<'_>) 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(), + global_session, + session: None, + modules: hashbrown::HashMap::new(), + programs: hashbrown::HashMap::new(), }) .ok(); @@ -1400,6 +928,10 @@ fn load_modules( 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); @@ -1413,7 +945,7 @@ fn load_modules( tracing::debug!(asset_label=?asset_label, "Loading shader module"); - let module = match load_module(&context.session, module_source) { + let module = match load_module(session, module_source) { Ok(module) => module, Err(err) => { tracing::error!("Failed to load shader module: {err}"); |
