diff options
Diffstat (limited to 'engine/src/shader')
| -rw-r--r-- | engine/src/shader/cursor.rs | 160 | ||||
| -rw-r--r-- | engine/src/shader/default.rs | 363 |
2 files changed, 523 insertions, 0 deletions
diff --git a/engine/src/shader/cursor.rs b/engine/src/shader/cursor.rs new file mode 100644 index 0000000..b5ba4e0 --- /dev/null +++ b/engine/src/shader/cursor.rs @@ -0,0 +1,160 @@ +use crate::asset::Handle as AssetHandle; +use crate::image::Image; +use crate::matrix::Matrix; +use crate::shader::{TypeKind, TypeLayout, VariableLayout}; +use crate::vector::Vec3; + +/// 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(AssetHandle<Image>), +} + +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/shader/default.rs b/engine/src/shader/default.rs new file mode 100644 index 0000000..28bbdc9 --- /dev/null +++ b/engine/src/shader/default.rs @@ -0,0 +1,363 @@ +use std::path::Path; +use std::sync::LazyLock; + +use ecs::Query; +use ecs::actions::Actions; +use ecs::query::term::Without; +use ecs::sole::Single; + +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::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::renderer::{DEFAULT_TEXTURE_ASSET_LABEL, PendingShaderBindings}; +use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor}; +use crate::shader::{ + Context as ShaderContext, + ModuleSource as ShaderModuleSource, + Shader, +}; +use crate::transform::{Scale, Transform, WorldPosition}; +use crate::vector::Vec3; +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 enqueue_set_shader_bindings( + renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, + camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, + window_query: Query<(&Window,)>, + 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::warn!("No current camera"); + return; + }; + + let Some((window,)) = window_query.iter().next() else { + // tracing::warn!("No window"); + 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 Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { + continue; + }; + + let Some(model_spec) = assets.get(&model.spec_asset) else { + 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(), + }; + + 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| !assets.is_loaded_and_has_type(&texture.asset_handle)) + { + 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(), + ), + ( + model_3d_shader_cursor.field("projection"), + create_projection_matrix( + &camera, + &camera_world_pos.position, + window.inner_size(), + ) + .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(), + ), + ]); + + 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.asset_handle.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) + .expect("Not possible") + }), + ), + )); + + 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.asset_handle.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) + .expect("Not possible") + }), + ), + )); + + 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.asset_handle.clone()) + .unwrap_or_else(|| { + assets + .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) + .expect("Not possible") + }), + ), + )); + + 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, + camera_world_pos: &Vec3<f32>, + window_size: &Dimens<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(camera_world_pos, ProjectionClipVolume::NegOneToOne), + } +} + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a MaterialFlags>, + Option<&'a WorldPosition>, + Option<&'a Scale>, + Option<&'a Shader>, + Option<&'a mut PendingShaderBindings>, +); |
