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 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"), }; let mut location_path = self.location_path.clone(); location_path.push(Location::Field(name)); 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(), }, 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, }) } pub fn into_binding_location(self) -> BindingLocation { self.binding_location } } /// 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), } impl BindingValue { fn validate_for_shader_type( &self, ty: TypeLayout<'_>, location_path: LocationPath, ) -> Result<(), BindingError> { match ( self, ty.kind(), ty.scalar_type(), ty.element_type_layout() .map(|elem_ty| elem_ty.scalar_type()), ty.element_cnt(), ty.resource_shape(), ) { (Self::Uint(_), TypeKind::Scalar, Some(ScalarType::Uint32), _, _, _) | (Self::Int(_), TypeKind::Scalar, Some(ScalarType::Int32), _, _, _) | (Self::Float(_), TypeKind::Scalar, Some(ScalarType::Float32), _, _, _) | ( Self::FVec3(_), TypeKind::Vector, _, Some(Some(ScalarType::Float32)), Some(3), _, ) | ( Self::Color(Color::Rgb(_)), TypeKind::Vector, _, Some(Some(ScalarType::Float32)), Some(3), _, ) | ( Self::Color(Color::Rgba(_)), TypeKind::Vector, _, Some(Some(ScalarType::Float32)), Some(4), _, ) | ( Self::Color(Color::Luma(_)), TypeKind::Vector, _, Some(Some(ScalarType::Float32)), Some(1), _, ) | ( Self::Color(Color::LumaA(_)), TypeKind::Vector, _, Some(Some(ScalarType::Float32)), Some(2), _, ) => Ok(()), ( Self::FMat4x4(_), TypeKind::Matrix, _, Some(Some(ScalarType::Float32)), _, _, ) if ty.row_cnt() == Some(4) && ty.column_cnt() == Some(4) => Ok(()), (Self::Texture(_), TypeKind::Resource, _, _, _, Some(res_shape)) if (res_shape & ResourceShape::BASE) .contains(ResourceShape::TEXTURE_2D) => { Ok(()) } _ => { cold_path(); Err(BindingError::IncorrectValueType { value: self.clone(), location_path, }) } } } } 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, 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, }, }