use std::any::type_name; use std::collections::VecDeque; use std::path::Path; use std::sync::LazyLock; 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, Label as AssetLabel}; use crate::builder; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; use crate::image::Image; use crate::mesh::Mesh; use crate::model::{MaterialSearchResult, Model}; use crate::renderer::object::{Id as ObjectId, Store as ObjectStore}; use crate::shader::cursor::{ BindingLocation as ShaderBindingLocation, BindingValue as ShaderBindingValue, Cursor as ShaderCursor, }; use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; use crate::shader::{ Context as ShaderContext, ModuleSource as ShaderModuleSource, Program as ShaderProgram, Shader, }; use crate::texture::{Properties as TextureProperties, Texture}; use crate::windowing::window::Window; pub mod object; pub mod opengl; static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); pub static DEFAULT_TEXTURE_ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { path: Path::new("").into(), name: Some("default_texture".into()), }); declare_entity!( pub PRE_RENDER_PHASE, ( Phase, Pair::builder() .relation::() .target_id(*POST_UPDATE_PHASE) .build() ) ); declare_entity!( pub RENDER_PHASE, ( Phase, Pair::builder() .relation::() .target_id(*PRE_RENDER_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), CreateShaderProgram(ObjectId, ShaderProgram), ActivateShader(ObjectId), SetShaderBinding(ShaderBindingLocation, ShaderBindingValue), CreateTexture(Texture), 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 DrawFlags>, Option<&'a Shader>, Option<&'a mut PendingShaderBindings>, ); pub fn init(mut assets: Single) { assets.store_with_label( DEFAULT_TEXTURE_ASSET_LABEL.clone(), Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8), ); } #[tracing::instrument(skip_all)] pub fn enqueue_commands( renderer_ctx_query: Query<( &mut CommandQueue, &ObjectStore, &[Pair], )>, renderable_query: Query, (Without,)>, assets: Single, shader_context: Single, mut actions: Actions, ) { let Some(default_shader_asset) = assets .get_handle_to_loaded::(DEFAULT_SHADER_ASSET_LABEL.clone()) else { tracing::error!("Default shader asset is not loaded"); 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; }; if window_ent.get::().is_none() { 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)); let default_texture_asset = assets .get_handle_to_loaded::(DEFAULT_TEXTURE_ASSET_LABEL.clone()) .expect("Not possible"); if !object_store .contains_with_id(&ObjectId::Asset(default_texture_asset.id())) { command_queue.push(Command::CreateTexture(Texture { asset_handle: default_texture_asset, properties: TextureProperties::default(), })); } command_queue.push(Command::ClearBuffers( BufferClearMask::COLOR | BufferClearMask::DEPTH, )); for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query { let shader_asset = match &shader { Some(shader) => &shader.asset_handle, None => &default_shader_asset, }; if pending_shader_bindings.as_ref().map_or_else( || true, |pending_shader_bindings| pending_shader_bindings.bindings.is_empty(), ) { continue; } 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 model_spec.find_first_material(&assets) { MaterialSearchResult::Found(model_material_asset) => { model_material_asset.clone() // Some(model_material_asset.clone()) } MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => { // MaterialSearchResult::NotFound => { continue; } // MaterialSearchResult::NoMaterials => None, }; if !object_store.contains_with_id(&ObjectId::Asset(shader_asset.id())) { let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { tracing::error!( "Shader context doesn't have a program for shader asset {:?}", assets.get_label(&shader_asset) ); continue; }; command_queue.push(Command::CreateShaderProgram( ObjectId::Asset(shader_asset.id()), shader_program.clone(), )); } command_queue .push(Command::ActivateShader(ObjectId::Asset(shader_asset.id()))); let Some(model_material) = assets.get(&model_material_asset) else { // TODO: Handle this case since it may occur unreachable!(); }; for texture in [ &model_material.ambient_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())); } } if let Some(pending_shader_bindings) = &mut pending_shader_bindings { for (shader_binding_loc, shader_binding_val) in pending_shader_bindings.bindings.drain(..) { command_queue.push(Command::SetShaderBinding( shader_binding_loc, shader_binding_val, )); } } 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)); } } } #[derive(Default, Clone, Component)] pub struct PendingShaderBindings { pub bindings: Vec<(ShaderBindingLocation, ShaderBindingValue)>, } impl<'a> Extend<(ShaderCursor<'a>, ShaderBindingValue)> for PendingShaderBindings { fn extend, ShaderBindingValue)>>( &mut self, iter: Iter, ) { self.bindings.extend(iter.into_iter().map( |(shader_cursor, shader_binding_val)| { (shader_cursor.into_binding_location(), shader_binding_val) }, )) } }