use std::any::type_name; use std::collections::VecDeque; use std::sync::atomic::{AtomicU64, Ordering}; use bitflags::bitflags; use ecs::actions::Actions; use ecs::pair::{ChildOf, Pair, Wildcard}; use ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase}; use ecs::query::term::Without; use ecs::sole::Single; use ecs::{Component, Query, declare_entity}; use crate::asset::{Assets, Handle as AssetHandle}; use crate::builder; use crate::camera::{Active as ActiveCamera, Camera}; use crate::data_types::dimens::Dimens; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::mesh::Mesh; use crate::model::{Materials as ModelMaterials, Model, Spec as ModelSpec}; use crate::renderer::object::{Id as ObjectId, Store as ObjectStore}; use crate::texture::Texture; use crate::transform::{Scale, Transform, WorldPosition}; use crate::windowing::window::Window; pub mod object; pub mod opengl; static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); declare_entity!( pub RENDER_PHASE, ( Phase, Pair::builder() .relation::() .target_id(*POST_UPDATE_PHASE) .build() ) ); builder! { /// Window graphics properties. #[builder(name=GraphicsPropertiesBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone, Component)] #[non_exhaustive] pub struct GraphicsProperties { /// Number of samples for multisampling. `None` means no multisampling. #[builder(skip_generate_fn)] pub multisampling_sample_cnt: Option, /// Whether graphics API debugging is enabled. pub debug: bool, /// Whether depth testing is enabled pub depth_test: bool, } } impl GraphicsProperties { pub fn builder() -> GraphicsPropertiesBuilder { GraphicsPropertiesBuilder::default() } } impl Default for GraphicsProperties { fn default() -> Self { Self::builder().build() } } impl GraphicsPropertiesBuilder { pub fn multisampling_sample_cnt(mut self, multisampling_sample_cnt: u8) -> Self { self.multisampling_sample_cnt = Some(multisampling_sample_cnt); self } pub fn no_multisampling(mut self) -> Self { self.multisampling_sample_cnt = None; self } } impl Default for GraphicsPropertiesBuilder { fn default() -> Self { Self { multisampling_sample_cnt: Some(8), debug: false, depth_test: true, } } } #[derive(Debug, Component)] pub struct SurfaceSpec { pub id: SurfaceId, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SurfaceId { inner: u64, } impl SurfaceId { pub fn new_unique() -> Self { Self { inner: NEXT_SURFACE_ID.fetch_add(1, Ordering::Relaxed), } } } #[derive(Debug)] #[non_exhaustive] pub enum Command { MakeCurrent(SurfaceId), ClearBuffers(BufferClearMask), SwapBuffers(SurfaceId), UseShader, // TODO: Add ability to specify shader ActivateShader, UseCamera(Camera, WorldPosition), ApplyTransform { transform: Transform, window_size: Dimens, }, SetShaderDirectionalLights(Vec), SetShaderPointLights(Vec<(PointLight, WorldPosition)>), CreateTexture(Texture), UseMaterial { material_asset: Option>, material_flags: MaterialFlags, global_light: GlobalLight, }, DrawMesh { mesh_asset: AssetHandle, }, SetPolygonModeConfig(PolygonModeConfig), } bitflags! { #[derive(Debug)] pub struct BufferClearMask: u8 { const COLOR = 1; const DEPTH = 2; const STENCIL = 3; } } /// Renderer command FIFO queue. /// /// This component is present in renderer context entities. #[derive(Debug, Component)] pub struct CommandQueue { queue: VecDeque, } impl CommandQueue { pub fn push(&mut self, command: Command) { self.queue.push_back(command); } pub fn drain(&mut self) -> impl Iterator + use<'_> { self.queue.drain(..) } } impl Default for CommandQueue { fn default() -> Self { CommandQueue { queue: VecDeque::with_capacity(100) } } } #[derive(Debug, Component)] pub struct WindowUsingRendererCtx; #[derive(Debug, Component)] pub struct CtxUsedByWindow; type RenderableEntity<'a> = ( &'a Model, Option<&'a MaterialFlags>, Option<&'a WorldPosition>, Option<&'a Scale>, Option<&'a DrawFlags>, ); #[tracing::instrument(skip_all)] pub fn enqueue_commands( renderer_ctx_query: Query<( &mut CommandQueue, &ObjectStore, &[Pair], )>, renderable_query: Query, (Without,)>, point_light_query: Query<(&PointLight, &WorldPosition)>, directional_light_query: Query<(&DirectionalLight,)>, camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, global_light: Single, assets: Single, mut actions: Actions, ) { let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { tracing::warn!("No current camera. Nothing will be rendered"); return; }; for (renderer_ctx_ent_id, (mut command_queue, object_store, used_by_windows)) in renderer_ctx_query.iter_with_euids() { for ctx_used_by_window in used_by_windows { let window_ent_id = ctx_used_by_window.id().target_entity(); let Some(window_ent) = ctx_used_by_window.get_target_ent() else { tracing::error!("Window entity does not exist"); continue; }; let Some(window) = window_ent.get::() else { tracing::debug!( window_entity_id=%window_ent_id, "Window entity does not have a {} component", type_name::() ); actions.remove_components( renderer_ctx_ent_id, [Pair::builder() .relation::() .target_id(window_ent_id) .build() .id()], ); continue; }; let Some(surface_spec) = window_ent.get::() else { tracing::debug!( window_entity_id=%window_ent_id, "Window entity does not have a {} component", type_name::() ); continue; }; command_queue.push(Command::MakeCurrent(surface_spec.id)); command_queue .push(Command::UseCamera(camera.clone(), camera_world_pos.clone())); command_queue.push(Command::ClearBuffers( BufferClearMask::COLOR | BufferClearMask::DEPTH, )); for (model, material_flags, world_pos, scale, draw_flags) in &renderable_query { let Some(model_spec) = assets.get(&model.spec_asset) else { continue; }; let Some(mesh_asset) = &model_spec.mesh_asset else { continue; }; debug_assert!(model_spec.material_names.len() <= 1); let model_material_asset = match find_first_model_material(model_spec, &assets) { MaterialSearchResult::Found(model_material_asset) => { Some(model_material_asset.clone()) } MaterialSearchResult::NotFound => { continue; } MaterialSearchResult::NoMaterials => None, }; if let Some(model_material_asset) = &model_material_asset { let Some(model_material) = assets.get(model_material_asset) else { unreachable!(); }; if let Some(ambient_map) = &model_material.ambient_map { if assets.get(&ambient_map.asset_handle).is_none() { continue; } } if let Some(diffuse_map) = &model_material.diffuse_map { if assets.get(&diffuse_map.asset_handle).is_none() { continue; } } if let Some(specular_map) = &model_material.specular_map { if assets.get(&specular_map.asset_handle).is_none() { continue; } } } command_queue.push(Command::UseShader); command_queue.push(Command::ApplyTransform { transform: Transform { position: world_pos .as_deref() .cloned() .unwrap_or_default() .position, scale: scale.as_deref().cloned().unwrap_or_default().scale, }, window_size: *window.inner_size(), }); command_queue.push(Command::SetShaderDirectionalLights( directional_light_query .iter() .map(|(dir_light,)| dir_light.clone()) .collect::>(), )); command_queue.push(Command::SetShaderPointLights( point_light_query .iter() .map(|(point_light, point_light_world_pos)| { (point_light.clone(), point_light_world_pos.clone()) }) .collect::>(), )); if let Some(model_material_asset) = &model_material_asset { let Some(model_material) = assets.get(model_material_asset) else { unreachable!(); }; for texture in [ &model_material.specular_map, &model_material.diffuse_map, &model_material.specular_map, ] .into_iter() .flatten() { if !object_store .contains_with_id(&ObjectId::Asset(texture.asset_handle.id())) { command_queue.push(Command::CreateTexture(texture.clone())); } } } command_queue.push(Command::UseMaterial { material_asset: model_material_asset, material_flags: material_flags .as_deref() .cloned() .unwrap_or_default(), global_light: global_light.clone(), }); command_queue.push(Command::ActivateShader); if let Some(draw_flags) = draw_flags.as_deref() && draw_flags.polygon_mode_config != PolygonModeConfig::default() { command_queue.push(Command::SetPolygonModeConfig( draw_flags.polygon_mode_config.clone(), )); } command_queue.push(Command::DrawMesh { mesh_asset: mesh_asset.clone() }); if let Some(draw_flags) = draw_flags.as_deref() && draw_flags.polygon_mode_config != PolygonModeConfig::default() { command_queue.push(Command::SetPolygonModeConfig( PolygonModeConfig::default(), )); } } command_queue.push(Command::SwapBuffers(surface_spec.id)); } } } enum MaterialSearchResult<'a> { Found(&'a AssetHandle), NotFound, NoMaterials, } fn find_first_model_material<'assets>( model_spec: &'assets ModelSpec, assets: &'assets Assets, ) -> MaterialSearchResult<'assets> { let Some(material_name) = model_spec.material_names.first() else { return MaterialSearchResult::NoMaterials; }; let Some(material_asset) = (match &model_spec.materials { ModelMaterials::Maps(material_asset_map_assets) => material_asset_map_assets .iter() .find_map(|mat_asset_map_asset| { let mat_asset_map = assets.get(mat_asset_map_asset)?; mat_asset_map.assets.get(material_name) }), ModelMaterials::Direct(material_assets) => material_assets.get(material_name), }) else { return MaterialSearchResult::NotFound; }; if assets.get(material_asset).is_none() { tracing::trace!("Missing material asset"); return MaterialSearchResult::NotFound; } MaterialSearchResult::Found(material_asset) }