use std::borrow::Cow; use std::fmt::Display; use std::hint::cold_path; use circular_buffer::FixedCircularBuffer; use crate::color::Color; use crate::data_types::matrix::Matrix; use crate::data_types::vector::Vec3; use crate::rendering::object::Id as RenderingObjectId; use crate::rendering::shader::{ ResourceShape, ScalarType, TypeKind, TypeLayout, VariableLayout, }; /// Shader cursor #[derive(Clone)] pub struct Cursor<'a> { type_layout: TypeLayout<'a>, binding_location: BindingLocation, location_path: LocationPath, } 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, location_path: LocationPath::default(), } } pub fn field(&self, name: impl Into>) -> Self { let name = name.into(); let Some(field_var_layout) = self.type_layout.get_field_by_name(name.as_ref()) else { panic!("Field '{name}' does not exist"); }; let field_type_kind = field_var_layout.ty().unwrap().kind(); let (new_var_layout, binding_index_offset) = match field_type_kind { TypeKind::ConstantBuffer => { let elem_var_layout = 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", ); ( elem_var_layout, field_var_layout.binding_index() + elem_var_layout.binding_index(), ) } TypeKind::Array | TypeKind::Matrix | TypeKind::Scalar | TypeKind::Vector | TypeKind::Struct | TypeKind::Resource => (field_var_layout, field_var_layout.binding_index()), type_kind => unimplemented!("Type kind {type_kind:?} is not yet supported"), }; let mut location_path = self.location_path.clone(); location_path.push(Location::Field(name)); Self { type_layout: new_var_layout.type_layout().unwrap(), binding_location: BindingLocation { binding_index: self.binding_location.binding_index + binding_index_offset, binding_size: if field_type_kind == TypeKind::ConstantBuffer { new_var_layout .type_layout() .unwrap() .uniform_size() .unwrap() } else { self.binding_location.binding_size }, byte_offset: self.binding_location.byte_offset + new_var_layout.offset(), }, location_path, } } 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.location_path.push(Location::Element(index)); self } pub fn binding(self, value: BindingValue) -> Result { value.validate_for_shader_type(self.type_layout, self.location_path)?; Ok(Binding { location: self.binding_location, value, }) } } /// Shader cursor location. #[derive(Debug, Clone, Default)] pub struct LocationPath { locations: FixedCircularBuffer, is_truncated: bool, } impl LocationPath { fn push(&mut self, location: Location) { if self.locations.push_back(location).is_some() { self.is_truncated = true; } } } impl Display for LocationPath { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_truncated { write!(formatter, "(...)")?; } for location in &self.locations { match location { Location::Field(field) => { write!(formatter, ".{field}")?; } Location::Element(element) => { write!(formatter, "[{element}]")?; } } } Ok(()) } } /// Shader cursor location. #[derive(Debug, Clone)] pub enum Location { Field(Cow<'static, str>), Element(usize), } impl Display for Location { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Field(field) => as Display>::fmt(field, formatter), Self::Element(element) => ::fmt(element, formatter), } } } #[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), Color(Color), FMat4x4(Matrix), Texture(RenderingObjectId, BindingTextureKind), } impl BindingValue { fn validate_for_shader_type( &self, ty: TypeLayout<'_>, location_path: LocationPath, ) -> Result<(), BindingError> { let ty_kind = ty.kind(); let scalar_ty = ty.scalar_type(); let element_scalar_ty = ty .element_type_layout() .and_then(|elem_ty| elem_ty.scalar_type()); let element_cnt = ty.element_cnt(); let is_valid = match self { Self::Uint(_) => { ty_kind == TypeKind::Scalar && scalar_ty == Some(ScalarType::Uint32) } Self::Int(_) => { ty_kind == TypeKind::Scalar && scalar_ty == Some(ScalarType::Int32) } Self::Float(_) => { ty_kind == TypeKind::Scalar && scalar_ty == Some(ScalarType::Float32) } Self::FVec3(_) => { ty_kind == TypeKind::Vector && element_scalar_ty == Some(ScalarType::Float32) && element_cnt == Some(3) } Self::Color(Color::Rgb(_)) => { ty_kind == TypeKind::Vector && element_scalar_ty == Some(ScalarType::Float32) && element_cnt == Some(3) } Self::Color(Color::Rgba(_)) => { ty_kind == TypeKind::Vector && element_scalar_ty == Some(ScalarType::Float32) && element_cnt == Some(4) } Self::Color(Color::Luma(_)) => { ty_kind == TypeKind::Scalar && scalar_ty == Some(ScalarType::Float32) } Self::Color(Color::LumaA(_)) => { ty_kind == TypeKind::Vector && element_scalar_ty == Some(ScalarType::Float32) && element_cnt == Some(2) } Self::FMat4x4(_) => { ty_kind == TypeKind::Matrix && element_scalar_ty == Some(ScalarType::Float32) && ty.row_cnt() == Some(4) && ty.column_cnt() == Some(4) } Self::Texture(_, BindingTextureKind::Texture2D) => { ty_kind == TypeKind::Resource && ty.resource_shape().is_some_and(|res_shape| { (res_shape & ResourceShape::BASE) .contains(ResourceShape::TEXTURE_2D) }) } Self::Texture(_, BindingTextureKind::Cube) => { ty_kind == TypeKind::Resource && ty.resource_shape().is_some_and(|res_shape| { (res_shape & ResourceShape::BASE) .contains(ResourceShape::TEXTURE_CUBE) }) } }; if !is_valid { cold_path(); return Err(BindingError::IncorrectValueType { value: self.clone(), location_path, }); } Ok(()) } } 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(color: Color) -> Self { BindingValue::Color(color) } } impl From> for BindingValue { fn from(matrix: Matrix) -> Self { BindingValue::FMat4x4(matrix) } } #[derive(Debug, Clone)] #[non_exhaustive] pub struct Binding { pub location: BindingLocation, pub value: BindingValue, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum BindingTextureKind { Texture2D, Cube, } #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum BindingError { #[error("Value {value:?} has incorrect type for value at {location_path} in shader")] IncorrectValueType { value: BindingValue, location_path: LocationPath, }, }