summaryrefslogtreecommitdiff
path: root/engine/src/shader
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/shader')
-rw-r--r--engine/src/shader/cursor.rs160
-rw-r--r--engine/src/shader/default.rs363
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>,
+);