summaryrefslogtreecommitdiff
path: root/engine/src/rendering
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/rendering')
-rw-r--r--engine/src/rendering/backend/opengl.rs14
-rw-r--r--engine/src/rendering/backend/opengl/graphics_mesh.rs2
-rw-r--r--engine/src/rendering/main_render_pass.rs20
-rw-r--r--engine/src/rendering/shader.rs1147
-rw-r--r--engine/src/rendering/shader/cursor.rs159
-rw-r--r--engine/src/rendering/shader/default.rs397
6 files changed, 1726 insertions, 13 deletions
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<ModuleSource>,
+}
+
+/// 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<Item = EntryPoint> + use<'_>
+ {
+ self.inner
+ .entry_points()
+ .map(|entry_point| EntryPoint { inner: entry_point })
+ }
+
+ pub fn get_entry_point(&self, entry_point: &str) -> Option<EntryPoint>
+ {
+ let entry_point = self.inner.find_entry_point_by_name(entry_point)?;
+
+ Some(EntryPoint { inner: entry_point })
+ }
+}
+
+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<Item = VariableLayout<'a>>
+ {
+ self.inner
+ .parameters()
+ .map(|param| VariableLayout { inner: param })
+ }
+
+ pub fn var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ Some(VariableLayout { inner: self.inner.var_layout()? })
+ }
+}
+
+#[derive(Clone)]
+pub struct Program
+{
+ inner: SlangComponentType,
+}
+
+impl Program
+{
+ pub fn link(&self) -> Result<Program, Error>
+ {
+ let linked_program = self.inner.link()?;
+
+ Ok(Program { inner: linked_program })
+ }
+
+ pub fn get_entry_point_code(&self, entry_point_index: u32) -> Result<Blob, Error>
+ {
+ let blob = self.inner.entry_point_code(entry_point_index.into(), 0)?;
+
+ Ok(Blob { inner: blob })
+ }
+
+ pub fn reflection(&self, target: u32) -> Result<ProgramReflection<'_>, 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::<Self>())
+ .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<EntryPointReflection<'a>>
+ {
+ Some(EntryPointReflection {
+ inner: self.inner.entry_point_by_index(index)?,
+ })
+ }
+
+ pub fn get_entry_point_by_name(&self, name: &str)
+ -> Option<EntryPointReflection<'a>>
+ {
+ Some(EntryPointReflection {
+ inner: self.inner.find_entry_point_by_name(name)?,
+ })
+ }
+
+ pub fn entry_points(
+ &self,
+ ) -> impl ExactSizeIterator<Item = EntryPointReflection<'a>> + use<'a>
+ {
+ self.inner
+ .entry_points()
+ .map(|entry_point| EntryPointReflection { inner: entry_point })
+ }
+
+ pub fn global_params_type_layout(&self) -> Option<TypeLayout<'a>>
+ {
+ Some(TypeLayout {
+ inner: self.inner.global_params_type_layout()?,
+ })
+ }
+
+ pub fn global_params_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ Some(VariableLayout {
+ inner: self.inner.global_params_var_layout()?,
+ })
+ }
+
+ pub fn get_type(&self, name: &str) -> Option<TypeReflection<'a>>
+ {
+ Some(TypeReflection {
+ inner: self.inner.find_type_by_name(name)?,
+ })
+ }
+
+ pub fn get_type_layout(&self, ty: &TypeReflection<'a>) -> Option<TypeLayout<'a>>
+ {
+ 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<usize>
+ {
+ 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<TypeReflection<'a>>
+ {
+ self.inner.ty().map(|ty| TypeReflection { inner: ty })
+ }
+
+ pub fn type_layout(&self) -> Option<TypeLayout<'a>>
+ {
+ 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<ScalarType>
+ {
+ Some(ScalarType::from_slang_scalar_type(
+ self.inner.scalar_type()?,
+ ))
+ }
+
+ 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;
+ }
+
+ 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<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;
+ }
+
+ Some(field_binding_range_offset.cast_unsigned())
+ }
+
+ pub fn ty(&self) -> Option<TypeReflection<'a>>
+ {
+ self.inner.ty().map(|ty| TypeReflection { inner: ty })
+ }
+
+ pub fn fields(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>>
+ {
+ 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<TypeLayout<'a>>
+ {
+ self.inner
+ .element_type_layout()
+ .map(|type_layout| TypeLayout { inner: type_layout })
+ }
+
+ pub fn element_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ self.inner
+ .element_var_layout()
+ .map(|var_layout| VariableLayout { inner: var_layout })
+ }
+
+ pub fn container_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ self.inner
+ .container_var_layout()
+ .map(|var_layout| VariableLayout { inner: var_layout })
+ }
+
+ pub fn uniform_size(&self) -> Option<usize>
+ {
+ // tracing::debug!(
+ // "uniform_size: {:?} categories: {:?}",
+ // self.inner.name(),
+ // self.inner.categories().collect::<Vec<_>>(),
+ // );
+
+ 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<AssetId, Module>,
+ programs: HashMap<AssetId, (Program, ProgramMetadata)>,
+}
+
+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<Item = Module>,
+ entry_points: impl IntoIterator<Item = EntryPoint>,
+ ) -> Result<Program, Error>
+ {
+ 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::<Vec<_>>();
+
+ 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<VertexDescription>,
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct VertexDescription
+{
+ pub fields: Box<[VertexFieldDescription]>,
+}
+
+impl VertexDescription
+{
+ pub fn new(
+ vs_entrypoint: &EntryPointReflection<'_>,
+ ) -> Result<Self, VertexDescriptionError>
+ {
+ 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::<Result<Vec<_>, _>>()?;
+
+ Ok(Self { fields: fields.into_boxed_slice() })
+ }
+}
+
+#[derive(Debug)]
+pub struct VertexFieldDescription
+{
+ pub name: Box<str>,
+ 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::<ChildOf>()
+ .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>)
+{
+ assets.set_importer::<_, _>(["slang"], import_slang_asset);
+}
+
+#[tracing::instrument(skip_all)]
+fn load_modules(mut context: Single<Context>, assets: Single<Assets>)
+{
+ for AssetEvent::Stored(asset_id, asset_label) in assets.events().last_tick_events() {
+ let asset_handle = AssetHandle::<ModuleSource>::from_id(*asset_id);
+
+ if !assets.is_loaded_and_has_type(&asset_handle) {
+ continue;
+ }
+
+ let Some(module_source) = assets.get(&asset_handle) else {
+ unreachable!();
+ };
+
+ tracing::debug!(asset_label=?asset_label, "Loading shader module");
+
+ let module = match load_module(&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::<Result<Vec<_>, 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<Module, Error>
+{
+ let module = session.load_module_from_source_string(
+ &module_source.name,
+ &module_source.file_path.to_string_lossy(),
+ &module_source.source,
+ )?;
+
+ Ok(Module { inner: module })
+}
+
+#[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<f32>),
+ FMat4x4(Matrix<f32, 4, 4>),
+ Texture(RenderingObjectId),
+}
+
+impl From<u32> for BindingValue
+{
+ fn from(value: u32) -> Self
+ {
+ BindingValue::Uint(value)
+ }
+}
+
+impl From<i32> for BindingValue
+{
+ fn from(value: i32) -> Self
+ {
+ BindingValue::Int(value)
+ }
+}
+
+impl From<f32> for BindingValue
+{
+ fn from(value: f32) -> Self
+ {
+ BindingValue::Float(value)
+ }
+}
+
+impl From<Vec3<f32>> for BindingValue
+{
+ fn from(vec: Vec3<f32>) -> Self
+ {
+ BindingValue::FVec3(vec)
+ }
+}
+
+impl From<Matrix<f32, 4, 4>> for BindingValue
+{
+ fn from(matrix: Matrix<f32, 4, 4>) -> 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<AssetLabel> = LazyLock::new(|| AssetLabel {
+ path: Path::new("").into(),
+ name: Some("default_shader".into()),
+});
+
+pub fn initialize(mut assets: Single<Assets>)
+{
+ 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<RenderableEntity<'_>, (Without<NoDraw>,)>,
+ camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
+ window_query: Query<(&Window, &SurfaceSpec), (With<RenderingTargetWindow>,)>,
+ point_light_query: Query<(&PointLight, &WorldPosition)>,
+ directional_light_query: Query<(&DirectionalLight,)>,
+ assets: Single<Assets>,
+ shader_context: Single<ShaderContext>,
+ global_light: Single<GlobalLight>,
+ 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::<ShaderModuleSource>(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<f32, 4, 4>
+{
+ 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<f32>) -> Matrix<f32, 4, 4>
+{
+ 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<u32>,
+) -> Matrix<f32, 4, 4>
+{
+ 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>,
+);