From 3794c39bfd3fd4d03f9f89226fc9c1f16b096572 Mon Sep 17 00:00:00 2001 From: HampusM Date: Fri, 10 Jul 2026 14:07:03 +0200 Subject: refactor(engine): move shader module to inside of rendering module --- engine/src/lib.rs | 4 - engine/src/rendering.rs | 7 +- engine/src/rendering/backend/opengl.rs | 14 +- .../src/rendering/backend/opengl/graphics_mesh.rs | 2 +- engine/src/rendering/main_render_pass.rs | 20 +- engine/src/rendering/shader.rs | 1147 +++++++++++++++++++ engine/src/rendering/shader/cursor.rs | 159 +++ engine/src/rendering/shader/default.rs | 397 +++++++ engine/src/shader.rs | 1166 -------------------- engine/src/shader/cursor.rs | 159 --- engine/src/shader/default.rs | 379 ------- engine/src/ui/imgui.rs | 15 +- 12 files changed, 1740 insertions(+), 1729 deletions(-) create mode 100644 engine/src/rendering/shader.rs create mode 100644 engine/src/rendering/shader/cursor.rs create mode 100644 engine/src/rendering/shader/default.rs delete mode 100644 engine/src/shader.rs delete mode 100644 engine/src/shader/cursor.rs delete mode 100644 engine/src/shader/default.rs (limited to 'engine') diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 79cf0c7..db20917 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -9,7 +9,6 @@ use crate::ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; use crate::ecs::system::initializable::Initializable; use crate::ecs::system::Into; use crate::ecs::World; -use crate::shader::Extension as ShaderExtension; mod util; mod work_queue; @@ -31,7 +30,6 @@ pub mod model; pub mod projection; pub mod reflection; pub mod rendering; -pub mod shader; pub mod texture; pub mod transform; pub mod ui; @@ -73,12 +71,10 @@ impl Engine crate::model::asset::add_importers(&mut assets); crate::material::asset::add_importers(&mut assets); - crate::shader::add_asset_importers(&mut assets); crate::texture::initialize(&mut assets); world.add_extension(AssetExtension { assets }); - world.add_extension(ShaderExtension); Self { world } } diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs index 51cde39..94e5000 100644 --- a/engine/src/rendering.rs +++ b/engine/src/rendering.rs @@ -22,12 +22,12 @@ use crate::ecs::{declare_entity, Component, Query, Sole}; use crate::mesh::Mesh; use crate::rendering::blending::Config as BlendingConfig; use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; -use crate::shader::cursor::{ +use crate::rendering::shader::cursor::{ BindingLocation as ShaderBindingLocation, BindingValue as ShaderBindingValue, Cursor as ShaderCursor, }; -use crate::shader::Program as ShaderProgram; +use crate::rendering::shader::Program as ShaderProgram; use crate::texture::Texture; use crate::vector::Vec2; use crate::windowing::dpi::PhysicalSize; @@ -37,6 +37,7 @@ pub mod backend; pub mod blending; pub mod main_render_pass; pub mod object; +pub mod shader; static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); @@ -110,6 +111,8 @@ impl crate::ecs::extension::Extension for Extension collector.add_observer(handle_window_changed); collector.add_observer(handle_window_removed); + crate::rendering::shader::prepare(&mut collector); + crate::rendering::backend::get_default().collect(collector); } } diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs index fbb402c..41d6968 100644 --- a/engine/src/rendering/backend/opengl.rs +++ b/engine/src/rendering/backend/opengl.rs @@ -92,6 +92,13 @@ use crate::rendering::object::{ RawValue as ObjectRawValue, Store as ObjectStore, }; +use crate::rendering::shader::cursor::BindingValue as ShaderBindingValue; +use crate::rendering::shader::{ + Context as ShaderContext, + Error as ShaderError, + Program as ShaderProgram, + Stage as ShaderStage, +}; use crate::rendering::{ BufferClearMask, Command, @@ -105,13 +112,6 @@ use crate::rendering::{ POST_RENDER_PHASE, RENDER_PHASE, }; -use crate::shader::cursor::BindingValue as ShaderBindingValue; -use crate::shader::{ - Context as ShaderContext, - Error as ShaderError, - Program as ShaderProgram, - Stage as ShaderStage, -}; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, diff --git a/engine/src/rendering/backend/opengl/graphics_mesh.rs b/engine/src/rendering/backend/opengl/graphics_mesh.rs index 4933197..6d5c248 100644 --- a/engine/src/rendering/backend/opengl/graphics_mesh.rs +++ b/engine/src/rendering/backend/opengl/graphics_mesh.rs @@ -9,8 +9,8 @@ use opengl_bindings::vertex_array::{ use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns; use crate::mesh::{Mesh, VertexAttrType}; +use crate::rendering::shader::VertexDescription as ShaderVertexDescription; use crate::rendering::MeshUsage; -use crate::shader::VertexDescription as ShaderVertexDescription; #[derive(Debug)] pub struct GraphicsMesh diff --git a/engine/src/rendering/main_render_pass.rs b/engine/src/rendering/main_render_pass.rs index 3429e34..ec30dc5 100644 --- a/engine/src/rendering/main_render_pass.rs +++ b/engine/src/rendering/main_render_pass.rs @@ -5,15 +5,25 @@ use crate::ecs::sole::Single; use crate::ecs::Query; use crate::model::{MaterialSearchResult, Model}; use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; -use crate::rendering::{ - BufferClearMask, Command, DrawMeshOptions, DrawProperties, DrawPropertiesUpdateFlags, MeshUsage, PendingShaderBindings, RenderPass, RenderPasses, SurfaceSpec, TargetWindow -}; -use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; -use crate::shader::{ +use crate::rendering::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; +use crate::rendering::shader::{ Context as ShaderContext, ModuleSource as ShaderModuleSource, Shader, }; +use crate::rendering::{ + BufferClearMask, + Command, + DrawMeshOptions, + DrawProperties, + DrawPropertiesUpdateFlags, + MeshUsage, + PendingShaderBindings, + RenderPass, + RenderPasses, + SurfaceSpec, + TargetWindow, +}; use crate::texture::{Texture, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL}; use crate::windowing::window::Window; diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs new file mode 100644 index 0000000..961bea2 --- /dev/null +++ b/engine/src/rendering/shader.rs @@ -0,0 +1,1147 @@ +use std::any::type_name; +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt::Debug; +use std::path::Path; +use std::str::Utf8Error; + +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, Pair}; +use crate::ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE}; +use crate::ecs::sole::Single; +use crate::ecs::{declare_entity, Component, Sole}; + +pub mod cursor; +pub mod default; + +/// The vertex parameter of a vertex entrypoint function in a shader should have this +/// semantic name. +pub const VERTEX_PARAM_SEMANTIC_NAME: &str = "VERTEX"; + +#[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 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, +} + +impl Program +{ + pub fn link(&self) -> Result + { + let linked_program = self.inner.link()?; + + Ok(Program { inner: linked_program }) + } + + 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 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) -> impl ExactSizeIterator> + { + self.inner + .fields() + .map(|field| VariableLayout { inner: field }) + } + + pub fn field_cnt(&self) -> u32 + { + self.inner.field_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 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"), + } + } +} + +#[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).map(|(program, _)| program) + } + + pub fn get_program_metadata(&self, asset_id: &AssetId) -> Option<&ProgramMetadata> + { + self.programs + .get(asset_id) + .map(|(_, program_metadata)| program_metadata) + } + + pub fn compose_into_program( + &self, + modules: impl IntoIterator, + entry_points: impl IntoIterator, + ) -> Result + { + let components = + modules + .into_iter() + .map(|module| SlangComponentType::from(module.inner.clone())) + .chain(entry_points.into_iter().map(|entry_point| { + SlangComponentType::from(entry_point.inner.clone()) + })) + .collect::>(); + + let program = self.session.create_composite_component_type(&components)?; + + Ok(Program { inner: program }) + } +} + +#[derive(Debug)] +#[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)] +#[non_exhaustive] +pub struct VertexDescription +{ + pub fields: Box<[VertexFieldDescription]>, +} + +impl VertexDescription +{ + pub fn new( + vs_entrypoint: &EntryPointReflection<'_>, + ) -> Result + { + if vs_entrypoint.stage() != Stage::Vertex { + return Err(VertexDescriptionError::EntrypointNotInVertexStage); + } + + let vs_entrypoint_vertex_param = vs_entrypoint + .parameters() + .find(|param| param.semantic_name() == Some(VERTEX_PARAM_SEMANTIC_NAME)) + .ok_or(VertexDescriptionError::EntrypointMissingVertexParam)?; + + let vs_entrypoint_vertex_param = vs_entrypoint_vertex_param + .type_layout() + .expect("Not possible"); + + if vs_entrypoint_vertex_param.parameter_category() + != ParameterCategory::VaryingInput + { + return Err(VertexDescriptionError::EntryPointVertexParamNotVaryingInput); + } + + if vs_entrypoint_vertex_param.kind() != TypeKind::Struct { + return Err(VertexDescriptionError::EntrypointVertexTypeNotStruct); + } + + let fields = vs_entrypoint_vertex_param + .fields() + .map(|field| { + let varying_input_offset = + field.varying_input_offset().expect("Not possible"); + + let field_ty = field.type_layout().expect("Maybe not possible"); + + let scalar_type = match field_ty.kind() { + TypeKind::Scalar => field_ty.scalar_type().expect("Not possible"), + TypeKind::Vector => { + let Some(scalar_type) = field_ty.scalar_type() else { + return Err( + VertexDescriptionError::UnsupportedVertexFieldType { + field_name: field.name().unwrap_or("").to_string(), + }, + ); + }; + + scalar_type + } + _ => { + return Err(VertexDescriptionError::UnsupportedVertexFieldType { + field_name: field.name().unwrap_or("").to_string(), + }); + } + }; + + Ok(VertexFieldDescription { + name: field.name().unwrap_or("").to_string().into_boxed_str(), + varying_input_offset, + type_kind: field_ty.kind(), + scalar_type, + }) + }) + .collect::, _>>()?; + + Ok(Self { fields: fields.into_boxed_slice() }) + } +} + +#[derive(Debug)] +pub struct VertexFieldDescription +{ + pub name: Box, + pub varying_input_offset: usize, + pub type_kind: TypeKind, + pub scalar_type: ScalarType, +} + +#[derive(Debug, thiserror::Error)] +pub enum VertexDescriptionError +{ + #[error("Entrypoint is not in vertex stage")] + EntrypointNotInVertexStage, + + #[error( + "Entrypoint does not have a vertex parameter (parameter with semantic name {})", + VERTEX_PARAM_SEMANTIC_NAME + )] + EntrypointMissingVertexParam, + + #[error("Entrypoint vertex parameter is not a varying input")] + EntryPointVertexParamNotVaryingInput, + + #[error("Entrypoint vertex type is not a struct")] + EntrypointVertexTypeNotStruct, + + #[error("Type of field '{field_name}' of vertex type is not supported")] + UnsupportedVertexFieldType + { + field_name: String + }, +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] shader_slang::Error); + +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!( + IMPORT_SHADERS_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*HANDLE_ASSETS_PHASE) + .build() + ) +); + +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_column(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) +{ + assets.set_importer::<_, _>(["slang"], import_slang_asset); +} + +#[tracing::instrument(skip_all)] +fn load_modules(mut context: Single, assets: Single) +{ + 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 entry_points = match module_source + .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(EntrypointNotFoundError { entrypoint_name })); + }; + + Some(Ok(entry_point)) + }) + .collect::, EntrypointNotFoundError>>() + { + Ok(entry_points) => entry_points, + Err(EntrypointNotFoundError { entrypoint_name }) => { + tracing::error!( + "Shader module does not have a '{entrypoint_name}' entry point" + ); + continue; + } + }; + + let shader_program = + match context.compose_into_program([module], entry_points) { + Ok(shader_program) => shader_program, + Err(err) => { + tracing::error!("Failed to compose shader into program: {err}"); + continue; + } + }; + + let linked_shader_program = match shader_program.link() { + Ok(linked_shader_program) => linked_shader_program, + Err(err) => { + tracing::error!("Failed to link shader: {err}"); + continue; + } + }; + + let vertex_desc = if module_source + .link_entrypoints + .contains(EntrypointFlags::VERTEX) + { + VertexDescription::new( + &shader_program + .reflection(0) + .expect("Not possible") + .get_entry_point_by_name("vertex_main") + .expect("Not possible"), + ) + .inspect_err(|err| { + tracing::error!( + "Failed to create a vertex description for shader {}: {}", + asset_label, + err + ); + }) + .ok() + } else { + None + }; + + context.programs.insert( + *asset_id, + (linked_shader_program, ProgramMetadata { vertex_desc }), + ); + } + } +} + +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 }) +} + +#[derive(Debug)] +struct EntrypointNotFoundError +{ + entrypoint_name: &'static str, +} diff --git a/engine/src/rendering/shader/cursor.rs b/engine/src/rendering/shader/cursor.rs new file mode 100644 index 0000000..17f5748 --- /dev/null +++ b/engine/src/rendering/shader/cursor.rs @@ -0,0 +1,159 @@ +use crate::data_types::matrix::Matrix; +use crate::data_types::vector::Vec3; +use crate::rendering::object::Id as RenderingObjectId; +use crate::rendering::shader::{TypeKind, TypeLayout, VariableLayout}; + +/// Shader cursor +#[derive(Clone)] +pub struct Cursor<'a> +{ + type_layout: TypeLayout<'a>, + binding_location: BindingLocation, +} + +impl<'a> Cursor<'a> +{ + pub fn new(var_layout: VariableLayout<'a>) -> Self + { + let binding_location = BindingLocation { + binding_index: var_layout.binding_index(), + binding_size: 0, + byte_offset: var_layout.offset(), + }; + + Self { + type_layout: var_layout.type_layout().unwrap(), + binding_location, + } + } + + pub fn field(&self, name: &str) -> Self + { + let Some(field_var_layout) = self.type_layout.get_field_by_name(name) else { + panic!("Field '{name}' does not exist"); + }; + + let field_type_kind = field_var_layout.ty().unwrap().kind(); + + let field_var_layout = match field_type_kind { + TypeKind::ConstantBuffer => field_var_layout + .type_layout() + .expect("Constant buffer field has no type layout") + .element_var_layout() + .expect("Constant buffer field type layout has no element var layout"), + TypeKind::Array + | TypeKind::Matrix + | TypeKind::Scalar + | TypeKind::Vector + | TypeKind::Struct + | TypeKind::Resource => field_var_layout, + type_kind => unimplemented!("Type kind {type_kind:?} is not yet supported"), + }; + + Self { + type_layout: field_var_layout.type_layout().unwrap(), + binding_location: BindingLocation { + binding_index: self.binding_location.binding_index + + field_var_layout.binding_index(), + binding_size: if field_type_kind == TypeKind::ConstantBuffer { + field_var_layout + .type_layout() + .unwrap() + .uniform_size() + .unwrap() + } else { + self.binding_location.binding_size + }, + byte_offset: self.binding_location.byte_offset + + field_var_layout.offset(), + }, + } + } + + pub fn element(mut self, index: usize) -> Self + { + let element_type_layout = self.type_layout.element_type_layout().unwrap(); + + self.binding_location.byte_offset += index * element_type_layout.stride(); + + self.type_layout = element_type_layout; + + self + } + + pub fn with_field(self, name: &str, func: impl FnOnce(Self) -> Self) -> Self + { + let _ = func(self.field(name)); + + self + } + + pub fn binding_location(&self) -> &BindingLocation + { + &self.binding_location + } + + pub fn into_binding_location(self) -> BindingLocation + { + self.binding_location + } +} + +#[derive(Debug, Clone)] +pub struct BindingLocation +{ + pub binding_index: u32, + pub binding_size: usize, + pub byte_offset: usize, +} + +#[derive(Debug, Clone)] +pub enum BindingValue +{ + Uint(u32), + Int(i32), + Float(f32), + FVec3(Vec3), + FMat4x4(Matrix), + Texture(RenderingObjectId), +} + +impl From for BindingValue +{ + fn from(value: u32) -> Self + { + BindingValue::Uint(value) + } +} + +impl From for BindingValue +{ + fn from(value: i32) -> Self + { + BindingValue::Int(value) + } +} + +impl From for BindingValue +{ + fn from(value: f32) -> Self + { + BindingValue::Float(value) + } +} + +impl From> for BindingValue +{ + fn from(vec: Vec3) -> Self + { + BindingValue::FVec3(vec) + } +} + +impl From> for BindingValue +{ + fn from(matrix: Matrix) -> Self + { + BindingValue::FMat4x4(matrix) + } +} diff --git a/engine/src/rendering/shader/default.rs b/engine/src/rendering/shader/default.rs new file mode 100644 index 0000000..a60fd61 --- /dev/null +++ b/engine/src/rendering/shader/default.rs @@ -0,0 +1,397 @@ +use std::path::Path; +use std::sync::LazyLock; + +use ecs::query::term::With; + +use crate::asset::{Assets, Label as AssetLabel}; +use crate::camera::{Active as ActiveCamera, Camera}; +use crate::data_types::dimens::Dimens; +use crate::draw_flags::NoDraw; +use crate::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; +use crate::ecs::Query; +use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; +use crate::material::{Flags as MaterialFlags, Material}; +use crate::matrix::Matrix; +use crate::model::{MaterialSearchResult, Model}; +use crate::projection::{ClipVolume as ProjectionClipVolume, Projection}; +use crate::rendering::shader::cursor::{ + BindingValue as ShaderBindingValue, + Cursor as ShaderCursor, +}; +use crate::rendering::shader::{ + Context as ShaderContext, + EntrypointFlags as ShaderEntrypointFlags, + ModuleSource as ShaderModuleSource, + Shader, +}; +use crate::rendering::{ + PendingShaderBindings, + SurfaceSpec, + TargetWindow as RenderingTargetWindow, +}; +use crate::texture::WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL; +use crate::transform::{Scale, Transform, WorldPosition}; +use crate::vector::Vec3; +use crate::windowing::dpi::PhysicalSize; +use crate::windowing::window::Window; + +pub static ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { + path: Path::new("").into(), + name: Some("default_shader".into()), +}); + +pub fn initialize(mut assets: Single) +{ + assets.store_with_label( + ASSET_LABEL.clone(), + ShaderModuleSource { + name: "default_shader.slang".into(), + file_path: Path::new("@engine/default_shader").into(), + source: include_str!("../../../res/default_shader.slang").into(), + link_entrypoints: ShaderEntrypointFlags::VERTEX + | ShaderEntrypointFlags::FRAGMENT, + }, + ); +} + +pub fn enqueue_set_shader_bindings( + renderable_query: Query, (Without,)>, + camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, + window_query: Query<(&Window, &SurfaceSpec), (With,)>, + point_light_query: Query<(&PointLight, &WorldPosition)>, + directional_light_query: Query<(&DirectionalLight,)>, + assets: Single, + shader_context: Single, + global_light: Single, + mut actions: Actions, +) +{ + let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { + tracing::trace!("No current camera"); + return; + }; + + let default_shader_asset = assets + .get_handle_to_loaded::(ASSET_LABEL.clone()) + .unwrap(); + + for ( + entity_id, + (model, material_flags, world_pos, scale, shader, mut pending_shader_bindings), + ) in renderable_query.iter_with_euids() + { + let shader_asset = match &shader { + Some(shader) => &shader.asset_handle, + None => &default_shader_asset, + }; + + if shader_asset.id() != default_shader_asset.id() { + continue; + } + + let has_pending_shader_bindings_comp = pending_shader_bindings.is_some(); + + let shader_bindings = match pending_shader_bindings.as_deref_mut() { + Some(pending_shader_bindings) => pending_shader_bindings, + None => &mut PendingShaderBindings::default(), + }; + + // Bindings are cleared to prevent them from growing infinitely + shader_bindings.bindings.clear(); + shader_bindings.surface_specific_bindings.clear(); + + let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { + continue; + }; + + let Some(model_spec) = assets.get(&model.spec_asset) else { + continue; + }; + + let shader_cursor = ShaderCursor::new( + shader_program + .reflection(0) + .unwrap() + .global_params_var_layout() + .unwrap(), + ); + + let model_matrix = create_model_matrix(Transform { + position: world_pos.as_deref().cloned().unwrap_or_default().position, + scale: scale.as_deref().cloned().unwrap_or_default().scale, + }); + + let inverted_model_matrix = model_matrix.inverse(); + + let model_material = match model_spec.find_first_material(&assets) { + MaterialSearchResult::Found(model_material_asset) => { + let Some(model_material) = assets.get(&model_material_asset) else { + continue; + }; + + model_material + } + MaterialSearchResult::NotFound => { + continue; + } + MaterialSearchResult::NoMaterials => &const { Material::builder().build() }, + }; + + if [ + &model_material.ambient_map, + &model_material.diffuse_map, + &model_material.specular_map, + ] + .into_iter() + .flatten() + .any(|texture_asset| !assets.is_loaded_and_has_type(&texture_asset)) + { + continue; + } + + let material_flags = material_flags + .as_deref() + .unwrap_or(&const { MaterialFlags::builder().build() }); + + let model_3d_shader_cursor = shader_cursor.field("Uniforms").field("model_3d"); + let lighting_shader_cursor = shader_cursor.field("Uniforms").field("lighting"); + let material_shader_cursor = lighting_shader_cursor.field("material"); + + shader_bindings.extend([ + (model_3d_shader_cursor.field("model"), model_matrix.into()), + ( + model_3d_shader_cursor.field("model_inverted"), + inverted_model_matrix.into(), + ), + ( + model_3d_shader_cursor.field("view"), + create_view_matrix(&camera, camera_world_pos.position).into(), + ), + ( + lighting_shader_cursor.field("view_pos"), + camera_world_pos.position.into(), + ), + ( + material_shader_cursor.field("ambient"), + Vec3::from( + if material_flags.use_ambient_color { + &model_material.ambient + } else { + &global_light.ambient + } + .clone(), + ) + .into(), + ), + ( + material_shader_cursor.field("diffuse"), + Vec3::from(model_material.diffuse.clone()).into(), + ), + ( + material_shader_cursor.field("specular"), + Vec3::from(model_material.specular.clone()).into(), + ), + ( + material_shader_cursor.field("shininess"), + model_material.shininess.into(), + ), + ( + lighting_shader_cursor.field("directional_light_cnt"), + u32::try_from(directional_light_query.iter().count()) + .expect( + "Directional light count does not fit in 32-bit unsigned integer", + ) + .into(), + ), + ( + lighting_shader_cursor.field("point_light_cnt"), + u32::try_from(point_light_query.iter().count()) + .expect("Point light count does not fit in 32-bit unsigned integer") + .into(), + ), + ]); + + for (window, window_surface_spec) in &window_query { + shader_bindings.surface_specific_bindings.push(( + window_surface_spec.id, + model_3d_shader_cursor + .field("projection") + .into_binding_location(), + create_projection_matrix(&camera, &window.inner_size).into(), + )); + } + + shader_bindings.extend(point_light_query.iter().enumerate().flat_map( + |(point_light_index, (point_light, point_light_world_pos))| { + let point_light_shader_cursor = lighting_shader_cursor + .field("point_lights") + .element(point_light_index); + + let phong_shader_cursor = point_light_shader_cursor.field("phong"); + + let attenuation_props_shader_cursor = + point_light_shader_cursor.field("attenuation_props"); + + [ + ( + phong_shader_cursor.field("diffuse"), + Vec3::from(point_light.diffuse.clone()).into(), + ), + ( + phong_shader_cursor.field("specular"), + Vec3::from(point_light.specular.clone()).into(), + ), + ( + point_light_shader_cursor.field("position"), + (point_light_world_pos.position + point_light.local_position) + .into(), + ), + ( + attenuation_props_shader_cursor.field("constant"), + point_light.attenuation_params.constant.into(), + ), + ( + attenuation_props_shader_cursor.field("linear"), + point_light.attenuation_params.linear.into(), + ), + ( + attenuation_props_shader_cursor.field("quadratic"), + point_light.attenuation_params.quadratic.into(), + ), + ] + }, + )); + + shader_bindings.extend(directional_light_query.iter().enumerate().flat_map( + |(directional_light_index, (directional_light,))| { + let directional_light_shader_cursor = lighting_shader_cursor + .field("directional_lights") + .element(directional_light_index); + + let phong_shader_cursor = directional_light_shader_cursor.field("phong"); + + [ + ( + phong_shader_cursor.field("diffuse"), + Vec3::from(directional_light.diffuse.clone()).into(), + ), + ( + phong_shader_cursor.field("specular"), + Vec3::from(directional_light.specular.clone()).into(), + ), + ( + directional_light_shader_cursor.field("direction"), + directional_light.direction.into(), + ), + ] + }, + )); + + shader_bindings.bindings.push(( + shader_cursor.field("ambient_map").into_binding_location(), + ShaderBindingValue::Texture( + model_material + .ambient_map + .as_ref() + .map(|ambient_map| ambient_map.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) + .expect("Not possible") + }) + .id() + .into(), + ), + )); + + shader_bindings.bindings.push(( + shader_cursor.field("diffuse_map").into_binding_location(), + ShaderBindingValue::Texture( + model_material + .diffuse_map + .as_ref() + .map(|diffuse_map| diffuse_map.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) + .expect("Not possible") + }) + .id() + .into(), + ), + )); + + shader_bindings.bindings.push(( + shader_cursor.field("specular_map").into_binding_location(), + ShaderBindingValue::Texture( + model_material + .specular_map + .as_ref() + .map(|specular_map| specular_map.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) + .expect("Not possible") + }) + .id() + .into(), + ), + )); + + if !has_pending_shader_bindings_comp { + actions.add_components(entity_id, (shader_bindings.clone(),)); + } + } +} + +fn create_model_matrix(transform: Transform) -> Matrix +{ + let mut matrix = Matrix::new_identity(); + + matrix.translate(&transform.position); + matrix.scale(&transform.scale); + + matrix +} + +fn create_view_matrix(camera: &Camera, camera_world_pos: Vec3) -> Matrix +{ + let mut view = Matrix::new(); + + // tracing::debug!("Camera target: {:?}", camera.target); + + view.look_at(camera_world_pos, camera.target, camera.global_up); + + view +} + +fn create_projection_matrix( + camera: &Camera, + window_size: &PhysicalSize, +) -> Matrix +{ + match &camera.projection { + Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( + window_size.width as f32 / window_size.height as f32, + ProjectionClipVolume::NegOneToOne, + ), + Projection::Orthographic(orthographic_proj) => orthographic_proj.to_matrix_rh( + Dimens { + width: window_size.width as f32, + height: window_size.height as f32, + }, + ProjectionClipVolume::NegOneToOne, + ), + } +} + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a MaterialFlags>, + Option<&'a WorldPosition>, + Option<&'a Scale>, + Option<&'a Shader>, + Option<&'a mut PendingShaderBindings>, +); diff --git a/engine/src/shader.rs b/engine/src/shader.rs deleted file mode 100644 index dda4191..0000000 --- a/engine/src/shader.rs +++ /dev/null @@ -1,1166 +0,0 @@ -use std::any::type_name; -use std::borrow::Cow; -use std::collections::HashMap; -use std::fmt::Debug; -use std::path::Path; -use std::str::Utf8Error; - -use bitflags::{bitflags, bitflags_match}; -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, Pair}; -use crate::ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE, START as START_PHASE}; -use crate::ecs::sole::Single; -use crate::ecs::{declare_entity, Component, Sole}; -use crate::shader::default::{ - enqueue_set_shader_bindings as default_shader_enqueue_set_shader_bindings, - ASSET_LABEL, -}; - -pub mod cursor; -pub mod default; - -/// The vertex parameter of a vertex entrypoint function in a shader should have this -/// semantic name. -pub const VERTEX_PARAM_SEMANTIC_NAME: &str = "VERTEX"; - -#[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 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, -} - -impl Program -{ - pub fn link(&self) -> Result - { - let linked_program = self.inner.link()?; - - Ok(Program { inner: linked_program }) - } - - 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 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) -> impl ExactSizeIterator> - { - self.inner - .fields() - .map(|field| VariableLayout { inner: field }) - } - - pub fn field_cnt(&self) -> u32 - { - self.inner.field_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 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"), - } - } -} - -#[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).map(|(program, _)| program) - } - - pub fn get_program_metadata(&self, asset_id: &AssetId) -> Option<&ProgramMetadata> - { - self.programs - .get(asset_id) - .map(|(_, program_metadata)| program_metadata) - } - - pub fn compose_into_program( - &self, - modules: impl IntoIterator, - entry_points: impl IntoIterator, - ) -> Result - { - let components = - modules - .into_iter() - .map(|module| SlangComponentType::from(module.inner.clone())) - .chain(entry_points.into_iter().map(|entry_point| { - SlangComponentType::from(entry_point.inner.clone()) - })) - .collect::>(); - - let program = self.session.create_composite_component_type(&components)?; - - Ok(Program { inner: program }) - } -} - -#[derive(Debug)] -#[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)] -#[non_exhaustive] -pub struct VertexDescription -{ - pub fields: Box<[VertexFieldDescription]>, -} - -impl VertexDescription -{ - pub fn new( - vs_entrypoint: &EntryPointReflection<'_>, - ) -> Result - { - if vs_entrypoint.stage() != Stage::Vertex { - return Err(VertexDescriptionError::EntrypointNotInVertexStage); - } - - let vs_entrypoint_vertex_param = vs_entrypoint - .parameters() - .find(|param| param.semantic_name() == Some(VERTEX_PARAM_SEMANTIC_NAME)) - .ok_or(VertexDescriptionError::EntrypointMissingVertexParam)?; - - let vs_entrypoint_vertex_param = vs_entrypoint_vertex_param - .type_layout() - .expect("Not possible"); - - if vs_entrypoint_vertex_param.parameter_category() - != ParameterCategory::VaryingInput - { - return Err(VertexDescriptionError::EntryPointVertexParamNotVaryingInput); - } - - if vs_entrypoint_vertex_param.kind() != TypeKind::Struct { - return Err(VertexDescriptionError::EntrypointVertexTypeNotStruct); - } - - let fields = vs_entrypoint_vertex_param - .fields() - .map(|field| { - let varying_input_offset = - field.varying_input_offset().expect("Not possible"); - - let field_ty = field.type_layout().expect("Maybe not possible"); - - let scalar_type = match field_ty.kind() { - TypeKind::Scalar => field_ty.scalar_type().expect("Not possible"), - TypeKind::Vector => { - let Some(scalar_type) = field_ty.scalar_type() else { - return Err( - VertexDescriptionError::UnsupportedVertexFieldType { - field_name: field.name().unwrap_or("").to_string(), - }, - ); - }; - - scalar_type - } - _ => { - return Err(VertexDescriptionError::UnsupportedVertexFieldType { - field_name: field.name().unwrap_or("").to_string(), - }); - } - }; - - Ok(VertexFieldDescription { - name: field.name().unwrap_or("").to_string().into_boxed_str(), - varying_input_offset, - type_kind: field_ty.kind(), - scalar_type, - }) - }) - .collect::, _>>()?; - - Ok(Self { fields: fields.into_boxed_slice() }) - } -} - -#[derive(Debug)] -pub struct VertexFieldDescription -{ - pub name: Box, - pub varying_input_offset: usize, - pub type_kind: TypeKind, - pub scalar_type: ScalarType, -} - -#[derive(Debug, thiserror::Error)] -pub enum VertexDescriptionError -{ - #[error("Entrypoint is not in vertex stage")] - EntrypointNotInVertexStage, - - #[error( - "Entrypoint does not have a vertex parameter (parameter with semantic name {})", - VERTEX_PARAM_SEMANTIC_NAME - )] - EntrypointMissingVertexParam, - - #[error("Entrypoint vertex parameter is not a varying input")] - EntryPointVertexParamNotVaryingInput, - - #[error("Entrypoint vertex type is not a struct")] - EntrypointVertexTypeNotStruct, - - #[error("Type of field '{field_name}' of vertex type is not supported")] - UnsupportedVertexFieldType - { - field_name: String - }, -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] shader_slang::Error); - -pub(crate) fn add_asset_importers(assets: &mut Assets) -{ - assets.set_importer::<_, _>(["slang"], import_slang_asset); -} - -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!( - IMPORT_SHADERS_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*HANDLE_ASSETS_PHASE) - .build() - ) -); - -pub(crate) struct Extension; - -impl crate::ecs::extension::Extension for Extension -{ - fn collect(self, mut collector: 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_column(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(*START_PHASE, initialize); - collector.add_system(*IMPORT_SHADERS_PHASE, load_modules); - - collector.add_system( - *POST_UPDATE_PHASE, - default_shader_enqueue_set_shader_bindings, - ); - } -} - -fn initialize(mut assets: Single) -{ - assets.store_with_label( - ASSET_LABEL.clone(), - ModuleSource { - name: "default_shader.slang".into(), - file_path: Path::new("@engine/default_shader").into(), - source: include_str!("../res/default_shader.slang").into(), - link_entrypoints: EntrypointFlags::VERTEX | EntrypointFlags::FRAGMENT, - }, - ); -} - -#[tracing::instrument(skip_all)] -fn load_modules(mut context: Single, assets: Single) -{ - 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 entry_points = match module_source - .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(EntrypointNotFoundError { entrypoint_name })); - }; - - Some(Ok(entry_point)) - }) - .collect::, EntrypointNotFoundError>>() - { - Ok(entry_points) => entry_points, - Err(EntrypointNotFoundError { entrypoint_name }) => { - tracing::error!( - "Shader module does not have a '{entrypoint_name}' entry point" - ); - continue; - } - }; - - let shader_program = - match context.compose_into_program([module], entry_points) { - Ok(shader_program) => shader_program, - Err(err) => { - tracing::error!("Failed to compose shader into program: {err}"); - continue; - } - }; - - let linked_shader_program = match shader_program.link() { - Ok(linked_shader_program) => linked_shader_program, - Err(err) => { - tracing::error!("Failed to link shader: {err}"); - continue; - } - }; - - let vertex_desc = if module_source - .link_entrypoints - .contains(EntrypointFlags::VERTEX) - { - VertexDescription::new( - &shader_program - .reflection(0) - .expect("Not possible") - .get_entry_point_by_name("vertex_main") - .expect("Not possible"), - ) - .inspect_err(|err| { - tracing::error!( - "Failed to create a vertex description for shader {}: {}", - asset_label, - err - ); - }) - .ok() - } else { - None - }; - - context.programs.insert( - *asset_id, - (linked_shader_program, ProgramMetadata { vertex_desc }), - ); - } - } -} - -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 }) -} - -#[derive(Debug)] -struct EntrypointNotFoundError -{ - entrypoint_name: &'static str, -} diff --git a/engine/src/shader/cursor.rs b/engine/src/shader/cursor.rs deleted file mode 100644 index 8011012..0000000 --- a/engine/src/shader/cursor.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::data_types::matrix::Matrix; -use crate::data_types::vector::Vec3; -use crate::rendering::object::Id as RenderingObjectId; -use crate::shader::{TypeKind, TypeLayout, VariableLayout}; - -/// Shader cursor -#[derive(Clone)] -pub struct Cursor<'a> -{ - type_layout: TypeLayout<'a>, - binding_location: BindingLocation, -} - -impl<'a> Cursor<'a> -{ - pub fn new(var_layout: VariableLayout<'a>) -> Self - { - let binding_location = BindingLocation { - binding_index: var_layout.binding_index(), - binding_size: 0, - byte_offset: var_layout.offset(), - }; - - Self { - type_layout: var_layout.type_layout().unwrap(), - binding_location, - } - } - - pub fn field(&self, name: &str) -> Self - { - let Some(field_var_layout) = self.type_layout.get_field_by_name(name) else { - panic!("Field '{name}' does not exist"); - }; - - let field_type_kind = field_var_layout.ty().unwrap().kind(); - - let field_var_layout = match field_type_kind { - TypeKind::ConstantBuffer => field_var_layout - .type_layout() - .expect("Constant buffer field has no type layout") - .element_var_layout() - .expect("Constant buffer field type layout has no element var layout"), - TypeKind::Array - | TypeKind::Matrix - | TypeKind::Scalar - | TypeKind::Vector - | TypeKind::Struct - | TypeKind::Resource => field_var_layout, - type_kind => unimplemented!("Type kind {type_kind:?} is not yet supported"), - }; - - Self { - type_layout: field_var_layout.type_layout().unwrap(), - binding_location: BindingLocation { - binding_index: self.binding_location.binding_index - + field_var_layout.binding_index(), - binding_size: if field_type_kind == TypeKind::ConstantBuffer { - field_var_layout - .type_layout() - .unwrap() - .uniform_size() - .unwrap() - } else { - self.binding_location.binding_size - }, - byte_offset: self.binding_location.byte_offset - + field_var_layout.offset(), - }, - } - } - - pub fn element(mut self, index: usize) -> Self - { - let element_type_layout = self.type_layout.element_type_layout().unwrap(); - - self.binding_location.byte_offset += index * element_type_layout.stride(); - - self.type_layout = element_type_layout; - - self - } - - pub fn with_field(self, name: &str, func: impl FnOnce(Self) -> Self) -> Self - { - let _ = func(self.field(name)); - - self - } - - pub fn binding_location(&self) -> &BindingLocation - { - &self.binding_location - } - - pub fn into_binding_location(self) -> BindingLocation - { - self.binding_location - } -} - -#[derive(Debug, Clone)] -pub struct BindingLocation -{ - pub binding_index: u32, - pub binding_size: usize, - pub byte_offset: usize, -} - -#[derive(Debug, Clone)] -pub enum BindingValue -{ - Uint(u32), - Int(i32), - Float(f32), - FVec3(Vec3), - FMat4x4(Matrix), - Texture(RenderingObjectId), -} - -impl From for BindingValue -{ - fn from(value: u32) -> Self - { - BindingValue::Uint(value) - } -} - -impl From for BindingValue -{ - fn from(value: i32) -> Self - { - BindingValue::Int(value) - } -} - -impl From for BindingValue -{ - fn from(value: f32) -> Self - { - BindingValue::Float(value) - } -} - -impl From> for BindingValue -{ - fn from(vec: Vec3) -> Self - { - BindingValue::FVec3(vec) - } -} - -impl From> for BindingValue -{ - fn from(matrix: Matrix) -> Self - { - BindingValue::FMat4x4(matrix) - } -} diff --git a/engine/src/shader/default.rs b/engine/src/shader/default.rs deleted file mode 100644 index c174e8b..0000000 --- a/engine/src/shader/default.rs +++ /dev/null @@ -1,379 +0,0 @@ -use std::path::Path; -use std::sync::LazyLock; - -use ecs::query::term::With; - -use crate::asset::{Assets, Label as AssetLabel}; -use crate::camera::{Active as ActiveCamera, Camera}; -use crate::data_types::dimens::Dimens; -use crate::draw_flags::NoDraw; -use crate::ecs::actions::Actions; -use crate::ecs::query::term::Without; -use crate::ecs::sole::Single; -use crate::ecs::Query; -use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; -use crate::material::{Flags as MaterialFlags, Material}; -use crate::matrix::Matrix; -use crate::model::{MaterialSearchResult, Model}; -use crate::projection::{ClipVolume as ProjectionClipVolume, Projection}; -use crate::rendering::{ - PendingShaderBindings, - SurfaceSpec, - TargetWindow as RenderingTargetWindow, -}; -use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor}; -use crate::shader::{ - Context as ShaderContext, - ModuleSource as ShaderModuleSource, - Shader, -}; -use crate::texture::WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL; -use crate::transform::{Scale, Transform, WorldPosition}; -use crate::vector::Vec3; -use crate::windowing::dpi::PhysicalSize; -use crate::windowing::window::Window; - -pub static ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { - path: Path::new("").into(), - name: Some("default_shader".into()), -}); - -pub fn enqueue_set_shader_bindings( - renderable_query: Query, (Without,)>, - camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, - window_query: Query<(&Window, &SurfaceSpec), (With,)>, - point_light_query: Query<(&PointLight, &WorldPosition)>, - directional_light_query: Query<(&DirectionalLight,)>, - assets: Single, - shader_context: Single, - global_light: Single, - mut actions: Actions, -) -{ - let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { - tracing::trace!("No current camera"); - return; - }; - - let default_shader_asset = assets - .get_handle_to_loaded::(ASSET_LABEL.clone()) - .unwrap(); - - for ( - entity_id, - (model, material_flags, world_pos, scale, shader, mut pending_shader_bindings), - ) in renderable_query.iter_with_euids() - { - let shader_asset = match &shader { - Some(shader) => &shader.asset_handle, - None => &default_shader_asset, - }; - - if shader_asset.id() != default_shader_asset.id() { - continue; - } - - let has_pending_shader_bindings_comp = pending_shader_bindings.is_some(); - - let shader_bindings = match pending_shader_bindings.as_deref_mut() { - Some(pending_shader_bindings) => pending_shader_bindings, - None => &mut PendingShaderBindings::default(), - }; - - // Bindings are cleared to prevent them from growing infinitely - shader_bindings.bindings.clear(); - shader_bindings.surface_specific_bindings.clear(); - - let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { - continue; - }; - - let Some(model_spec) = assets.get(&model.spec_asset) else { - continue; - }; - - let shader_cursor = ShaderCursor::new( - shader_program - .reflection(0) - .unwrap() - .global_params_var_layout() - .unwrap(), - ); - - let model_matrix = create_model_matrix(Transform { - position: world_pos.as_deref().cloned().unwrap_or_default().position, - scale: scale.as_deref().cloned().unwrap_or_default().scale, - }); - - let inverted_model_matrix = model_matrix.inverse(); - - let model_material = match model_spec.find_first_material(&assets) { - MaterialSearchResult::Found(model_material_asset) => { - let Some(model_material) = assets.get(&model_material_asset) else { - continue; - }; - - model_material - } - MaterialSearchResult::NotFound => { - continue; - } - MaterialSearchResult::NoMaterials => &const { Material::builder().build() }, - }; - - if [ - &model_material.ambient_map, - &model_material.diffuse_map, - &model_material.specular_map, - ] - .into_iter() - .flatten() - .any(|texture_asset| !assets.is_loaded_and_has_type(&texture_asset)) - { - continue; - } - - let material_flags = material_flags - .as_deref() - .unwrap_or(&const { MaterialFlags::builder().build() }); - - let model_3d_shader_cursor = shader_cursor.field("Uniforms").field("model_3d"); - let lighting_shader_cursor = shader_cursor.field("Uniforms").field("lighting"); - let material_shader_cursor = lighting_shader_cursor.field("material"); - - shader_bindings.extend([ - (model_3d_shader_cursor.field("model"), model_matrix.into()), - ( - model_3d_shader_cursor.field("model_inverted"), - inverted_model_matrix.into(), - ), - ( - model_3d_shader_cursor.field("view"), - create_view_matrix(&camera, camera_world_pos.position).into(), - ), - ( - lighting_shader_cursor.field("view_pos"), - camera_world_pos.position.into(), - ), - ( - material_shader_cursor.field("ambient"), - Vec3::from( - if material_flags.use_ambient_color { - &model_material.ambient - } else { - &global_light.ambient - } - .clone(), - ) - .into(), - ), - ( - material_shader_cursor.field("diffuse"), - Vec3::from(model_material.diffuse.clone()).into(), - ), - ( - material_shader_cursor.field("specular"), - Vec3::from(model_material.specular.clone()).into(), - ), - ( - material_shader_cursor.field("shininess"), - model_material.shininess.into(), - ), - ( - lighting_shader_cursor.field("directional_light_cnt"), - u32::try_from(directional_light_query.iter().count()) - .expect( - "Directional light count does not fit in 32-bit unsigned integer", - ) - .into(), - ), - ( - lighting_shader_cursor.field("point_light_cnt"), - u32::try_from(point_light_query.iter().count()) - .expect("Point light count does not fit in 32-bit unsigned integer") - .into(), - ), - ]); - - for (window, window_surface_spec) in &window_query { - shader_bindings.surface_specific_bindings.push(( - window_surface_spec.id, - model_3d_shader_cursor - .field("projection") - .into_binding_location(), - create_projection_matrix(&camera, &window.inner_size).into(), - )); - } - - shader_bindings.extend(point_light_query.iter().enumerate().flat_map( - |(point_light_index, (point_light, point_light_world_pos))| { - let point_light_shader_cursor = lighting_shader_cursor - .field("point_lights") - .element(point_light_index); - - let phong_shader_cursor = point_light_shader_cursor.field("phong"); - - let attenuation_props_shader_cursor = - point_light_shader_cursor.field("attenuation_props"); - - [ - ( - phong_shader_cursor.field("diffuse"), - Vec3::from(point_light.diffuse.clone()).into(), - ), - ( - phong_shader_cursor.field("specular"), - Vec3::from(point_light.specular.clone()).into(), - ), - ( - point_light_shader_cursor.field("position"), - (point_light_world_pos.position + point_light.local_position) - .into(), - ), - ( - attenuation_props_shader_cursor.field("constant"), - point_light.attenuation_params.constant.into(), - ), - ( - attenuation_props_shader_cursor.field("linear"), - point_light.attenuation_params.linear.into(), - ), - ( - attenuation_props_shader_cursor.field("quadratic"), - point_light.attenuation_params.quadratic.into(), - ), - ] - }, - )); - - shader_bindings.extend(directional_light_query.iter().enumerate().flat_map( - |(directional_light_index, (directional_light,))| { - let directional_light_shader_cursor = lighting_shader_cursor - .field("directional_lights") - .element(directional_light_index); - - let phong_shader_cursor = directional_light_shader_cursor.field("phong"); - - [ - ( - phong_shader_cursor.field("diffuse"), - Vec3::from(directional_light.diffuse.clone()).into(), - ), - ( - phong_shader_cursor.field("specular"), - Vec3::from(directional_light.specular.clone()).into(), - ), - ( - directional_light_shader_cursor.field("direction"), - directional_light.direction.into(), - ), - ] - }, - )); - - shader_bindings.bindings.push(( - shader_cursor.field("ambient_map").into_binding_location(), - ShaderBindingValue::Texture( - model_material - .ambient_map - .as_ref() - .map(|ambient_map| ambient_map.clone()) - .unwrap_or_else(|| { - assets - .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) - .expect("Not possible") - }) - .id() - .into(), - ), - )); - - shader_bindings.bindings.push(( - shader_cursor.field("diffuse_map").into_binding_location(), - ShaderBindingValue::Texture( - model_material - .diffuse_map - .as_ref() - .map(|diffuse_map| diffuse_map.clone()) - .unwrap_or_else(|| { - assets - .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) - .expect("Not possible") - }) - .id() - .into(), - ), - )); - - shader_bindings.bindings.push(( - shader_cursor.field("specular_map").into_binding_location(), - ShaderBindingValue::Texture( - model_material - .specular_map - .as_ref() - .map(|specular_map| specular_map.clone()) - .unwrap_or_else(|| { - assets - .get_handle_to_loaded(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) - .expect("Not possible") - }) - .id() - .into(), - ), - )); - - if !has_pending_shader_bindings_comp { - actions.add_components(entity_id, (shader_bindings.clone(),)); - } - } -} - -fn create_model_matrix(transform: Transform) -> Matrix -{ - let mut matrix = Matrix::new_identity(); - - matrix.translate(&transform.position); - matrix.scale(&transform.scale); - - matrix -} - -fn create_view_matrix(camera: &Camera, camera_world_pos: Vec3) -> Matrix -{ - let mut view = Matrix::new(); - - // tracing::debug!("Camera target: {:?}", camera.target); - - view.look_at(camera_world_pos, camera.target, camera.global_up); - - view -} - -fn create_projection_matrix( - camera: &Camera, - window_size: &PhysicalSize, -) -> Matrix -{ - match &camera.projection { - Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( - window_size.width as f32 / window_size.height as f32, - ProjectionClipVolume::NegOneToOne, - ), - Projection::Orthographic(orthographic_proj) => orthographic_proj.to_matrix_rh( - Dimens { - width: window_size.width as f32, - height: window_size.height as f32, - }, - ProjectionClipVolume::NegOneToOne, - ), - } -} - -type RenderableEntity<'a> = ( - &'a Model, - Option<&'a MaterialFlags>, - Option<&'a WorldPosition>, - Option<&'a Scale>, - Option<&'a Shader>, - Option<&'a mut PendingShaderBindings>, -); diff --git a/engine/src/ui/imgui.rs b/engine/src/ui/imgui.rs index 5565455..45efc6d 100644 --- a/engine/src/ui/imgui.rs +++ b/engine/src/ui/imgui.rs @@ -49,6 +49,15 @@ use crate::rendering::blending::{ Factor as RenderingBlendingFactor, }; use crate::rendering::object::{Id as RenderingObjectId, Store as RenderingObjectStore}; +use crate::rendering::shader::cursor::{ + BindingValue as ShaderBindingValue, + Cursor as ShaderCursor, +}; +use crate::rendering::shader::{ + Context as ShaderContext, + EntrypointFlags as ShaderEntrypointFlags, + ModuleSource as ShaderModuleSource, +}; use crate::rendering::{ Command as RenderingCommand, DrawMeshOptions, @@ -60,12 +69,6 @@ use crate::rendering::{ SurfaceSpec, PRE_RENDER_PHASE, }; -use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor}; -use crate::shader::{ - Context as ShaderContext, - EntrypointFlags as ShaderEntrypointFlags, - ModuleSource as ShaderModuleSource, -}; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, -- cgit v1.2.3-18-g5258