diff options
Diffstat (limited to 'engine')
| -rw-r--r-- | engine/src/asset.rs | 1116 | ||||
| -rw-r--r-- | engine/src/material.rs | 2 | ||||
| -rw-r--r-- | engine/src/model.rs | 2 | ||||
| -rw-r--r-- | engine/src/rendering.rs | 16 | ||||
| -rw-r--r-- | engine/src/rendering/backend/opengl.rs | 100 | ||||
| -rw-r--r-- | engine/src/rendering/main_pipeline.rs (renamed from engine/src/rendering/main_render_pass.rs) | 14 | ||||
| -rw-r--r-- | engine/src/rendering/shader.rs | 1017 | ||||
| -rw-r--r-- | engine/src/rendering/shader/cursor.rs | 2 | ||||
| -rw-r--r-- | engine/src/rendering/shader/reflection.rs | 713 | ||||
| -rw-r--r-- | engine/src/ui/dear_imgui.rs | 13 | ||||
| -rw-r--r-- | engine/src/ui/view/world.rs | 182 | ||||
| -rw-r--r-- | engine/src/windowing.rs | 11 |
12 files changed, 1952 insertions, 1236 deletions
diff --git a/engine/src/asset.rs b/engine/src/asset.rs index e1f699a..e8753bc 100644 --- a/engine/src/asset.rs +++ b/engine/src/asset.rs @@ -5,6 +5,7 @@ use std::convert::Infallible; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::marker::PhantomData; +use std::num::NonZero; use std::panic::{catch_unwind, RefUnwindSafe, UnwindSafe}; use std::path::{Path, PathBuf}; use std::sync::mpsc::{ @@ -51,6 +52,53 @@ impl<'a> Dependency<'a> } } +/// Asset status. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Status +{ + /// The asset is loaded + Ready, + + /// The asset or one of it's dependencies are loading + Pending, + + /// The asset has failed in some way + Failed(FailureReason), + + /// The asset does not exist + NotFound, +} + +impl Status +{ + #[must_use] + pub fn is_ready(self) -> bool + { + matches!(self, Self::Ready) + } + + #[must_use] + pub fn is_pending(self) -> bool + { + matches!(self, Self::Pending) + } + + #[must_use] + pub fn is_failed(self) -> bool + { + matches!(self, Self::Failed(_)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum FailureReason +{ + Import, + Dependency, +} + /// Asset label. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Label<'a> @@ -110,11 +158,12 @@ impl Display for Label<'_> } } -#[derive(Debug, Sole)] +#[derive(Sole)] pub struct Assets { store: store::Store, - metadata_lut: RefCell<hashbrown::HashMap<LabelOwned, Metadata>>, + id_lut: RefCell<hashbrown::HashMap<LabelOwned, IdValid>>, + metadata_lut: RefCell<hashbrown::HashMap<IdValid, AssetMetadata>>, importers: hashbrown::HashMap<AssetTypeId, WrappedImporterFn>, import_work_thread_pool: ThreadPool, import_work_msg_receiver: MpscReceiver<ImportWorkMessage>, @@ -135,6 +184,7 @@ impl Assets Self { store: store::Store::with_capacity(capacity), + id_lut: RefCell::new(hashbrown::HashMap::with_capacity(capacity)), metadata_lut: RefCell::new(hashbrown::HashMap::with_capacity(capacity)), importers: hashbrown::HashMap::new(), import_work_thread_pool: ThreadPoolBuilder::new() @@ -150,7 +200,8 @@ impl Assets pub fn set_importer<AssetT, AssetSettings, Err>( &mut self, - func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err> + + RefUnwindSafe, ) where AssetT: Asset, AssetSettings: UnwindSafe + RefUnwindSafe + 'static, @@ -162,7 +213,7 @@ impl Assets #[tracing::instrument( skip_all, - fields(asset_label, asset_type = %type_name::<AssetT>()) + fields(asset_type = %type_name::<AssetT>()) )] pub fn get<AssetT>(&self, handle: &Handle<AssetT>) -> Option<&AssetT> where @@ -170,17 +221,20 @@ impl Assets { let id = handle.id.into_valid()?; + if !self.get_is_asset_ready(id) { + return None; + } + let stored_asset = self.store.get(id.index)?; - tracing::Span::current() - .record("asset_label", tracing::field::display(&stored_asset.label)); + let Some(asset) = stored_asset.data.downcast_ref::<AssetT>() else { + let metadata_lut = self.metadata_lut.borrow(); - let AssetState::Ready { data: asset_data } = &stored_asset.state else { - return None; - }; + // A stored asset will always have metadata + let asset_metadata = metadata_lut.get(&id).unwrap_or_else(|| unreachable!()); + + tracing::error!(label = %asset_metadata.label, "Wrong asset type"); - let Some(asset) = asset_data.downcast_ref::<AssetT>() else { - tracing::error!("Wrong asset type"); return None; }; @@ -189,7 +243,7 @@ impl Assets #[tracing::instrument( skip_all, - fields(asset_label, asset_type = %type_name::<AssetT>()) + fields(asset_type = %type_name::<AssetT>()) )] pub fn get_mut<AssetT>(&mut self, handle: &Handle<AssetT>) -> Option<&mut AssetT> where @@ -197,24 +251,27 @@ impl Assets { let id = handle.id.into_valid()?; + if !self.get_is_asset_ready(id) { + return None; + } + let stored_asset = self.store.get_mut(id.index)?; - tracing::Span::current() - .record("asset_label", tracing::field::display(&stored_asset.label)); + let Some(asset) = stored_asset.data.downcast_mut::<AssetT>() else { + let metadata_lut = self.metadata_lut.get_mut(); - let AssetState::Ready { data: asset_data } = &mut stored_asset.state else { - return None; - }; + // A stored asset will always have metadata + let asset_metadata = metadata_lut.get(&id).unwrap_or_else(|| unreachable!()); + + tracing::error!(label = %asset_metadata.label, "Wrong asset type"); - let Some(asset) = asset_data.downcast_mut::<AssetT>() else { - tracing::error!("Wrong asset type"); return None; }; Some(asset) } - #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::<AssetT>()))] + #[tracing::instrument(skip_all, fields(asset_type = %type_name::<AssetT>()))] pub fn get_handle_to_loaded<'label, AssetT>( &self, label: impl Into<Label<'label>>, @@ -224,20 +281,16 @@ impl Assets { let label = label.into(); - tracing::Span::current().record("label", tracing::field::display(&label)); - - let (stored_asset, id) = self.get_asset_by_label(&label)?; - - let Some(StoredAsset { - state: AssetState::Ready { data: asset_data }, - .. - }) = stored_asset - else { + let Some((Some(stored_asset), id)) = self.get_asset_by_label(&label) else { return None; }; - if asset_data.downcast_ref::<AssetT>().is_none() { - tracing::error!("Wrong asset type"); + if !self.get_is_asset_ready(id) { + return None; + } + + if stored_asset.data.downcast_ref::<AssetT>().is_none() { + tracing::error!(%label, "Wrong asset type"); return None; } @@ -252,15 +305,15 @@ impl Assets return false; }; - let Some(StoredAsset { - state: AssetState::Ready { data: asset_data }, - .. - }) = self.store.get(id.index) - else { + let Some(stored_asset) = self.store.get(id.index) else { return false; }; - asset_data.downcast_ref::<AssetT>().is_some() + if !self.get_is_asset_ready(id) { + return false; + } + + stored_asset.data.downcast_ref::<AssetT>().is_some() } pub fn get_label<AssetT>(&self, handle: &Handle<AssetT>) -> Option<Label<'static>> @@ -274,9 +327,176 @@ impl Assets { let id = id.into_valid()?; - let stored_asset = self.store.get(id.index)?; + let metadata_lut = self.metadata_lut.borrow(); + + Some(metadata_lut.get(&id)?.label.clone().into_label()) + } - Some(stored_asset.label.clone().into_label()) + pub fn get_status<AssetT: Asset>(&self, handle: &Handle<AssetT>) -> Status + { + self.get_status_by_id(handle.id()) + } + + pub fn get_status_by_id(&self, asset_id: Id) -> Status + { + let Some(asset_id) = asset_id.into_valid() else { + return Status::NotFound; + }; + + let metadata_lut = self.metadata_lut.borrow(); + + let Some(asset_metadata) = metadata_lut.get(&asset_id) else { + return Status::NotFound; + }; + + let mut is_asset_loading = false; + + match &asset_metadata.state { + AssetState::DependenciesPending { .. } => { + return Status::Pending; + } + AssetState::Failed { reason, .. } => { + return Status::Failed(*reason); + } + AssetState::Ready => { + return Status::Ready; + } + AssetState::Loading => { + // TODO: It might be possible to just return Status::Pending here + // since asset states are propagated to their related assets + is_asset_loading = true; + } + AssetState::NotStored => {} + } + + if matches!(asset_metadata.relation, AssetRelation::Parent { .. }) { + return if is_asset_loading { + Status::Pending + } else { + Status::NotFound + }; + } + + let parent_asset_state = (|| { + let parent_asset_id = match &asset_metadata.relation { + AssetRelation::Child { parent } => *parent, + AssetRelation::Parent { .. } => { + // label.name is Some so the asset can not be a parent + unreachable!() + } + }; + + let parent_asset_metadata = metadata_lut.get(&parent_asset_id)?; + + match &parent_asset_metadata.state { + AssetState::Failed { reason, .. } => Some(Status::Failed(*reason)), + AssetState::Ready + | AssetState::NotStored + | AssetState::Loading + | AssetState::DependenciesPending { .. } => None, + } + })(); + + match parent_asset_state { + Some(Status::Failed(FailureReason::Dependency)) => { + // It would make no sense for a asset's status to be Status::Failed if the + // parent asset has a missing dependency + + if is_asset_loading { + Status::Pending + } else { + Status::NotFound + } + } + Some(parent_asset_state) => parent_asset_state, + None if is_asset_loading => Status::Pending, + None => Status::NotFound, + } + } + + pub fn get_status_by_label(&self, label: &Label<'_>) -> Status + { + let id_lut = self.id_lut.borrow(); + let metadata_lut = self.metadata_lut.borrow(); + + let asset_id = id_lut.get(label); + + let asset_metadata = asset_id.and_then(|asset_id| metadata_lut.get(asset_id)); + + let mut is_asset_loading = false; + + if let Some(asset_metadata) = asset_metadata { + match &asset_metadata.state { + AssetState::DependenciesPending { .. } => { + return Status::Pending; + } + AssetState::Failed { reason, .. } => { + return Status::Failed(*reason); + } + AssetState::Ready => { + return Status::Ready; + } + AssetState::Loading => { + // TODO: It might be possible to just return Status::Pending here + // since asset states are propagated to their related assets + is_asset_loading = true; + } + AssetState::NotStored => {} + } + } + + if label.name.is_none() { + return if is_asset_loading { + Status::Pending + } else { + Status::NotFound + }; + } + + let parent_asset_state = (|| { + let parent_asset_id = asset_metadata + .map(|metadata| match &metadata.relation { + AssetRelation::Child { parent } => *parent, + AssetRelation::Parent { .. } => { + // label.name is Some so the asset can not be a parent + unreachable!() + } + }) + .or_else(|| { + id_lut + .get(&Label { + path: label.path.as_ref().into(), + name: None, + }) + .copied() + })?; + + let parent_asset_metadata = metadata_lut.get(&parent_asset_id)?; + + match &parent_asset_metadata.state { + AssetState::Failed { reason, .. } => Some(Status::Failed(*reason)), + AssetState::Ready + | AssetState::NotStored + | AssetState::Loading + | AssetState::DependenciesPending { .. } => None, + } + })(); + + match parent_asset_state { + Some(Status::Failed(FailureReason::Dependency)) => { + // It would make no sense for a asset's status to be Status::Failed if the + // parent asset has a missing dependency + + if is_asset_loading { + Status::Pending + } else { + Status::NotFound + } + } + Some(parent_asset_state) => parent_asset_state, + None if is_asset_loading => Status::Pending, + None => Status::NotFound, + } } #[tracing::instrument(skip_all, fields(label, asset_type = %type_name::<AssetT>()))] @@ -288,7 +508,8 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let id = self.load_asset::<Infallible>(AssetTypeId::of::<AssetT>(), label, None); + let (id, _res) = + self.load_asset::<Infallible>(AssetTypeId::of::<AssetT>(), label, None); Handle::from_id(Id::from_valid(id)) } @@ -307,7 +528,7 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); - let id = + let (id, _res) = self.load_asset(AssetTypeId::of::<AssetT>(), label, Some(asset_settings)); Handle::from_id(Id::from_valid(id)) @@ -363,128 +584,163 @@ impl Assets tracing::Span::current().record("label", tracing::field::display(&label)); let id = match self.get_asset_by_label(&label) { - Some(( - Some(StoredAsset { - state: - AssetState::Ready { .. } | AssetState::DependenciesPending { .. }, - .. - }), - id, - )) => { - tracing::error!("Asset already exists"); - - return Handle::from_id(Id::from_valid(id)); - } - Some((Some(StoredAsset { state: AssetState::Pending, .. }) | None, id)) => id, + Some((Some(StoredAsset { .. }) | None, id)) => id, None => IdValid::new(&self.store, &label), }; - let mut missing_dependencies = 0; + self.register_asset(id, &label); - let mut dependencies = Vec::new(); + if self.get_status_by_label(&label) == Status::Ready { + tracing::error!("Asset already exists"); - asset.visit_dependencies(&mut |dependency| { - dependencies.push(dependency); - }); + return Handle::from_id(Id::from_valid(id)); + } + + let dependencies = { + let mut dependencies = Vec::new(); + + asset.visit_dependencies(&mut |dependency| { + dependencies.push(dependency); + }); + + dependencies.sort_by(|dependency_a, dependency_b| { + dependency_a.label.cmp(&dependency_b.label) + }); + + dependencies.dedup_by(|dependency_a, dependency_b| { + dependency_a.label == dependency_b.label + }); + + dependencies + }; + + let mut missing_dependencies = 0usize; + let mut has_failed_dependency = false; for dependency in dependencies { - match self.get_asset_by_label_mut(&dependency.label) { - Some((Some(StoredAsset { state: AssetState::Ready { .. }, .. }), _)) => {} - Some(( - Some(StoredAsset { - state: - AssetState::Pending | AssetState::DependenciesPending { .. }, - waiting_dependants, - .. - }), - _, - )) => { - tracing::trace!( - dependency = %dependency.label, - "Asset has missing dependency" - ); - - missing_dependencies += 1; - - waiting_dependants.push(id); - } - Some((None, _)) | None => { - tracing::trace!( - dependency = %dependency.label, - "Asset has missing dependency" - ); - - missing_dependencies += 1; - - let dependency_id = self.load_asset::<Infallible>( - dependency.asset_ty_id, - dependency.label.clone(), - None, - ); - - self.store - .get_or_insert_with(dependency_id.index, || StoredAsset { - state: AssetState::Pending, - label: dependency.label.to_owned(), - waiting_dependants: Vec::new(), - }) - .waiting_dependants - .push(id); - } + let dependency_status = self.get_status_by_label(&dependency.label); + + if let Status::Ready = dependency_status { + continue; + } + + missing_dependencies += 1; + + if let Status::Failed(_) = dependency_status { + has_failed_dependency = true; + } + + let (dependency_id, res) = self.load_asset::<Infallible>( + dependency.asset_ty_id, + dependency.label.clone(), + None, + ); + + if res.is_err() { + has_failed_dependency = true; + } + + let Some(dependency_metadata) = + self.metadata_lut.get_mut().get_mut(&dependency_id) + else { + // Self::load_asset adds metadata if it doesn't already exist + unreachable!(); + }; + + if !dependency_metadata.waiting_dependants.contains(&id) { + dependency_metadata.waiting_dependants.push(id); } } tracing::debug!("Storing asset"); - let stored_asset = self.store.get_or_insert_with(id.index, || StoredAsset { - state: AssetState::Pending, - label: label.to_owned(), - waiting_dependants: Vec::new(), - }); + debug_assert!(self.store.get(id.index).is_none()); - let label = label.to_owned(); + self.store + .insert(id.index, StoredAsset { data: Box::new(asset) }); - if missing_dependencies != 0 { - stored_asset.state = AssetState::DependenciesPending { - data: Box::new(asset), - remaining_dependencies: missing_dependencies, - }; + let Some(metadata) = self.metadata_lut.get_mut().get_mut(&id) else { + unreachable!(); // Metadata is added by Self::register_asset, called above + }; + + if let Some(missing_deps) = NonZero::<usize>::new(missing_dependencies) { + metadata.state = + AssetState::DependenciesPending { remaining_dependencies: missing_deps }; + + if has_failed_dependency { + self.set_failed_asset_states(&label, FailureReason::Dependency); + } } else { - stored_asset.state = AssetState::Ready { data: Box::new(asset) }; + metadata.state = AssetState::Ready; - self.curr_tick_events.push(Event::Stored( + self.curr_tick_events.push(Event::Loaded( Id::from_valid(id), - label.clone().into_label(), + label.to_owned().into_label(), )); - let waiting_dependants = std::mem::take(&mut stored_asset.waiting_dependants); + let waiting_dependants = std::mem::take(&mut metadata.waiting_dependants); - self.recurse_update_dependants(waiting_dependants); + self.recurse_make_dependants_state_ready(waiting_dependants); } - let metadata_lut = self.metadata_lut.get_mut(); + Handle::from_id(Id::from_valid(id)) + } + + fn register_asset(&self, id: IdValid, label: &Label<'_>) + { + let mut id_lut = self.id_lut.borrow_mut(); + let mut metadata_lut = self.metadata_lut.borrow_mut(); let parent_id = label.name.is_some().then(|| { let parent_label = Label { - path: label.path.as_path().into(), + path: label.path.as_ref().into(), name: None, }; - metadata_lut + let parent_id = *id_lut .entry(parent_label.to_owned()) - .or_insert_with(|| Metadata { - id: IdValid::new(&self.store, &parent_label), - parent_id: None, - }) - .id + .or_insert_with(|| IdValid::new(&self.store, &parent_label)); + + let parent_metadata = + metadata_lut + .entry(parent_id) + .or_insert_with(|| AssetMetadata { + label: parent_label.to_owned(), + waiting_dependants: Vec::new(), + relation: AssetRelation::Parent { + children: Vec::with_capacity(2), + }, + state: AssetState::NotStored, + }); + + let AssetRelation::Parent { children: parent_children } = + &mut parent_metadata.relation + else { + // The label of the parent asset has no name so it can not be a child + unreachable!(); + }; + + if !parent_children.contains(&id) { + parent_children.push(id); + } + + parent_id }); - metadata_lut.insert(label.clone(), Metadata { id, parent_id }); + id_lut.insert(label.to_owned(), id); - Handle::from_id(Id::from_valid(id)) + metadata_lut.entry(id).or_insert_with(|| AssetMetadata { + label: label.to_owned(), + waiting_dependants: Vec::new(), + relation: match parent_id { + Some(parent_id) => AssetRelation::Child { parent: parent_id }, + None => AssetRelation::Parent { children: Vec::new() }, + }, + state: AssetState::NotStored, + }); } - fn recurse_update_dependants(&mut self, initial_dependants: Vec<IdValid>) + fn recurse_make_dependants_state_ready(&mut self, initial_dependants: Vec<IdValid>) { let mut all_dependants = initial_dependants; @@ -496,39 +752,223 @@ impl Assets } for dependant_id in dependants { - let Some(dependant_asset) = self.store.get_mut(dependant_id.index) else { + let Some(dependant_metadata) = + self.metadata_lut.get_mut().get_mut(&dependant_id) + else { continue; }; - { - let AssetState::DependenciesPending { - remaining_dependencies, .. - } = &mut dependant_asset.state - else { - continue; - }; - - *remaining_dependencies = remaining_dependencies.saturating_sub(1); - - if *remaining_dependencies > 0 { + let prev_remaining_deps = match &dependant_metadata.state { + AssetState::NotStored + | AssetState::Loading + | AssetState::Ready + | AssetState::Failed { reason: FailureReason::Import, .. } => { continue; } - } + AssetState::DependenciesPending { remaining_dependencies } => { + remaining_dependencies.get() + } + AssetState::Failed { + reason: FailureReason::Dependency, + remaining_dependencies, + } => *remaining_dependencies, + }; - let AssetState::DependenciesPending { data: dependant_data, .. } = - std::mem::replace(&mut dependant_asset.state, AssetState::Pending) - else { + let Some(new_remaining_deps) = prev_remaining_deps.checked_sub(1) else { unreachable!(); }; - dependant_asset.state = AssetState::Ready { data: dependant_data }; + match &mut dependant_metadata.state { + AssetState::DependenciesPending { remaining_dependencies } => { + debug_assert!(self.store.contains_index(dependant_id.index)); + + if let Some(new_remaining_deps) = + NonZero::<usize>::new(new_remaining_deps) + { + *remaining_dependencies = new_remaining_deps; + } else { + dependant_metadata.state = AssetState::Ready; + } + } + AssetState::Failed { + reason: FailureReason::Dependency, + remaining_dependencies, + } => { + debug_assert!(self.store.contains_index(dependant_id.index)); + + *remaining_dependencies = new_remaining_deps; - self.curr_tick_events.push(Event::Stored( + if *remaining_dependencies == 0 { + dependant_metadata.state = AssetState::Ready; + } + } + _ => unreachable!(), + } + + if new_remaining_deps != 0 { + continue; + } + + self.curr_tick_events.push(Event::Loaded( Id::from_valid(dependant_id), - dependant_asset.label.clone().into_label(), + dependant_metadata.label.clone().into_label(), )); - all_dependants.append(&mut dependant_asset.waiting_dependants); + all_dependants.append(&mut dependant_metadata.waiting_dependants); + } + } + } + + fn set_asset_states(&self, path: &Path, func: impl Fn(AssetState) -> AssetState) + { + let label = Label { path: path.into(), name: None }; + + let id = match self.get_asset_by_label(&label) { + Some((Some(_) | None, id)) => id, + None => IdValid::new(&self.store, &label), + }; + + self.register_asset(id, &label); + + let mut metadata_lut = self.metadata_lut.borrow_mut(); + + let Some(metadata) = metadata_lut.get_mut(&id) else { + unreachable!(); // Metadata is added by Self::register_asset, called above + }; + + metadata.state = func(std::mem::replace( + &mut metadata.state, + AssetState::NotStored, + )); + + let AssetRelation::Parent { children } = &metadata.relation else { + // label.name is always None so the asset can not be a child + unreachable!(); + }; + + for child in children.clone() { + let Some(child_asset_metadata) = metadata_lut.get_mut(&child) else { + continue; + }; + + child_asset_metadata.state = func(std::mem::replace( + &mut child_asset_metadata.state, + AssetState::NotStored, + )); + } + } + + fn set_failed_asset_states(&self, label: &Label<'_>, reason: FailureReason) + { + let id = match self.get_asset_by_label(label) { + Some((Some(_) | None, id)) => id, + None => IdValid::new(&self.store, label), + }; + + self.register_asset(id, label); + + let mut metadata_lut = self.metadata_lut.borrow_mut(); + + let Some(metadata) = metadata_lut.get_mut(&id) else { + unreachable!(); // Metadata is added by Self::register_asset, called above + }; + + let remaining_deps = match &metadata.state { + AssetState::Failed { remaining_dependencies, .. } => *remaining_dependencies, + AssetState::DependenciesPending { remaining_dependencies } => { + remaining_dependencies.get() + } + AssetState::Ready | AssetState::NotStored | AssetState::Loading => 0, + }; + + metadata.state = AssetState::Failed { + reason, + remaining_dependencies: remaining_deps, + }; + + let waiting_dependants = metadata.waiting_dependants.clone(); + + Self::recurse_make_dependants_state_failed(&mut metadata_lut, waiting_dependants); + + if label.name.is_some() { + return; + } + + let Some(metadata) = metadata_lut.get_mut(&id) else { + unreachable!(); // Metadata is added by Self::register_asset, called above + }; + + let AssetRelation::Parent { children } = &metadata.relation else { + // label.name is None so the asset can not be a child + unreachable!(); + }; + + for child in children.clone() { + let Some(child_asset_metadata) = metadata_lut.get_mut(&child) else { + continue; + }; + + let child_remaining_deps = match &child_asset_metadata.state { + AssetState::Failed { .. } | AssetState::Loading => { + continue; + } + AssetState::DependenciesPending { remaining_dependencies } => { + remaining_dependencies.get() + } + AssetState::Ready | AssetState::NotStored => 0, + }; + + child_asset_metadata.state = AssetState::Failed { + reason, + remaining_dependencies: child_remaining_deps, + }; + + let child_waiting_dependants = + child_asset_metadata.waiting_dependants.clone(); + + Self::recurse_make_dependants_state_failed( + &mut metadata_lut, + child_waiting_dependants, + ); + } + } + + fn recurse_make_dependants_state_failed( + metadata_lut: &mut hashbrown::HashMap<IdValid, AssetMetadata>, + initial_dependants: Vec<IdValid>, + ) + { + let mut all_dependants = initial_dependants; + + loop { + let dependants = std::mem::take(&mut all_dependants); + + if dependants.is_empty() { + break; + } + + for dependant_id in dependants { + let Some(dependant_metadata) = metadata_lut.get_mut(&dependant_id) else { + continue; + }; + + let remaining_deps = match &dependant_metadata.state { + AssetState::Failed { .. } | AssetState::Loading => { + continue; + } + AssetState::DependenciesPending { remaining_dependencies } => { + remaining_dependencies.get() + } + AssetState::NotStored | AssetState::Ready => 0, + }; + + dependant_metadata.state = AssetState::Failed { + reason: FailureReason::Dependency, + remaining_dependencies: remaining_deps, + }; + + all_dependants + .extend(dependant_metadata.waiting_dependants.iter().copied()); } } } @@ -538,97 +978,139 @@ impl Assets asset_ty_id: AssetTypeId, label: Label<'_>, asset_settings: Option<AssetSettings>, - ) -> IdValid + ) -> (IdValid, Result<(), LoadAssetError>) where AssetSettings: UnwindSafe + RefUnwindSafe + Send + Sync + Debug + 'static, { - let id = match self.get_asset_by_label(&label) { - Some((Some(_), id)) => { - return id; - } - Some((None, id)) => id, + let status = self.get_status_by_label(&label); + + if let Status::Ready + | Status::Pending + | Status::Failed(FailureReason::Dependency) = status + { + let Some(id) = self.id_lut.borrow().get(&label).copied() else { + unreachable!(); + }; + + return (id, Ok(())); + } + + let id = match self.id_lut.borrow().get(&label).copied() { + Some(id) => id, None => IdValid::new(&self.store, &label), }; - let mut metadata_lut = self.metadata_lut.borrow_mut(); + let parent_exists = label.name.is_some() + && self + .id_lut + .borrow() + .get(&Label { + path: label.path.as_ref().into(), + name: None, + }) + .copied() + .is_some_and(|parent_id| { + let metadata_lut = self.metadata_lut.borrow(); - let mut parent_exists = false; + let Some(parent_metadata) = metadata_lut.get(&parent_id) else { + unreachable!(); + }; - let parent_id = label.name.is_some().then(|| { - let parent_label = Label { - path: label.path.as_ref().into(), - name: None, - }; + match &parent_metadata.state { + AssetState::Loading + | AssetState::Ready + | AssetState::DependenciesPending { .. } + | AssetState::Failed { + reason: FailureReason::Dependency, .. + } => true, + AssetState::NotStored + | AssetState::Failed { reason: FailureReason::Import, .. } => { + false + } + } + }); - parent_exists = metadata_lut.contains_key(&parent_label); + self.register_asset(id, &label); - metadata_lut - .entry(parent_label.to_owned()) - .or_insert_with(|| Metadata { - id: IdValid::new(&self.store, &parent_label), - parent_id: None, - }) - .id + if parent_exists { + return (id, Ok(())); + } + + let Some(importer) = self.importers.get(&asset_ty_id).cloned() else { + tracing::error!("No importer exists for asset"); + + self.set_failed_asset_states(&label, FailureReason::Import); + + return (id, Err(LoadAssetError::NoImporterFound)); + }; + + let mut metadata_lut = self.metadata_lut.borrow_mut(); + + let parent_id = metadata_lut.get(&id).and_then(|asset_metadata| { + match &asset_metadata.relation { + AssetRelation::Child { parent } => Some(*parent), + AssetRelation::Parent { .. } => None, + } }); - metadata_lut.insert(label.to_owned(), Metadata { id, parent_id }); + if let Some(parent_id) = parent_id { + let Some(AssetMetadata { + relation: AssetRelation::Parent { children: parent_children }, + state: parent_state, + .. + }) = metadata_lut.get_mut(&parent_id) + else { + unreachable!(); + }; + + *parent_state = AssetState::Loading; - if parent_exists { - return id; + for child_id in parent_children.clone() { + let Some(child_metadata) = metadata_lut.get_mut(&child_id) else { + continue; + }; + + child_metadata.state = AssetState::Loading; + } } - self.add_import_work::<AssetSettings>(asset_ty_id, &label, asset_settings); + tracing::debug!("Adding import work!"); - id + self.add_import_work::<AssetSettings>( + label.path.clone().into_owned(), + asset_settings, + importer, + ); + + (id, Ok(())) } fn add_import_work<AssetSettings>( &self, - asset_ty_id: AssetTypeId, - label: &Label<'_>, + asset_path: PathBuf, asset_settings: Option<AssetSettings>, + importer: WrappedImporterFn, ) where AssetSettings: UnwindSafe + RefUnwindSafe + Any + Send + Sync, { - let Some(importer) = self.importers.get(&asset_ty_id).cloned() else { - tracing::error!("No importer exists for asset"); - return; - }; - let import_work_msg_sender = self.import_work_msg_sender.clone(); - let asset_path = label.path.to_path_buf(); - self.import_work_thread_pool.spawn(move || { - let result = catch_unwind(|| { - if let Err(err) = importer.call( - import_work_msg_sender, - asset_path.as_path(), - asset_settings - .as_ref() - .map(|asset_settings| asset_settings as &(dyn Any + Send + Sync)), - ) { - tracing::error!( - "Failed to import asset {}: {:#}", - asset_path.display(), - crate::Error::new(err) - ); - } - }); - - if let Err(panic_msg) = result { - let panic_msg = if let Some(msg) = panic_msg.downcast_ref::<&str>() { - msg - } else if let Some(msg) = panic_msg.downcast_ref::<String>() { - msg.as_str() - } else { - "(unknown panic payload type)" - }; - + if let Err(err) = importer.call( + &import_work_msg_sender, + asset_path.as_path(), + asset_settings + .as_ref() + .map(|asset_settings| asset_settings as &(dyn Any + Send + Sync)), + ) { tracing::error!( - "Failed to import asset {}: Thread panicked: {panic_msg}", + "Failed to import asset {}: {:#}", asset_path.display(), + crate::Error::new(err) ); + + let _ = + import_work_msg_sender.send(ImportWorkMessage::Failed(asset_path)); } }); } @@ -638,23 +1120,22 @@ impl Assets label: &Label<'_>, ) -> Option<(Option<&StoredAsset>, IdValid)> { - let metadata_lut = self.metadata_lut.borrow(); + let id_lut = self.id_lut.borrow(); - let id = metadata_lut.get(label)?.id; + let id = *id_lut.get(label)?; Some((self.store.get(id.index), id)) } - fn get_asset_by_label_mut( - &mut self, - label: &Label<'_>, - ) -> Option<(Option<&mut StoredAsset>, IdValid)> + fn get_is_asset_ready(&self, id: IdValid) -> bool { - let metadata_lut = self.metadata_lut.get_mut(); + let metadata_lut = self.metadata_lut.borrow(); - let id = metadata_lut.get(label)?.id; + let Some(asset_metadata) = metadata_lut.get(&id) else { + return false; + }; - Some((self.store.get_mut(id.index), id)) + matches!(asset_metadata.state, AssetState::Ready) } } @@ -666,11 +1147,10 @@ impl Default for Assets } } -pub struct Submitter<'path> +pub struct Submitter<'a> { - pending_messages: Vec<ImportWorkMessage>, - import_work_msg_sender: MpscSender<ImportWorkMessage>, - asset_path: &'path Path, + storer_funcs: Vec<StorerFunc>, + _pd: PhantomData<&'a ()>, } impl Submitter<'_> @@ -679,41 +1159,29 @@ impl Submitter<'_> where AssetT: Asset, { - let label = LabelOwned { - path: self.asset_path.into(), - name: None, - }; - - self.pending_messages.push(ImportWorkMessage::Store { - do_store: Box::new(move |assets, label| { - assets.store_with_label::<AssetT>(label.as_label(), asset); - }), - label, - }); + self.storer_funcs.push(Box::new(move |assets, path| { + assets.store_with_label::<AssetT>( + Label { path: path.into(), name: None }, + asset, + ); + })); } pub fn submit_store_named<AssetT>(&mut self, name: impl AsRef<str>, asset: AssetT) where AssetT: Asset, { - let label = LabelOwned { - path: self.asset_path.into(), - name: Some(name.as_ref().into()), - }; - - self.pending_messages.push(ImportWorkMessage::Store { - do_store: Box::new(move |assets, label| { - assets.store_with_label::<AssetT>(label.as_label(), asset); - }), - label, - }); - } - - fn perform_submissions(&mut self) - { - for msg in self.pending_messages.drain(..) { - let _ = self.import_work_msg_sender.send(msg); - } + let name = name.as_ref().to_owned(); + + self.storer_funcs.push(Box::new(move |assets, path| { + assets.store_with_label::<AssetT>( + Label { + path: path.into(), + name: Some(name.into()), + }, + asset, + ); + })); } } @@ -826,8 +1294,8 @@ impl Events #[derive(Debug)] pub enum Event { - /// Asset stored. - Stored(Id, Label<'static>), + /// Asset loading is completed. + Loaded(Id, Label<'static>), } #[derive(Debug, thiserror::Error)] @@ -837,14 +1305,17 @@ enum ImporterError IncorrectAssetSettingsType(PathBuf), #[error(transparent)] - Other(Box<dyn std::error::Error + Send + Sync>), + ErrorResult(Box<dyn std::error::Error + Send + Sync>), + + #[error("Thread panicked: {0}")] + ThreadPanicked(Cow<'static, str>), } #[derive(Debug, Clone)] struct WrappedImporterFn { wrapper_func: fn( - MpscSender<ImportWorkMessage>, + &MpscSender<ImportWorkMessage>, &Path, Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError>, @@ -854,15 +1325,15 @@ impl WrappedImporterFn { fn new<InnerFunc, AssetSettings, Err>(inner_func_param: InnerFunc) -> Self where - InnerFunc: - Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + InnerFunc: Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err> + + RefUnwindSafe, AssetSettings: UnwindSafe + RefUnwindSafe + 'static, Err: std::error::Error + Send + Sync + 'static, { assert_eq!(size_of::<InnerFunc>(), 0); let wrapper_func = - |import_work_msg_sender: MpscSender<ImportWorkMessage>, + |import_work_msg_sender: &MpscSender<ImportWorkMessage>, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>| { let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() }; @@ -879,16 +1350,34 @@ impl WrappedImporterFn }) .transpose()?; - let mut submitter = Submitter { - pending_messages: Vec::with_capacity(2), - import_work_msg_sender, - asset_path, - }; + let pending_message = catch_unwind(|| { + let mut submitter = Submitter { + storer_funcs: Vec::with_capacity(2), + _pd: PhantomData, + }; - inner_func(&mut submitter, asset_path, asset_settings) - .map_err(|err| ImporterError::Other(Box::new(err)))?; + inner_func(&mut submitter, asset_path, asset_settings) + .map_err(|err| ImporterError::ErrorResult(Box::new(err)))?; - submitter.perform_submissions(); + Result::<_, ImporterError>::Ok(ImportWorkMessage::Completed { + storer_funcs: submitter.storer_funcs, + path: asset_path.to_path_buf(), + }) + }) + .map_err(|panic_msg| { + let panic_msg = + if let Some(msg) = panic_msg.downcast_ref::<&'static str>() { + Cow::from(*msg) + } else if let Ok(msg) = panic_msg.downcast::<String>() { + Cow::from(*msg) + } else { + Cow::from("(unknown panic payload type)") + }; + + ImporterError::ThreadPanicked(panic_msg) + })??; + + let _ = import_work_msg_sender.send(pending_message); Ok(()) }; @@ -900,7 +1389,7 @@ impl WrappedImporterFn fn call( &self, - import_work_msg_sender: MpscSender<ImportWorkMessage>, + import_work_msg_sender: &MpscSender<ImportWorkMessage>, asset_path: &Path, asset_settings: Option<&(dyn Any + Send + Sync)>, ) -> Result<(), ImporterError> @@ -909,7 +1398,7 @@ impl WrappedImporterFn } } -#[derive(Debug, Default)] +#[derive(Default)] pub(crate) struct Extension { pub assets: Assets, @@ -944,50 +1433,85 @@ fn add_received_assets(mut assets: Single<Assets>, mut events: Single<Events>) while let Ok(import_work_msg) = assets.import_work_msg_receiver.try_recv() { match import_work_msg { - ImportWorkMessage::Store { do_store, label } => { - do_store(assets, label); + ImportWorkMessage::Completed { storer_funcs, path } => { + // It is not guaranteed that all registered assets with this path will be + // stored + assets.set_asset_states(&path, |curr_state| { + if matches!(curr_state, AssetState::Loading) { + AssetState::NotStored + } else { + curr_state + } + }); + + for storer in storer_funcs { + storer(assets, &path); + } + } + ImportWorkMessage::Failed(asset_path) => { + assets.set_failed_asset_states( + &Label { path: asset_path.into(), name: None }, + FailureReason::Import, + ); } } } } +type StorerFunc = Box<dyn FnOnce(&mut Assets, &Path) + Send>; + enum ImportWorkMessage { - Store + Completed { - do_store: Box<dyn FnOnce(&mut Assets, LabelOwned) + Send>, - label: LabelOwned, + storer_funcs: Vec<StorerFunc>, + path: PathBuf, }, + Failed(PathBuf), +} + +#[derive(Debug)] +struct AssetMetadata +{ + label: LabelOwned, + waiting_dependants: Vec<IdValid>, + relation: AssetRelation, + state: AssetState, } #[derive(Debug)] -struct Metadata +enum AssetRelation { - id: IdValid, - parent_id: Option<IdValid>, + Parent + { + children: Vec<IdValid> + }, + Child + { + parent: IdValid + }, } #[derive(Debug)] enum AssetState { - Ready + Ready, + Loading, + DependenciesPending { - data: Box<dyn Any + Send + Sync>, + remaining_dependencies: NonZero<usize>, }, - Pending, - DependenciesPending + Failed { - data: Box<dyn Any + Send + Sync>, + reason: FailureReason, remaining_dependencies: usize, }, + NotStored, } -#[derive(Debug)] struct StoredAsset { - state: AssetState, - label: LabelOwned, - waiting_dependants: Vec<IdValid>, + data: Box<dyn Any + Send + Sync>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -1080,6 +1604,12 @@ fn calc_import_work_thread_cnt() -> usize .unwrap_or(BACKUP_IMPORT_WORK_THREAD_CNT) } +#[derive(Debug)] +enum LoadAssetError +{ + NoImporterFound, +} + mod store { use std::num::NonZero; @@ -1087,7 +1617,6 @@ mod store use super::StoredAsset; - #[derive(Debug)] pub struct Store { store: Vec<Option<StoredAsset>>, @@ -1104,6 +1633,11 @@ mod store } } + pub fn contains_index(&self, index: ReservedIndex) -> bool + { + self.store.get(index.0.get()).is_some_and(Option::is_some) + } + pub fn get(&self, index: ReservedIndex) -> Option<&StoredAsset> { self.store.get(index.0.get())?.as_ref() @@ -1114,11 +1648,7 @@ mod store self.store.get_mut(index.0.get())?.as_mut() } - pub fn get_or_insert_with( - &mut self, - index: ReservedIndex, - func: impl FnOnce() -> StoredAsset, - ) -> &mut StoredAsset + pub fn insert(&mut self, index: ReservedIndex, asset: StoredAsset) { let index = index.0.get(); @@ -1131,7 +1661,7 @@ mod store .extend(std::iter::repeat_with(|| None).take(missing)); } - self.store[index].get_or_insert_with(func) + self.store[index] = Some(asset); } pub fn reserve_index(&self) -> ReservedIndex diff --git a/engine/src/material.rs b/engine/src/material.rs index ded30a2..853670d 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -255,7 +255,7 @@ pub(crate) fn set_stored_materials_texture_handles( let assets = assets.get_mut().unwrap(); let asset_events = asset_events.get().unwrap(); - for AssetEvent::Stored(asset_id, ..) in asset_events.iter_last_tick() { + for AssetEvent::Loaded(asset_id, ..) in asset_events.iter_last_tick() { let asset_handle = AssetHandle::<Material>::from_id(*asset_id); if !assets.is_loaded_and_has_type(&asset_handle) { diff --git a/engine/src/model.rs b/engine/src/model.rs index 62cfe21..daca6f0 100644 --- a/engine/src/model.rs +++ b/engine/src/model.rs @@ -88,7 +88,7 @@ pub(crate) fn set_stored_mat_descs_handles( let assets = assets.get_mut().unwrap(); let asset_events = asset_events.get().unwrap(); - for AssetEvent::Stored(asset_id, ..) in asset_events.iter_last_tick() { + for AssetEvent::Loaded(asset_id, ..) in asset_events.iter_last_tick() { let asset_handle = AssetHandle::<MaterialDescription>::from_id(*asset_id); if !assets.is_loaded_and_has_type(&asset_handle) { diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs index 747fdfa..1f1be64 100644 --- a/engine/src/rendering.rs +++ b/engine/src/rendering.rs @@ -24,7 +24,10 @@ use crate::mesh::Mesh; use crate::rendering::blending::Config as BlendingConfig; use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; use crate::rendering::shader::cursor::Binding as ShaderBinding; -use crate::rendering::shader::Program as ShaderProgram; +use crate::rendering::shader::{ + CompileTargetId as ShaderCompileTargetId, + Program as ShaderProgram, +}; use crate::texture::{CubeMapFace, Properties as TextureProperties}; use crate::vector::Vec2; use crate::windowing::dpi::PhysicalSize; @@ -32,7 +35,7 @@ use crate::windowing::window::Window; pub mod backend; pub mod blending; -pub mod main_render_pass; +pub mod main_pipeline; pub mod object; pub mod shader; @@ -80,7 +83,7 @@ impl crate::ecs::extension::Extension for Extension collector.add_system( *PRE_RENDER_PHASE, - main_render_pass::add_main_render_pass + main_pipeline::add_passes .into_system() .initialize(Default::default()), ); @@ -199,6 +202,13 @@ impl SurfaceId } } +/// Properties of the current rendering backend. +#[derive(Debug, Sole)] +pub struct BackendProperties +{ + pub shader_target_id: ShaderCompileTargetId, +} + #[derive(Debug, Default, Sole)] pub struct RenderPasses { diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs index 0a04d6e..bdaa555 100644 --- a/engine/src/rendering/backend/opengl.rs +++ b/engine/src/rendering/backend/opengl.rs @@ -6,6 +6,7 @@ use std::convert::Infallible; use std::hint::cold_path; use std::num::NonZero; +use ecs::phase::INIT as INIT_PHASE; use ecs::query::term::With; use glutin::config::Config as GlutinConfig; use glutin::display::GetGlDisplay; @@ -116,14 +117,17 @@ use crate::rendering::shader::cursor::{ BindingLocation as ShaderBindingLocation, BindingValue as ShaderBindingValue, }; +use crate::rendering::shader::reflection::Stage as ShaderStage; use crate::rendering::shader::{ + EntrypointFlags as ShaderEntrypointFlags, Error as ShaderError, Program as ShaderProgram, - ProgramMetadata as ShaderProgramMetadata, - Stage as ShaderStage, + VertexDescCreationError as ShaderVertexDescCreationError, + VertexDescription as ShaderVertexDescription, }; use crate::rendering::{ AssetOrValue, + BackendProperties, BufferClearMask, Command, CommandQueue, @@ -436,7 +440,7 @@ enum BackendResource Shader { program: GlShaderProgram, - program_metadata: ShaderProgramMetadata, + info: ShaderInfo, bindings: IntMap<ShaderBindingIndex, BackendShaderBinding>, }, Texture @@ -489,6 +493,14 @@ enum TextureKind CubeMap, } +#[derive(Debug, Clone)] +struct ShaderInfo +{ + /// If the shader program has a entry point in the vertex stage, this field will + /// contain a description of the vertex type passed to the entry point. + vertex_desc: Option<ShaderVertexDescription>, +} + #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} @@ -497,6 +509,8 @@ impl crate::ecs::extension::Extension for Extension { fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { + collector.add_system(*INIT_PHASE, init); + collector.add_system(*RENDER_PHASE, handle_commands); collector.add_system(*POST_RENDER_PHASE, prepare_windows); @@ -506,6 +520,24 @@ impl crate::ecs::extension::Extension for Extension } } +fn init( + mut shader_context: Single<crate::rendering::shader::Context>, + mut actions: Actions, +) -> Result<(), crate::Error> +{ + let shader_context = shader_context.get_mut()?; + + let shader_target_ids = shader_context + .init_session(&[&crate::rendering::shader::CompileTarget::default() + .format(crate::rendering::shader::CompileTargetFormat::Glsl)])?; + + actions.create_sole(BackendProperties { + shader_target_id: shader_target_ids[0], + }); + + Ok(()) +} + fn prepare_windows( window_query: Query<( Option<&Window>, @@ -756,6 +788,7 @@ fn handle_commands( mut object_store: Single<ObjectStore>, mut command_queue: Single<CommandQueue>, assets: Single<Assets>, + backend_properties: Single<BackendProperties>, ) -> Result<(), crate::Error> { let Ok(graphics_ctx) = graphics_ctx.get_mut() else { @@ -766,6 +799,10 @@ fn handle_commands( let command_queue = command_queue.get_mut()?; let assets = assets.get()?; + let Ok(backend_properties) = backend_properties.get() else { + return Ok(()); + }; + let GraphicsContext { ref gl_context, ref mut surfaces, @@ -776,11 +813,8 @@ fn handle_commands( return Ok(()); }; - let mut activated_gl_shader_program: Option<( - ObjectId, - GlShaderProgram, - ShaderProgramMetadata, - )> = None; + let mut activated_gl_shader_program: Option<(ObjectId, GlShaderProgram, ShaderInfo)> = + None; for command in command_queue.drain() { let tracing_span = tracing::info_span!( @@ -895,8 +929,21 @@ fn handle_commands( program: create_shader_program( gl_context, &shader_program, + backend_properties, )?, - program_metadata: shader_program.metadata().clone(), + info: ShaderInfo { + vertex_desc: if shader_program + .metadata() + .link_entrypoints + .contains(ShaderEntrypointFlags::VERTEX) + { + Some(shader_program.create_vertex_desc( + backend_properties.shader_target_id, + )?) + } else { + None + }, + }, bindings: IntMap::with_capacity(4), }) }, @@ -914,7 +961,7 @@ fn handle_commands( continue; }; - let BackendResource::Shader { program, program_metadata, bindings } = + let BackendResource::Shader { program, info: shader_info, bindings } = resource else { unreachable!(); @@ -927,7 +974,7 @@ fn handle_commands( } activated_gl_shader_program = - Some((shader_object_id, program.clone(), program_metadata.clone())); + Some((shader_object_id, program.clone(), shader_info.clone())); } Command::SetShaderBinding( shader_object_id, @@ -1162,7 +1209,7 @@ fn handle_commands( unreachable!(); }; - let Some((shader_program_obj_id, _, shader_program_metadata)) = + let Some((shader_program_obj_id, _, shader_info)) = &activated_gl_shader_program else { tracing::error!("No shader program is activated"); @@ -1174,8 +1221,7 @@ fn handle_commands( vertex_attrs_updated_for_shader != *shader_program_obj_id }, ) { - let Some(shader_program_vertex_desc) = - &shader_program_metadata.vertex_desc + let Some(shader_program_vertex_desc) = &shader_info.vertex_desc else { tracing::error!( "Activated shader program does not have a vertex description" @@ -1799,9 +1845,12 @@ fn cube_map_texture_face_to_gl(face: CubeMapTextureFace) -> GlCubeMapTextureFace fn create_shader_program( current_context: &MaybeCurrentContextWithFns, shader_program: &ShaderProgram, + backend_properties: &BackendProperties, ) -> Result<GlShaderProgram, CreateShaderError> { - let shader_program_reflection = shader_program.reflection(0).expect("Not possible"); + let shader_program_reflection = shader_program + .reflection(backend_properties.shader_target_id) + .expect("Not possible"); let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection .entry_points() @@ -1812,9 +1861,12 @@ fn create_shader_program( })?; let vertex_shader_entry_point_code = shader_program - .get_entry_point_code(vs_entry_point_index.try_into().expect( - "Vertex shader entry point index does not fit in 32-bit unsigned int", - )) + .get_entry_point_code( + vs_entry_point_index.try_into().expect( + "Vertex shader entry point index does not fit in 32-bit unsigned int", + ), + backend_properties.shader_target_id, + ) .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { err, stage: ShaderStage::Vertex, @@ -1832,9 +1884,12 @@ fn create_shader_program( })?; let fragment_shader_entry_point_code = shader_program - .get_entry_point_code(fs_entry_point_index.try_into().expect( - "Fragment shader entry point index does not fit in 32-bit unsigned int", - )) + .get_entry_point_code( + fs_entry_point_index.try_into().expect( + "Fragment shader entry point index does not fit in 32-bit unsigned int", + ), + backend_properties.shader_target_id, + ) .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { err, stage: ShaderStage::Fragment, @@ -1888,6 +1943,9 @@ enum CreateShaderError #[error("No entrypoint was found for shader stage {0:?}")] NoShaderStageEntrypointFound(ShaderStage), + #[error("Failed to create shader vertex description")] + VertexDescCreationFailed(#[from] ShaderVertexDescCreationError), + #[error(transparent)] ShaderError(#[from] GlShaderError), } diff --git a/engine/src/rendering/main_render_pass.rs b/engine/src/rendering/main_pipeline.rs index 99d2909..754b507 100644 --- a/engine/src/rendering/main_render_pass.rs +++ b/engine/src/rendering/main_pipeline.rs @@ -42,6 +42,7 @@ use crate::rendering::shader::{ }; use crate::rendering::{ AssetOrValue, + BackendProperties, BufferClearMask, Command, DepthFunction, @@ -104,7 +105,7 @@ pub struct MiscObjectIds } #[tracing::instrument(skip_all)] -pub fn add_main_render_pass( +pub fn add_passes( renderable_query: Query<RenderableEntity<'_>>, camera_query: Query<( &Camera, @@ -130,6 +131,7 @@ pub fn add_main_render_pass( Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>, )>, shader_context: Single<ShaderContext>, + backend_properties: Single<BackendProperties>, mut assets: Single<Assets>, mut render_passes: Single<RenderPasses>, mut object_store: Single<ObjectStore>, @@ -142,6 +144,10 @@ pub fn add_main_render_pass( let render_passes = render_passes.get_mut()?; let object_store = object_store.get_mut()?; + let Ok(backend_properties) = backend_properties.get() else { + return Ok(()); + }; + let Some((scene_ent_id, (scene_env_lighting, scene_skybox, scene_skybox_state))) = scene_query.iter_with_euids().next() else { @@ -186,7 +192,7 @@ pub fn add_main_render_pass( let main_3d_shader_cursor = ShaderCursor::new( shaders .main_3d_shader_program - .reflection(0) + .reflection(backend_properties.shader_target_id) .context("Unable to get reflection for main 3D shader")? .global_params_var_layout() .ok_or_else(|| { @@ -327,6 +333,7 @@ pub fn add_main_render_pass( &window, shaders.sky_box_shader_obj_id, shaders.sky_box_shader_program, + &backend_properties, )?; } } @@ -820,6 +827,7 @@ fn add_sky_box_commands( window: &Window, sky_box_shader_obj_id: ObjectId, sky_box_shader_program: &ShaderProgram, + backend_properties: &BackendProperties, ) -> Result<(), crate::Error> { render_pass @@ -828,7 +836,7 @@ fn add_sky_box_commands( let sky_box_shader_cursor = ShaderCursor::new( sky_box_shader_program - .reflection(0) + .reflection(backend_properties.shader_target_id) .unwrap() .global_params_var_layout() .unwrap(), diff --git a/engine/src/rendering/shader.rs b/engine/src/rendering/shader.rs index a4edbc0..926302a 100644 --- a/engine/src/rendering/shader.rs +++ b/engine/src/rendering/shader.rs @@ -1,6 +1,5 @@ use std::any::type_name; use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Display, Write}; use std::path::Path; use std::str::Utf8Error; @@ -15,10 +14,7 @@ use shader_slang::{ EntryPoint as SlangEntryPoint, GlobalSession as SlangGlobalSession, Module as SlangModule, - ParameterCategory as SlangParameterCategory, - ScalarType as SlangScalarType, Session as SlangSession, - TypeKind as SlangTypeKind, }; use crate::asset::{ @@ -39,6 +35,7 @@ use crate::ecs::{declare_entity, pair, Sole}; use crate::mesh::vertex_buffer::VertexLabel; pub mod cursor; +pub mod reflection; pub const STD_VERT_IN_SEM_NAME_POSITION: &str = "STD_POSITION"; pub const STD_VERT_IN_SEM_NAME_NORMAL: &str = "STD_NORMAL"; @@ -113,68 +110,14 @@ pub struct EntryPoint impl EntryPoint { #[must_use] - pub fn function(&self) -> FunctionReflection<'_> + pub fn function_reflection(&self) -> self::reflection::Function<'_> { - FunctionReflection { + self::reflection::Function { inner: self.inner.function_reflection(), } } } -pub struct FunctionReflection<'a> -{ - inner: &'a shader_slang::reflection::Function, -} - -impl FunctionReflection<'_> -{ - #[must_use] - pub fn name(&self) -> Option<&str> - { - self.inner.name() - } -} - -pub struct EntryPointReflection<'a> -{ - inner: &'a shader_slang::reflection::EntryPoint, -} - -impl<'a> EntryPointReflection<'a> -{ - #[must_use] - pub fn name(&self) -> Option<&str> - { - self.inner.name() - } - - #[must_use] - pub fn name_override(&self) -> Option<&str> - { - self.inner.name_override() - } - - #[must_use] - pub fn stage(&self) -> Stage - { - Stage::from_slang_stage(self.inner.stage()) - } - - #[must_use] - pub fn parameters(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>> - { - self.inner - .parameters() - .map(|param| VariableLayout { inner: param }) - } - - #[must_use] - pub fn var_layout(&self) -> Option<VariableLayout<'a>> - { - Some(VariableLayout { inner: self.inner.var_layout()? }) - } -} - #[derive(Clone)] pub struct Program { @@ -200,641 +143,61 @@ impl Program &self.metadata } - 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(i64::from(target))?; - - 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> -{ - #[must_use] - pub fn get_entry_point_by_index(&self, index: u32) - -> Option<EntryPointReflection<'a>> - { - Some(EntryPointReflection { - inner: self.inner.entry_point_by_index(index)?, - }) - } - - #[must_use] - pub fn get_entry_point_by_name(&self, name: &str) - -> Option<EntryPointReflection<'a>> - { - Some(EntryPointReflection { - inner: self.inner.find_entry_point_by_name(name)?, - }) - } - - #[must_use] - pub fn entry_points( + pub fn get_entry_point_code( &self, - ) -> impl ExactSizeIterator<Item = EntryPointReflection<'a>> + use<'a> - { - self.inner - .entry_points() - .map(|entry_point| EntryPointReflection { inner: entry_point }) - } - - #[must_use] - pub fn global_params_type_layout(&self) -> Option<TypeLayout<'a>> - { - Some(TypeLayout { - inner: self.inner.global_params_type_layout()?, - }) - } - - #[must_use] - pub fn global_params_var_layout(&self) -> Option<VariableLayout<'a>> - { - Some(VariableLayout { - inner: self.inner.global_params_var_layout()?, - }) - } - - #[must_use] - pub fn get_type(&self, name: &str) -> Option<TypeReflection<'a>> - { - Some(TypeReflection { - inner: self.inner.find_type_by_name(name)?, - }) - } - - #[must_use] - 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> -{ - #[must_use] - pub fn name(&self) -> Option<&'a str> + entry_point_index: u32, + target_id: CompileTargetId, + ) -> Result<Blob, Error> { - self.inner.name() - } - - #[must_use] - pub fn semantic_name(&self) -> Option<&str> - { - self.inner.semantic_name() - } - - #[must_use] - pub fn binding_index(&self) -> u32 - { - self.inner - .offset(shader_slang::ParameterCategory::DescriptorTableSlot) as u32 - - // self.inner.binding_index() - } - - #[must_use] - pub fn varying_input_offset(&self) -> Option<usize> - { - if !self + let blob = self .inner - .categories() - .any(|category| category == SlangParameterCategory::VaryingInput) - { - return None; - } - - Some(self.inner.offset(SlangParameterCategory::VaryingInput)) - } - - #[must_use] - pub fn binding_space(&self) -> u32 - { - self.inner.binding_space() - } - - #[must_use] - pub fn semantic_index(&self) -> usize - { - self.inner.semantic_index() - } - - #[must_use] - pub fn offset(&self) -> usize - { - self.inner.offset(shader_slang::ParameterCategory::Uniform) - } - - #[must_use] - pub fn ty(&self) -> Option<TypeReflection<'a>> - { - self.inner.ty().map(|ty| TypeReflection { inner: ty }) - } - - #[must_use] - 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> -{ - #[must_use] - pub fn kind(&self) -> TypeKind - { - TypeKind::from_slang_type_kind(self.inner.kind()) - } + .entry_point_code(entry_point_index.into(), i64::from(target_id.index))?; - #[must_use] - pub fn scalar_type(&self) -> Option<ScalarType> - { - Some(ScalarType::from_slang_scalar_type( - self.inner.scalar_type()?, - )) - } - - #[must_use] - pub fn resource_shape(&self) -> Option<ResourceShape> - { - Some(ResourceShape::from_bits_retain( - self.inner.resource_shape()? as u32, - )) - } - - #[must_use] - 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 }) - } - - #[must_use] - pub fn parameter_category(&self) -> ParameterCategory - { - ParameterCategory::from_slang_parameter_category(self.inner.parameter_category()) - } - - #[must_use] - pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64 - { - self.inner.binding_range_descriptor_set_index(index) - } - - #[must_use] - 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()) - } - - #[must_use] - pub fn ty(&self) -> Option<TypeReflection<'a>> - { - self.inner.ty().map(|ty| TypeReflection { inner: ty }) - } - - #[must_use] - pub fn fields(&self) -> FieldIter<'a> - { - FieldIter { - type_layout: *self, - cnt: self.field_cnt(), - index: 0, - } - } - - #[must_use] - pub fn field_cnt(&self) -> u32 - { - self.inner.field_count() - } - - #[must_use] - pub fn element_cnt(&self) -> Option<usize> - { - self.inner.element_count() - } - - #[must_use] - pub fn row_cnt(&self) -> Option<u32> - { - self.inner.row_count() - } - - #[must_use] - pub fn column_cnt(&self) -> Option<u32> - { - self.inner.column_count() - } - - #[must_use] - pub fn element_type_layout(&self) -> Option<TypeLayout<'a>> - { - self.inner - .element_type_layout() - .map(|type_layout| TypeLayout { inner: type_layout }) + Ok(Blob { inner: blob }) } - #[must_use] - pub fn element_var_layout(&self) -> Option<VariableLayout<'a>> + pub fn reflection( + &self, + target_id: CompileTargetId, + ) -> Result<self::reflection::Program<'_>, Error> { - self.inner - .element_var_layout() - .map(|var_layout| VariableLayout { inner: var_layout }) - } + let reflection = self.inner.layout(i64::from(target_id.index))?; - #[must_use] - pub fn container_var_layout(&self) -> Option<VariableLayout<'a>> - { - self.inner - .container_var_layout() - .map(|var_layout| VariableLayout { inner: var_layout }) + Ok(self::reflection::Program { inner: reflection }) } - #[must_use] - pub fn uniform_size(&self) -> Option<usize> + pub fn create_vertex_desc( + &self, + target_id: CompileTargetId, + ) -> Result<VertexDescription, VertexDescCreationError> { - // tracing::debug!( - // "uniform_size: {:?} categories: {:?}", - // self.inner.name(), - // self.inner.categories().collect::<Vec<_>>(), - // ); - if !self - .inner - .categories() - .any(|category| category == SlangParameterCategory::Uniform) + .metadata + .link_entrypoints + .contains(EntrypointFlags::VERTEX) { - return None; + return Err(VertexDescCreationError::NoVertexStageEntrypointInProgram); } - // let category = self.inner.categories().next().unwrap(); - - // println!( - // "AARGH size Category: {category:?} Category count: {}", - // self.inner.category_count() - // ); + let program_reflection = self + .reflection(target_id) + .map_err(VertexDescCreationError::GetProgramReflectionFailed)?; - // Some(self.inner.size(category)) - - Some(self.inner.size(SlangParameterCategory::Uniform)) - } - - #[must_use] - pub fn stride(&self) -> usize - { - self.inner.stride(self.inner.categories().next().unwrap()) + VertexDescription::new( + &program_reflection + .get_entry_point_by_name("vertex_main") + .expect("Not possible"), + ) } } -pub struct FieldIter<'a> -{ - type_layout: TypeLayout<'a>, - cnt: u32, - index: u32, -} - -impl<'a> Iterator for FieldIter<'a> -{ - type Item = VariableLayout<'a>; - - fn next(&mut self) -> Option<Self::Item> - { - if self.index == self.cnt { - return None; - } - - let Some(field) = self.type_layout.inner.field_by_index(self.index) else { - unreachable!(); - }; - - self.index += 1; - - Some(VariableLayout { inner: field }) - } - - fn size_hint(&self) -> (usize, Option<usize>) - { - let len = (self.cnt - self.index) as usize; - - (len, Some(len)) - } -} - -impl ExactSizeIterator for FieldIter<'_> -{ - fn len(&self) -> usize - { - (self.cnt - self.index) as usize - } -} - -impl DoubleEndedIterator for FieldIter<'_> -{ - fn next_back(&mut self) -> Option<Self::Item> - { - if self.cnt == 0 || self.index == self.cnt - 1 { - return None; - } - - let Some(field) = self.type_layout.inner.field_by_index(self.cnt - 1) else { - unreachable!(); - }; - - self.cnt -= 1; - - Some(VariableLayout { inner: field }) - } -} - -pub struct TypeReflection<'a> -{ - inner: &'a shader_slang::reflection::Type, -} - -impl TypeReflection<'_> -{ - #[must_use] - 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"), - } - } -} - -bitflags! { -#[derive(Debug, Copy, Clone)] -pub struct ResourceShape: u32 { - const BASE = shader_slang::ResourceShape::SlangResourceBaseShapeMask as u32; - const NONE = shader_slang::ResourceShape::SlangResourceNone as u32; - const TEXTURE_1D = shader_slang::ResourceShape::SlangTexture1d as u32; - const TEXTURE_2D = shader_slang::ResourceShape::SlangTexture2d as u32; - const TEXTURE_3D = shader_slang::ResourceShape::SlangTexture3d as u32; - const TEXTURE_CUBE = shader_slang::ResourceShape::SlangTextureCube as u32; - const TEXTURE_BUFFER = shader_slang::ResourceShape::SlangTextureBuffer as u32; - const STRUCTURED_BUFFER = shader_slang::ResourceShape::SlangStructuredBuffer as u32; - const BYTE_ADDRESS_BUFFER = shader_slang::ResourceShape::SlangByteAddressBuffer as u32; - const UNKNOWN = shader_slang::ResourceShape::SlangResourceUnknown as u32; - const ACCELERATION_STRUCTURE = shader_slang::ResourceShape::SlangAccelerationStructure as u32; - const TEXTURE_SUBPASS = shader_slang::ResourceShape::SlangTextureSubpass as u32; - const EXT_SHAPE = shader_slang::ResourceShape::SlangResourceExtShapeMask as u32; - const TEXTURE_FEEDBACK_FLAG = shader_slang::ResourceShape::SlangTextureFeedbackFlag as u32; - const TEXTURE_SHADOW_FLAG = shader_slang::ResourceShape::SlangTextureShadowFlag as u32; - const TEXTURE_ARRAY_FLAG = shader_slang::ResourceShape::SlangTextureArrayFlag as u32; - const TEXTURE_MULTISAMPLE_FLAG = shader_slang::ResourceShape::SlangTextureMultisampleFlag as u32; - const TEXTURE_COMBINED_FLAG = shader_slang::ResourceShape::SlangTextureCombinedFlag as u32; - const TEXTURE_1D_ARRAY = shader_slang::ResourceShape::SlangTexture1dArray as u32; - const TEXTURE_2D_ARRAY = shader_slang::ResourceShape::SlangTexture2dArray as u32; - const TEXTURE_CUBE_ARRAY = shader_slang::ResourceShape::SlangTextureCubeArray as u32; - const TEXTURE_2D_MULTISAMPLE = shader_slang::ResourceShape::SlangTexture2dMultisample as u32; - const TEXTURE_2D_MULTISAMPLE_ARRAY = shader_slang::ResourceShape::SlangTexture2dMultisampleArray as u32; - const TEXTURE_SUBPASS_MULTISAMPLE = shader_slang::ResourceShape::SlangTextureSubpassMultisample as u32; -} -} - -#[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 +impl Debug for Program { - fn from_slang_parameter_category(parameter_category: SlangParameterCategory) -> Self + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - 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, - } + formatter + .debug_struct(type_name::<Self>()) + .finish_non_exhaustive() } } @@ -851,61 +214,13 @@ impl Blob self.inner.as_slice() } + #[allow(clippy::missing_errors_doc)] 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)] @@ -919,14 +234,63 @@ pub struct Settings #[derive(Sole)] pub struct Context { - _global_session: SlangGlobalSession, - session: SlangSession, - modules: HashMap<AssetId, Module>, - programs: HashMap<AssetId, Program>, + global_session: SlangGlobalSession, + session: Option<SlangSession>, + modules: hashbrown::HashMap<AssetId, Module>, + programs: hashbrown::HashMap<AssetId, Program>, } impl Context { + #[allow(clippy::missing_errors_doc)] + pub fn init_session( + &mut self, + compile_targets: &[&CompileTarget], + ) -> Result<Vec<CompileTargetId>, ContextSessionInitError> + { + if self.session.is_some() { + return Err(ContextSessionInitError::AlreadyInitialized); + } + + if compile_targets.len() > u32::MAX as usize { + return Err(ContextSessionInitError::TooManyCompileTargets); + } + + let session_options = shader_slang::CompilerOptions::default() + .optimization(shader_slang::OptimizationLevel::None) + .matrix_layout_row(true) + .debug_information(SlangDebugInfoLevel::Maximal) + .no_mangle(true); + + let (compile_target_ids, target_descs) = compile_targets + .iter() + .enumerate() + .map(|(index, compile_target)| { + // The length of compile_targets is not greater than u32::MAX + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + ( + CompileTargetId { index: index as u32 }, + shader_slang::TargetDesc::default() + .format(compile_target.format.into_slang()), + ) + }) + .collect::<(Vec<_>, Vec<_>)>(); + + let session_desc = shader_slang::SessionDesc::default() + .targets(&target_descs) + .search_paths([""]) + .options(&session_options); + + let Some(session) = self.global_session.create_session(&session_desc) else { + tracing::error!("Failed to create shader-slang session"); + return Err(ContextSessionInitError::Internal); + }; + + self.session = Some(session); + + Ok(compile_target_ids) + } + #[must_use] pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module> { @@ -946,6 +310,10 @@ impl Context link_entrypoints: EntrypointFlags, ) -> Result<Program, ComposeProgramError> { + let Some(session) = self.session.as_ref() else { + return Err(ComposeProgramError::ContextSessionNotInitialized); + }; + let entry_points = link_entrypoints .iter() .filter_map(|entrypoint_flag| { @@ -971,42 +339,63 @@ impl Context .chain([SlangComponentType::from(module.inner)]) .collect::<Vec<_>>(); - let program = self - .session + let program = session .create_composite_component_type(&components) .map_err(|err| ComposeProgramError::Other(Error(err)))?; - let vertex_desc = if link_entrypoints.contains(EntrypointFlags::VERTEX) { - // TODO: Do not have hard coded target here - let Ok(program_reflection) = program.layout(0) else { - unreachable!(); - }; - - let program_reflection = ProgramReflection { inner: program_reflection }; - - Some(VertexDescription::new( - &program_reflection - .get_entry_point_by_name("vertex_main") - .expect("Not possible"), - )?) - } else { - None - }; - Ok(Program { inner: program, - metadata: ProgramMetadata { vertex_desc }, + metadata: ProgramMetadata { link_entrypoints }, }) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CompileTargetId +{ + index: u32, +} + +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct CompileTarget +{ + pub format: CompileTargetFormat, +} + +impl CompileTarget +{ + #[must_use] + pub fn format(mut self, format: CompileTargetFormat) -> Self + { + self.format = format; + self + } +} + +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub enum CompileTargetFormat +{ + #[default] + Glsl, +} + +impl CompileTargetFormat +{ + fn into_slang(self) -> shader_slang::CompileTarget + { + match self { + Self::Glsl => shader_slang::CompileTarget::Glsl, + } + } +} + #[derive(Debug, Clone)] #[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>, + pub link_entrypoints: EntrypointFlags, } #[derive(Debug, Clone)] @@ -1019,16 +408,12 @@ pub struct VertexDescription impl VertexDescription { #[tracing::instrument(skip_all, fields(vs_entry_point_name = vs_entrypoint.name()))] - pub fn new( - vs_entrypoint: &EntryPointReflection<'_>, - ) -> Result<Self, VertexDescriptionError> + fn new( + vs_entrypoint: &self::reflection::EntryPointReflection<'_>, + ) -> Result<Self, VertexDescCreationError> { - if vs_entrypoint.stage() != Stage::Vertex { - return Err(VertexDescriptionError::EntrypointNotInVertexStage); - } - let inputs = VarInputDfsIter::new(vs_entrypoint).inspect(|var_input| { - if var_input.type_layout.kind() != TypeKind::Struct { + if var_input.type_layout.kind() != self::reflection::TypeKind::Struct { return; } @@ -1043,17 +428,19 @@ impl VertexDescription ); }); - let mut seen_inputs = HashSet::<VertexInputSemName>::new(); + let mut seen_inputs = hashbrown::HashSet::<VertexInputSemName>::new(); Ok(Self { inputs: inputs - .filter(|var_input| var_input.type_layout.kind() != TypeKind::Struct) + .filter(|var_input| { + var_input.type_layout.kind() != self::reflection::TypeKind::Struct + }) .map(|var_input| { let name = var_input.var_layout.name().unwrap_or("<unnamed>"); let semantic_name = var_input.var_layout.semantic_name().ok_or_else(|| { - VertexDescriptionError::VertexInputMissingSemanticName { + VertexDescCreationError::VertexInputMissingSemanticName { name: name.to_owned(), } })?; @@ -1063,19 +450,26 @@ impl VertexDescription if seen_inputs.contains(&semantic_name) { return Err( - VertexDescriptionError::VertexInputHasOccupiedSemanticName { + VertexDescCreationError::VertexInputHasOccupiedSemanticName { name: name.to_owned(), }, ); } - let (TypeKind::Scalar | TypeKind::Vector, Some(scalar_type)) = ( + let ( + self::reflection::TypeKind::Scalar + | self::reflection::TypeKind::Vector, + Some(scalar_type), + ) = ( var_input.type_layout.kind(), var_input.type_layout.scalar_type(), - ) else { - return Err(VertexDescriptionError::UnsupportedVertexInputType { - name: name.to_owned(), - }); + ) + else { + return Err( + VertexDescCreationError::UnsupportedVertexInputType { + name: name.to_owned(), + }, + ); }; seen_inputs.insert(semantic_name.clone()); @@ -1099,8 +493,8 @@ pub struct VertexInputDescription { pub semantic_name: VertexInputSemName, pub index: usize, - pub type_kind: TypeKind, - pub scalar_type: ScalarType, + pub type_kind: self::reflection::TypeKind, + pub scalar_type: self::reflection::ScalarType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1166,10 +560,13 @@ impl Display for VertexInputSemName } #[derive(Debug, thiserror::Error)] -pub enum VertexDescriptionError +pub enum VertexDescCreationError { - #[error("Entrypoint is not in vertex stage")] - EntrypointNotInVertexStage, + #[error("Shader program has no entry point in vertex stage")] + NoVertexStageEntrypointInProgram, + + #[error("Failed to get shader program reflection")] + GetProgramReflectionFailed(#[source] Error), #[error("Type of vertex input '{name}' is not supported")] UnsupportedVertexInputType @@ -1195,6 +592,9 @@ pub enum VertexDescriptionError #[derive(Debug, thiserror::Error)] pub enum ComposeProgramError { + #[error("A shader context session has not been initialized")] + ContextSessionNotInitialized, + #[error("Shader module does not have a '{entrypoint_name}' entry point")] EntrypointNotFoundError { @@ -1202,24 +602,41 @@ pub enum ComposeProgramError }, #[error("Failed to create vertex description")] - VertexDescriptionCreationFailed(#[from] VertexDescriptionError), + VertexDescriptionCreationFailed(#[from] VertexDescCreationError), #[error(transparent)] Other(Error), } #[derive(Debug, thiserror::Error)] +pub enum ContextSessionInitError +{ + #[error("A shader context session has already been initialized")] + AlreadyInitialized, + + #[error("Too many compile targets. Count would overflow a u32")] + TooManyCompileTargets, + + #[error("An internal error occurred")] + Internal, +} + +#[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct Error(#[from] shader_slang::Error); struct VarInputDfsIter<'a> { - stack: Vec<(VariableLayout<'a>, TypeLayout<'a>, usize)>, + stack: Vec<( + self::reflection::VariableLayout<'a>, + self::reflection::TypeLayout<'a>, + usize, + )>, } impl<'a> VarInputDfsIter<'a> { - fn new(entry_point: &EntryPointReflection<'a>) -> Self + fn new(entry_point: &self::reflection::EntryPointReflection<'a>) -> Self { Self { stack: entry_point @@ -1251,7 +668,7 @@ impl<'a> Iterator for VarInputDfsIter<'a> } }; - if type_layout.kind() == TypeKind::Struct { + if type_layout.kind() == self::reflection::TypeKind::Struct { self.stack.extend(type_layout.fields().map(|field| { let Some(field_type_layout) = field.type_layout() else { // I do not know in which cases type_layout can return @@ -1269,8 +686,8 @@ impl<'a> Iterator for VarInputDfsIter<'a> struct VarInput<'a> { - var_layout: VariableLayout<'a>, - type_layout: TypeLayout<'a>, + var_layout: self::reflection::VariableLayout<'a>, + type_layout: self::reflection::TypeLayout<'a>, index: usize, } @@ -1300,9 +717,7 @@ fn import( source: std::fs::read_to_string(file_path) .map_err(ImportError::ReadFileFailed)? .into(), - link_entrypoints: settings - .map(|settings| settings.link_entrypoints) - .unwrap_or_default(), + link_entrypoints: settings.map_or_default(|settings| settings.link_entrypoints), }); Ok(()) @@ -1338,36 +753,12 @@ pub(super) fn prepare(collector: &mut crate::ecs::extension::Collector<'_>) return; }; - let session_options = shader_slang::CompilerOptions::default() - .optimization(shader_slang::OptimizationLevel::None) - .matrix_layout_row(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(), + global_session, + session: None, + modules: hashbrown::HashMap::new(), + programs: hashbrown::HashMap::new(), }) .ok(); @@ -1400,7 +791,11 @@ fn load_modules( let assets = assets.get()?; let asset_events = asset_events.get()?; - for AssetEvent::Stored(asset_id, asset_label) in asset_events.iter_last_tick() { + let Some(session) = context.session.as_ref() else { + return Ok(()); + }; + + for AssetEvent::Loaded(asset_id, asset_label) in asset_events.iter_last_tick() { let asset_handle = AssetHandle::<ModuleSource>::from_id(*asset_id); if !assets.is_loaded_and_has_type(&asset_handle) { @@ -1413,7 +808,7 @@ fn load_modules( tracing::debug!(asset_label=?asset_label, "Loading shader module"); - let module = match load_module(&context.session, module_source) { + let module = match load_module(session, module_source) { Ok(module) => module, Err(err) => { tracing::error!("Failed to load shader module: {err}"); diff --git a/engine/src/rendering/shader/cursor.rs b/engine/src/rendering/shader/cursor.rs index 24d0135..7750623 100644 --- a/engine/src/rendering/shader/cursor.rs +++ b/engine/src/rendering/shader/cursor.rs @@ -8,7 +8,7 @@ use crate::color::{Color, Rgb, Rgba}; use crate::data_types::matrix::Matrix; use crate::data_types::vector::Vec3; use crate::rendering::object::Id as RenderingObjectId; -use crate::rendering::shader::{ +use crate::rendering::shader::reflection::{ ResourceShape, ScalarType, TypeKind, diff --git a/engine/src/rendering/shader/reflection.rs b/engine/src/rendering/shader/reflection.rs new file mode 100644 index 0000000..c6d9248 --- /dev/null +++ b/engine/src/rendering/shader/reflection.rs @@ -0,0 +1,713 @@ +use bitflags::bitflags; + +pub struct Function<'a> +{ + pub(super) inner: &'a shader_slang::reflection::Function, +} + +impl Function<'_> +{ + #[must_use] + pub fn name(&self) -> Option<&str> + { + self.inner.name() + } +} + +pub struct EntryPointReflection<'a> +{ + inner: &'a shader_slang::reflection::EntryPoint, +} + +impl<'a> EntryPointReflection<'a> +{ + #[must_use] + pub fn name(&self) -> Option<&str> + { + self.inner.name() + } + + #[must_use] + pub fn name_override(&self) -> Option<&str> + { + self.inner.name_override() + } + + #[must_use] + pub fn stage(&self) -> Stage + { + Stage::from_slang_stage(self.inner.stage()) + } + + #[must_use] + pub fn parameters(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>> + { + self.inner + .parameters() + .map(|param| VariableLayout { inner: param }) + } + + #[must_use] + pub fn var_layout(&self) -> Option<VariableLayout<'a>> + { + Some(VariableLayout { inner: self.inner.var_layout()? }) + } +} + +pub struct Program<'a> +{ + pub(super) inner: &'a shader_slang::reflection::Shader, +} + +impl<'a> Program<'a> +{ + #[must_use] + pub fn get_entry_point_by_index(&self, index: u32) + -> Option<EntryPointReflection<'a>> + { + Some(EntryPointReflection { + inner: self.inner.entry_point_by_index(index)?, + }) + } + + #[must_use] + pub fn get_entry_point_by_name(&self, name: &str) + -> Option<EntryPointReflection<'a>> + { + Some(EntryPointReflection { + inner: self.inner.find_entry_point_by_name(name)?, + }) + } + + #[must_use] + pub fn entry_points( + &self, + ) -> impl ExactSizeIterator<Item = EntryPointReflection<'a>> + use<'a> + { + self.inner + .entry_points() + .map(|entry_point| EntryPointReflection { inner: entry_point }) + } + + #[must_use] + pub fn global_params_type_layout(&self) -> Option<TypeLayout<'a>> + { + Some(TypeLayout { + inner: self.inner.global_params_type_layout()?, + }) + } + + #[must_use] + pub fn global_params_var_layout(&self) -> Option<VariableLayout<'a>> + { + Some(VariableLayout { + inner: self.inner.global_params_var_layout()?, + }) + } + + #[must_use] + pub fn get_type(&self, name: &str) -> Option<TypeReflection<'a>> + { + Some(TypeReflection { + inner: self.inner.find_type_by_name(name)?, + }) + } + + #[must_use] + 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> +{ + #[must_use] + pub fn name(&self) -> Option<&'a str> + { + self.inner.name() + } + + #[must_use] + pub fn semantic_name(&self) -> Option<&str> + { + self.inner.semantic_name() + } + + #[must_use] + pub fn binding_index(&self) -> u32 + { + self.inner + .offset(shader_slang::ParameterCategory::DescriptorTableSlot) as u32 + + // self.inner.binding_index() + } + + #[must_use] + pub fn varying_input_offset(&self) -> Option<usize> + { + if !self + .inner + .categories() + .any(|category| category == shader_slang::ParameterCategory::VaryingInput) + { + return None; + } + + Some( + self.inner + .offset(shader_slang::ParameterCategory::VaryingInput), + ) + } + + #[must_use] + pub fn binding_space(&self) -> u32 + { + self.inner.binding_space() + } + + #[must_use] + pub fn semantic_index(&self) -> usize + { + self.inner.semantic_index() + } + + #[must_use] + pub fn offset(&self) -> usize + { + self.inner.offset(shader_slang::ParameterCategory::Uniform) + } + + #[must_use] + pub fn ty(&self) -> Option<TypeReflection<'a>> + { + self.inner.ty().map(|ty| TypeReflection { inner: ty }) + } + + #[must_use] + 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> +{ + #[must_use] + pub fn kind(&self) -> TypeKind + { + TypeKind::from_slang_type_kind(self.inner.kind()) + } + + #[must_use] + pub fn scalar_type(&self) -> Option<ScalarType> + { + Some(ScalarType::from_slang_scalar_type( + self.inner.scalar_type()?, + )) + } + + #[must_use] + pub fn resource_shape(&self) -> Option<ResourceShape> + { + Some(ResourceShape::from_bits_retain( + self.inner.resource_shape()? as u32, + )) + } + + #[must_use] + 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 }) + } + + #[must_use] + pub fn parameter_category(&self) -> ParameterCategory + { + ParameterCategory::from_slang_parameter_category(self.inner.parameter_category()) + } + + #[must_use] + pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64 + { + self.inner.binding_range_descriptor_set_index(index) + } + + #[must_use] + 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()) + } + + #[must_use] + pub fn ty(&self) -> Option<TypeReflection<'a>> + { + self.inner.ty().map(|ty| TypeReflection { inner: ty }) + } + + #[must_use] + pub fn fields(&self) -> FieldIter<'a> + { + FieldIter { + type_layout: *self, + cnt: self.field_cnt(), + index: 0, + } + } + + #[must_use] + pub fn field_cnt(&self) -> u32 + { + self.inner.field_count() + } + + #[must_use] + pub fn element_cnt(&self) -> Option<usize> + { + self.inner.element_count() + } + + #[must_use] + pub fn row_cnt(&self) -> Option<u32> + { + self.inner.row_count() + } + + #[must_use] + pub fn column_cnt(&self) -> Option<u32> + { + self.inner.column_count() + } + + #[must_use] + pub fn element_type_layout(&self) -> Option<TypeLayout<'a>> + { + self.inner + .element_type_layout() + .map(|type_layout| TypeLayout { inner: type_layout }) + } + + #[must_use] + pub fn element_var_layout(&self) -> Option<VariableLayout<'a>> + { + self.inner + .element_var_layout() + .map(|var_layout| VariableLayout { inner: var_layout }) + } + + #[must_use] + pub fn container_var_layout(&self) -> Option<VariableLayout<'a>> + { + self.inner + .container_var_layout() + .map(|var_layout| VariableLayout { inner: var_layout }) + } + + #[must_use] + pub fn uniform_size(&self) -> Option<usize> + { + if !self + .inner + .categories() + .any(|category| category == shader_slang::ParameterCategory::Uniform) + { + return None; + } + + Some(self.inner.size(shader_slang::ParameterCategory::Uniform)) + } + + #[must_use] + pub fn stride(&self) -> usize + { + self.inner.stride(self.inner.categories().next().unwrap()) + } +} + +pub struct FieldIter<'a> +{ + type_layout: TypeLayout<'a>, + cnt: u32, + index: u32, +} + +impl<'a> Iterator for FieldIter<'a> +{ + type Item = VariableLayout<'a>; + + fn next(&mut self) -> Option<Self::Item> + { + if self.index == self.cnt { + return None; + } + + let Some(field) = self.type_layout.inner.field_by_index(self.index) else { + unreachable!(); + }; + + self.index += 1; + + Some(VariableLayout { inner: field }) + } + + fn size_hint(&self) -> (usize, Option<usize>) + { + let len = (self.cnt - self.index) as usize; + + (len, Some(len)) + } +} + +impl ExactSizeIterator for FieldIter<'_> +{ + fn len(&self) -> usize + { + (self.cnt - self.index) as usize + } +} + +impl DoubleEndedIterator for FieldIter<'_> +{ + fn next_back(&mut self) -> Option<Self::Item> + { + if self.cnt == 0 || self.index == self.cnt - 1 { + return None; + } + + let Some(field) = self.type_layout.inner.field_by_index(self.cnt - 1) else { + unreachable!(); + }; + + self.cnt -= 1; + + Some(VariableLayout { inner: field }) + } +} + +pub struct TypeReflection<'a> +{ + inner: &'a shader_slang::reflection::Type, +} + +impl TypeReflection<'_> +{ + #[must_use] + 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: shader_slang::TypeKind) -> Self + { + match type_kind { + shader_slang::TypeKind::None => Self::None, + shader_slang::TypeKind::Struct => Self::Struct, + shader_slang::TypeKind::Enum => Self::Enum, + shader_slang::TypeKind::Array => Self::Array, + shader_slang::TypeKind::Matrix => Self::Matrix, + shader_slang::TypeKind::Vector => Self::Vector, + shader_slang::TypeKind::Scalar => Self::Scalar, + shader_slang::TypeKind::ConstantBuffer => Self::ConstantBuffer, + shader_slang::TypeKind::Resource => Self::Resource, + shader_slang::TypeKind::SamplerState => Self::SamplerState, + shader_slang::TypeKind::TextureBuffer => Self::TextureBuffer, + shader_slang::TypeKind::ShaderStorageBuffer => Self::ShaderStorageBuffer, + shader_slang::TypeKind::ParameterBlock => Self::ParameterBlock, + shader_slang::TypeKind::GenericTypeParameter => Self::GenericTypeParameter, + shader_slang::TypeKind::Interface => Self::Interface, + shader_slang::TypeKind::OutputStream => Self::OutputStream, + shader_slang::TypeKind::MeshOutput => Self::MeshOutput, + shader_slang::TypeKind::Specialized => Self::Specialized, + shader_slang::TypeKind::Feedback => Self::Feedback, + shader_slang::TypeKind::Pointer => Self::Pointer, + shader_slang::TypeKind::DynamicResource => Self::DynamicResource, + shader_slang::TypeKind::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: shader_slang::ScalarType) -> Self + { + match scalar_type { + shader_slang::ScalarType::None => Self::None, + shader_slang::ScalarType::Void => Self::Void, + shader_slang::ScalarType::Bool => Self::Bool, + shader_slang::ScalarType::Int32 => Self::Int32, + shader_slang::ScalarType::Uint32 => Self::Uint32, + shader_slang::ScalarType::Int64 => Self::Int64, + shader_slang::ScalarType::Uint64 => Self::Uint64, + shader_slang::ScalarType::Float16 => Self::Float16, + shader_slang::ScalarType::Float32 => Self::Float32, + shader_slang::ScalarType::Float64 => Self::Float64, + shader_slang::ScalarType::Int8 => Self::Int8, + shader_slang::ScalarType::Uint8 => Self::Uint8, + shader_slang::ScalarType::Int16 => Self::Int16, + shader_slang::ScalarType::Uint16 => Self::Uint16, + shader_slang::ScalarType::Intptr => Self::Intptr, + shader_slang::ScalarType::Uintptr => Self::Uintptr, + shader_slang::ScalarType::Bfloat16 => Self::Bfloat16, + shader_slang::ScalarType::FloatE4m3 => Self::FloatE4m3, + shader_slang::ScalarType::FloatE5m2 => Self::FloatE5m2, + #[allow(unreachable_patterns)] + _ => unimplemented!("conversion from slang scalar type"), + } + } +} + +bitflags! { +#[derive(Debug, Copy, Clone)] +pub struct ResourceShape: u32 { + const BASE = shader_slang::ResourceShape::SlangResourceBaseShapeMask as u32; + const NONE = shader_slang::ResourceShape::SlangResourceNone as u32; + const TEXTURE_1D = shader_slang::ResourceShape::SlangTexture1d as u32; + const TEXTURE_2D = shader_slang::ResourceShape::SlangTexture2d as u32; + const TEXTURE_3D = shader_slang::ResourceShape::SlangTexture3d as u32; + const TEXTURE_CUBE = shader_slang::ResourceShape::SlangTextureCube as u32; + const TEXTURE_BUFFER = shader_slang::ResourceShape::SlangTextureBuffer as u32; + const STRUCTURED_BUFFER = shader_slang::ResourceShape::SlangStructuredBuffer as u32; + const BYTE_ADDRESS_BUFFER = shader_slang::ResourceShape::SlangByteAddressBuffer as u32; + const UNKNOWN = shader_slang::ResourceShape::SlangResourceUnknown as u32; + const ACCELERATION_STRUCTURE = shader_slang::ResourceShape::SlangAccelerationStructure as u32; + const TEXTURE_SUBPASS = shader_slang::ResourceShape::SlangTextureSubpass as u32; + const EXT_SHAPE = shader_slang::ResourceShape::SlangResourceExtShapeMask as u32; + const TEXTURE_FEEDBACK_FLAG = shader_slang::ResourceShape::SlangTextureFeedbackFlag as u32; + const TEXTURE_SHADOW_FLAG = shader_slang::ResourceShape::SlangTextureShadowFlag as u32; + const TEXTURE_ARRAY_FLAG = shader_slang::ResourceShape::SlangTextureArrayFlag as u32; + const TEXTURE_MULTISAMPLE_FLAG = shader_slang::ResourceShape::SlangTextureMultisampleFlag as u32; + const TEXTURE_COMBINED_FLAG = shader_slang::ResourceShape::SlangTextureCombinedFlag as u32; + const TEXTURE_1D_ARRAY = shader_slang::ResourceShape::SlangTexture1dArray as u32; + const TEXTURE_2D_ARRAY = shader_slang::ResourceShape::SlangTexture2dArray as u32; + const TEXTURE_CUBE_ARRAY = shader_slang::ResourceShape::SlangTextureCubeArray as u32; + const TEXTURE_2D_MULTISAMPLE = shader_slang::ResourceShape::SlangTexture2dMultisample as u32; + const TEXTURE_2D_MULTISAMPLE_ARRAY = shader_slang::ResourceShape::SlangTexture2dMultisampleArray as u32; + const TEXTURE_SUBPASS_MULTISAMPLE = shader_slang::ResourceShape::SlangTextureSubpassMultisample as u32; +} +} + +#[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: shader_slang::ParameterCategory, + ) -> Self + { + match parameter_category { + shader_slang::ParameterCategory::None => Self::None, + shader_slang::ParameterCategory::Mixed => Self::Mixed, + shader_slang::ParameterCategory::ConstantBuffer => Self::ConstantBuffer, + shader_slang::ParameterCategory::ShaderResource => Self::ShaderResource, + shader_slang::ParameterCategory::UnorderedAccess => Self::UnorderedAccess, + shader_slang::ParameterCategory::VaryingInput => Self::VaryingInput, + shader_slang::ParameterCategory::VaryingOutput => Self::VaryingOutput, + shader_slang::ParameterCategory::SamplerState => Self::SamplerState, + shader_slang::ParameterCategory::Uniform => Self::Uniform, + shader_slang::ParameterCategory::DescriptorTableSlot => { + Self::DescriptorTableSlot + } + shader_slang::ParameterCategory::SpecializationConstant => { + Self::SpecializationConstant + } + shader_slang::ParameterCategory::PushConstantBuffer => { + Self::PushConstantBuffer + } + shader_slang::ParameterCategory::RegisterSpace => Self::RegisterSpace, + shader_slang::ParameterCategory::Generic => Self::Generic, + shader_slang::ParameterCategory::RayPayload => Self::RayPayload, + shader_slang::ParameterCategory::HitAttributes => Self::HitAttributes, + shader_slang::ParameterCategory::CallablePayload => Self::CallablePayload, + shader_slang::ParameterCategory::ShaderRecord => Self::ShaderRecord, + shader_slang::ParameterCategory::ExistentialTypeParam => { + Self::ExistentialTypeParam + } + shader_slang::ParameterCategory::ExistentialObjectParam => { + Self::ExistentialObjectParam + } + shader_slang::ParameterCategory::SubElementRegisterSpace => { + Self::SubElementRegisterSpace + } + shader_slang::ParameterCategory::Subpass => Self::Subpass, + shader_slang::ParameterCategory::MetalArgumentBufferElement => { + Self::MetalArgumentBufferElement + } + shader_slang::ParameterCategory::MetalAttribute => Self::MetalAttribute, + shader_slang::ParameterCategory::MetalPayload => Self::MetalPayload, + shader_slang::ParameterCategory::Count => Self::Count, + } + } +} + +#[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, + } + } +} diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs index 0047394..b3a102b 100644 --- a/engine/src/ui/dear_imgui.rs +++ b/engine/src/ui/dear_imgui.rs @@ -9,7 +9,6 @@ use dear_imgui_rs::{ MouseButton as ImguiMouseButton, }; use ecs::component::local::Local; -use ecs::component::Component; use ecs::error::Context as _; use ecs::event::component::{Changed, EventMatchExt}; use ecs::pair::Pair; @@ -56,6 +55,7 @@ use crate::rendering::shader::{ }; use crate::rendering::{ AssetOrValue, + BackendProperties as RenderingBackendProperties, Command as RenderingCommand, DrawMeshOptions, DrawProperties, @@ -211,7 +211,7 @@ fn handle_window_changed( let Some(event_match) = observe .iter() - .find(|event_match| event_match.get_entity().has_component(TargetWindow::id())) + .find(|event_match| event_match.get_entity().has_component::<TargetWindow>()) else { return; }; @@ -244,6 +244,7 @@ fn update( mut assets: Single<Assets>, mut render_passes: Single<RenderPasses>, shader_context: Single<ShaderContext>, + rendering_backend_properties: Single<RenderingBackendProperties>, keyboard: Single<Keyboard>, mouse: Single<Mouse>, mouse_buttons: Single<MouseButtons>, @@ -261,6 +262,10 @@ fn update( let mouse = mouse.get()?; let mouse_buttons = mouse_buttons.get()?; + let Ok(rendering_backend_properties) = rendering_backend_properties.get() else { + return Ok(()); + }; + let Some((window, window_surface)) = target_window_query.iter().next() else { return Ok(()); }; @@ -302,6 +307,7 @@ fn update( context, render_passes, shader_context, + rendering_backend_properties, window_surface.id, shader_asset.clone(), mesh_obj_id, @@ -479,6 +485,7 @@ fn add_drawing_render_pass( context: &mut Context, render_passes: &mut RenderPasses, shader_context: &ShaderContext, + rendering_backend_properties: &RenderingBackendProperties, window_surface_id: SurfaceId, shader_asset: AssetHandle<ShaderModuleSource>, mesh_obj_id: RenderingObjectId, @@ -507,7 +514,7 @@ fn add_drawing_render_pass( let shader_cursor = ShaderCursor::new( shader_program - .reflection(0) + .reflection(rendering_backend_properties.shader_target_id) .unwrap() .global_params_var_layout() .unwrap(), diff --git a/engine/src/ui/view/world.rs b/engine/src/ui/view/world.rs index 411acd7..bf92859 100644 --- a/engine/src/ui/view/world.rs +++ b/engine/src/ui/view/world.rs @@ -4,15 +4,15 @@ use std::io::Cursor; use std::ops::{Deref, DerefMut}; use ecs::error::Context; +use ecs::phase::Phase; +use ecs::query::term::{With, Without}; +use ecs::sole::Is as IsSole; +use ecs::system::Function as SystemFunction; use crate::color::{Rgb, Rgba}; use crate::ecs::actions::Actions; use crate::ecs::component::local::Local; -use crate::ecs::component::{ - Handle as ComponentHandle, - Info as ComponentInfo, - Parts as ComponentParts, -}; +use crate::ecs::component::{Info as ComponentInfo, Parts as ComponentParts}; use crate::ecs::entity::{Handle as EntityHandle, Name as EntityName}; use crate::ecs::sole::Single; use crate::ecs::uid::Uid; @@ -47,7 +47,13 @@ pub struct State } pub fn show( - query: Query<()>, + non_sole_query: Query<( + Without<ComponentInfo>, + Without<Phase>, + Without<SystemFunction>, + Without<IsSole>, + )>, + sole_query: Query<(With<IsSole>,)>, mut dear_imgui_context: Single<DearImguiContext>, mut state: Local<State>, world: &World, @@ -84,7 +90,18 @@ pub fn show( frame.spacing(); frame.spacing(); - for ent_handle in &query.into_flexible_query() { + for ent_handle in &non_sole_query.into_flexible_query() { + create_entity_widgets( + frame, + popup_state, + icon_textures, + &ent_handle, + world, + &mut actions, + ); + } + + for ent_handle in &sole_query.into_flexible_query() { create_entity_widgets( frame, popup_state, @@ -169,18 +186,6 @@ fn create_entity_widgets( actions: &mut Actions, ) { - if ent_handle.component_ids().next().is_some() - && !ent_handle.component_ids().any(|comp_id| { - world.get_entity(comp_id).is_some_and(|comp_ent| { - comp_ent - .get::<ComponentInfo>() - .is_some_and(|comp_info| comp_info.type_reflection.is_some()) - }) - }) - { - return; - } - let ent_title = create_entity_title(ent_handle); let mut is_open = false; @@ -266,14 +271,6 @@ fn create_component_widgets( return; }; - let Some(component_type) = component_info.type_reflection else { - return; - }; - - let Some(mut component) = ent_handle.get_any_by_id_mut(component_id) else { - unreachable!(); - }; - { let _color_token = frame.push_style_color( dear_imgui_bindings::StyleColor::Button, @@ -301,42 +298,43 @@ fn create_component_widgets( frame.same_line(); - let mut component_changed = false; + frame.text(component_info.name); - create_item_title_widget( - frame, - component_info.name, - &ItemType::Reflected(component_type), - None, - ); + if let Some(component_type) = component_info.type_reflection { + let item_type = ItemType::Reflected(component_type); - let item_type = ItemType::Reflected(component_type); + let _token = item_type.indent_or_same_line(frame); - let _token = item_type.indent_or_same_line(frame); + let Some(mut component) = ent_handle.get_any_by_id_mut(component_id) else { + unreachable!(); + }; - add_item_to_frame( - frame, - ItemRef::Mutable(&mut *component), - &mut component_changed, - ItemInfo { - title: component_info.name, - tag: &format!( - "world_view_entity_{}_component_{}", - ent_handle.uid(), - component_info.name, - ), - type_name: None, - ty: item_type, - is_read_only: false, - }, - &[], - ); + let mut component_changed = false; - frame.spacing(); + add_item_to_frame( + frame, + ItemRef::Mutable(&mut *component), + &mut component_changed, + ItemInfo { + title: component_info.name, + tag: &format!( + "world_view_entity_{}_component_{}", + ent_handle.uid(), + component_info.name, + ), + type_name: None, + ty: item_type, + is_read_only: false, + }, + &[], + ); - if component_changed { - component.set_changed(); + if component_changed { + component.set_changed(); + } } + + frame.spacing(); } fn create_pair_component_widgets( @@ -375,22 +373,8 @@ fn create_pair_component_widgets( frame.same_line(); - let relation_comp_info_or_ent_name = - get_comp_info_or_ent_name(world, pair_id.relation()); - - let relation_name: Cow<'_, str> = match &relation_comp_info_or_ent_name { - Some(Either::A(comp_info)) => comp_info.name.into(), - Some(Either::B(ent_name)) => format!("🌐 {}", ent_name.name).into(), - None => format!("<unnamed> {}", pair_id.relation()).into(), - }; - - let target_comp_info_or_ent_name = get_comp_info_or_ent_name(world, pair_id.target()); - - let target_name: Cow<'_, str> = match &target_comp_info_or_ent_name { - Some(Either::A(comp_info)) => comp_info.name.into(), - Some(Either::B(ent_name)) => format!("🌐 {}", ent_name.name).into(), - None => format!("<unnamed> {}", pair_id.target()).into(), - }; + let relation_name = get_comp_name_or_ent_name(world, pair_id.relation()); + let target_name = get_comp_name_or_ent_name(world, pair_id.target()); // TODO: Make relation & target ids editable @@ -402,15 +386,14 @@ fn create_pair_component_widgets( let pair_data_ty_id = (*pair_data).type_id(); - let Some(pair_data_comp_info) = [ - &relation_comp_info_or_ent_name, - &target_comp_info_or_ent_name, - ] - .map(Option::as_ref) - .map(|item| item.and_then(Either::as_a)) - .into_iter() - .flatten() - .find(|comp_info| comp_info.ty_id == pair_data_ty_id) else { + let pair_member_comp_infos = [pair_id.relation(), pair_id.target()] + .map(|member_id| world.get_entity(member_id)?.get::<ComponentInfo>()); + + let Some(pair_data_comp_info) = pair_member_comp_infos + .into_iter() + .filter_map(std::convert::identity) + .find(|comp_info| comp_info.ty_id == pair_data_ty_id) + else { return; }; @@ -465,22 +448,21 @@ fn create_pair_component_widgets( // pairs as event targets } -fn get_comp_info_or_ent_name( - world: &World, - id: Uid, -) -> Option<Either<ComponentHandle<'_, ComponentInfo>, ComponentHandle<'_, EntityName>>> +fn get_comp_name_or_ent_name(world: &World, id: Uid) -> Cow<'static, str> { - let ent = world.get_entity(id)?; + let Some(ent) = world.get_entity(id) else { + return format!("<unnamed> {id}").into(); + }; if let Some(component_info) = ent.get::<ComponentInfo>() { - return Some(Either::A(component_info)); + return component_info.name.into(); } if let Some(entity_name) = ent.get::<EntityName>() { - return Some(Either::B(entity_name)); + return format!("🌐 {}", entity_name.name).into(); } - None + format!("🌐 <unnamed> {id}").into() } fn create_add_component_button_widget( @@ -506,10 +488,24 @@ fn create_add_component_button_widget( [90.0, 162.0, 51.0, 255.0].map(|num| num / 255.0), ); - let is_button_clicked = frame.button(format!( - "+##add_component_to_entity_button_{}", - ent_handle.uid() - )); + let ent_is_sole = ent_handle.has_component::<IsSole>(); + + let is_button_clicked = { + let _disabled_token = frame.begin_disabled_with_cond(ent_is_sole); + + frame.button(format!( + "+##add_component_to_entity_button_{}", + ent_handle.uid() + )) + }; + + if ent_is_sole { + if frame.is_item_hovered_with_flags( + dear_imgui_bindings::ItemHoveredFlags::ALLOW_WHEN_DISABLED, + ) { + frame.tooltip_text("Cannot add a component to the entity of a sole"); + } + } if is_button_clicked { enter_add_component_popup_state(popup_state, ent_handle, ent_title, world); diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs index 3e27075..b76d942 100644 --- a/engine/src/windowing.rs +++ b/engine/src/windowing.rs @@ -23,7 +23,6 @@ use winit::keyboard::PhysicalKey; use winit::window::{Window as WinitWindow, WindowId as WinitWindowId}; use crate::ecs::actions::Actions; -use crate::ecs::component::Component; use crate::ecs::event::component::{Added, Changed, EventMatchExt, Removed}; use crate::ecs::pair::{ChildOf, Pair}; use crate::ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE}; @@ -90,7 +89,7 @@ impl crate::ecs::extension::Extension for Extension collector.spawn_declared_entity(&PHASE); - collector.add_system(*PHASE, update_stuff); + collector.add_system(*PHASE, update); collector.add_observer(handle_window_changed); collector.add_observer(handle_window_removed); @@ -112,7 +111,7 @@ fn handle_window_creation_ready( unreachable!(); }; - if ent.has_component(Window::id()) || ent.has_component(WindowClosed::id()) { + if ent.has_component::<Window>() || ent.has_component::<WindowClosed>() { continue; } @@ -128,7 +127,7 @@ fn handle_window_creation_ready( } #[tracing::instrument(skip_all)] -fn update_stuff( +fn update( mut context: Single<Context>, mut keyboard: Single<Keyboard>, mut mouse: Single<Mouse>, @@ -153,7 +152,7 @@ fn update_stuff( update_inputs(context, keyboard, mouse, mouse_buttons); - receive_app_messages(context, &mut actions, world); + handle_app_messages(context, &mut actions, world); if context.shared_state.thread_panicked.load(Ordering::Relaxed) { cold_path(); @@ -181,7 +180,7 @@ fn update_stuff( Ok(()) } -fn receive_app_messages(context: &mut Context, actions: &mut Actions, world: &World) +fn handle_app_messages(context: &mut Context, actions: &mut Actions, world: &World) { let Context { ref mut windows, ref shared_state, .. |
