use std::any::type_name; use std::collections::VecDeque; use std::sync::atomic::{AtomicU64, Ordering}; use bitflags::bitflags; use ecs::entity::obtainer::Obtainer as EntityObtainer; use ecs::event::component::Changed; use ecs::pair::{ChildOf, Pair, Wildcard}; use ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase}; use ecs::sole::Single; use ecs::system::observer::Observe; use ecs::uid::Uid; use ecs::{Component, Query, Sole, declare_entity}; use crate::asset::Handle as AssetHandle; use crate::builder; use crate::data_types::dimens::Dimens; use crate::draw_flags::PolygonModeConfig; use crate::mesh::Mesh; use crate::renderer::blending::Config as BlendingConfig; use crate::renderer::object::Id as ObjectId; use crate::shader::Program as ShaderProgram; use crate::shader::cursor::{ BindingLocation as ShaderBindingLocation, BindingValue as ShaderBindingValue, Cursor as ShaderCursor, }; use crate::texture::Texture; use crate::vector::Vec2; use crate::windowing::window::Window; pub mod blending; pub mod main_render_pass; pub mod object; pub mod opengl; static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); 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() ) ); declare_entity!( pub POST_RENDER_PHASE, (Phase, Pair::builder().relation::().target_id(*RENDER_PHASE).build()) ); builder! { #[builder(name=ExtensionBuilder, derives=(Debug, Clone, Default))] #[derive(Debug, Clone)] #[non_exhaustive] pub struct Extension { pub graphics_props: GraphicsProperties, } } impl Extension { pub fn builder() -> ExtensionBuilder { ExtensionBuilder::default() } } impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_declared_entity(&PRE_RENDER_PHASE); collector.add_declared_entity(&RENDER_PHASE); collector.add_declared_entity(&POST_RENDER_PHASE); let _ = collector.add_sole(RenderPasses::default()); let _ = collector.add_sole(self.graphics_props); collector.add_system(*PRE_RENDER_PHASE, main_render_pass::add_main_render_passes); collector.add_system(*RENDER_PHASE, enqueue_commands_from_render_passes); collector.add_observer(handle_window_changed); opengl::Extension::default().collect(collector); } } impl Default for Extension { fn default() -> Self { Self::builder().build() } } builder! { #[builder(name=GraphicsPropertiesBuilder, derives=(Debug, Clone))] #[derive(Debug, Clone, Sole)] #[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, Default, Sole)] pub struct RenderPasses { pub passes: VecDeque, } #[derive(Debug)] pub struct RenderPass { pub renderer_ctx_ent_id: Uid, pub surface_id: SurfaceId, pub commands: Vec, pub draw_properties: DrawProperties, } #[derive(Debug)] #[non_exhaustive] pub enum Command { MakeCurrent(SurfaceId), ClearBuffers(BufferClearMask), SwapBuffers(SurfaceId), CreateShaderProgram(ObjectId, ShaderProgram), ActivateShader(ObjectId), SetShaderBinding(ShaderBindingLocation, ShaderBindingValue), CreateTexture(AssetHandle), CreateMesh { obj_id: ObjectId, /// Optional mesh data. Must be included if `obj_id` is [`ObjectId::Sequential`]. /// If `obj_id` is [`ObjectId::Asset`], this mesh data will be used instead of /// the mesh data stored in the asset. mesh: Option, usage: MeshUsage, }, UpdateMesh { obj_id: ObjectId, mesh: Mesh, usage: MeshUsage, }, RemoveMesh(ObjectId), DrawMesh(ObjectId, DrawMeshOptions), UpdateDrawProperties(DrawProperties, DrawPropertiesUpdateFlags), SetViewport { size: Dimens, position: Vec2, }, } builder! { #[builder(name = DrawMeshOptionsBuilder, derives = (Debug, Default, Clone))] #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct DrawMeshOptions { pub element_offset: u32, pub vertex_offset: u32, #[builder(skip_generate_fn)] pub element_cnt: Option, } } impl DrawMeshOptions { pub fn builder() -> DrawMeshOptionsBuilder { DrawMeshOptionsBuilder::default() } } impl DrawMeshOptionsBuilder { pub fn element_cnt(mut self, element_cnt: u32) -> Self { self.element_cnt = Some(element_cnt); self } } bitflags! { #[derive(Debug)] pub struct BufferClearMask: u8 { const COLOR = 1; const DEPTH = 2; const STENCIL = 3; } } #[derive(Debug, Clone, Copy)] pub enum MeshUsage { /// The mesh data is set only once and used by the GPU at most a few times. Stream, /// The mesh data is set only once and used many times. Static, /// The mesh data is changed a lot and used many times. Dynamic, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScissorBox { /// Size of the scissor box in window coordinates. When `None`, the dimensions of the /// window is used pub size: Option>, /// Position (in window coordinates) of the lower left corner of the scissor box. pub lower_left_corner_pos: Vec2, } impl Default for ScissorBox { fn default() -> Self { Self { size: None, lower_left_corner_pos: Vec2 { x: 0, y: 0 }, } } } #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct DrawProperties { pub polygon_mode_config: PolygonModeConfig, pub blending_enabled: bool, pub blending_config: BlendingConfig, pub depth_test_enabled: bool, pub scissor_test_enabled: bool, pub scissor_box: ScissorBox, pub face_culling_enabled: bool, } impl Default for DrawProperties { fn default() -> Self { Self { polygon_mode_config: PolygonModeConfig::default(), blending_enabled: false, blending_config: BlendingConfig::default(), depth_test_enabled: true, scissor_test_enabled: false, scissor_box: ScissorBox::default(), face_culling_enabled: false, } } } bitflags! { #[derive(Debug, Clone, Copy)] pub struct DrawPropertiesUpdateFlags: usize { const POLYGON_MODE_CONFIG = 1 << 0; const BLENDING_CONFIG = 1 << 1; const BLENDING_ENABLED = 1 << 2; const DEPTH_TEST_ENABLED = 1 << 3; const SCISSOR_TEST_ENABLED = 1 << 4; const SCISSOR_BOX = 1 << 5; const FACE_CULLING_ENABLED = 1 << 6; } } #[derive(Debug, Default, Clone, Component)] pub struct ActiveDrawProperties { pub draw_properties: DrawProperties, } /// 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; #[tracing::instrument(skip_all)] pub fn enqueue_commands_from_render_passes( renderer_ctx_query: Query<( &mut CommandQueue, &[Pair], &mut ActiveDrawProperties, )>, mut render_passes: Single, entity_obtainer: EntityObtainer, ) { let Some((_, _, mut active_draw_props)) = renderer_ctx_query.iter().next() else { return; }; let mut last_render_pass_draw_props = active_draw_props.draw_properties.clone(); for render_pass in render_passes.passes.drain(..) { let Some(renderer_ctx_ent) = entity_obtainer.get_entity(render_pass.renderer_ctx_ent_id) else { tracing::error!( renderer_ctx_entity_id=%render_pass.renderer_ctx_ent_id, "Renderer context entity does not exist" ); continue; }; let Some(mut renderer_ctx_command_queue) = renderer_ctx_ent.get_mut::() else { tracing::error!( renderer_ctx_entity_id=%render_pass.renderer_ctx_ent_id, "Renderer context entity does not have a command queue component" ); continue; }; if render_pass.draw_properties != last_render_pass_draw_props { renderer_ctx_command_queue.push(Command::UpdateDrawProperties( render_pass.draw_properties.clone(), DrawPropertiesUpdateFlags::all(), )); last_render_pass_draw_props = render_pass.draw_properties; } let last_updated_draw_props = render_pass .commands .iter() .filter_map(|command| match command { Command::UpdateDrawProperties(draw_props, _) => Some(draw_props.clone()), _ => None, }) .last(); renderer_ctx_command_queue .queue .extend(render_pass.commands); if let Some(last_updated_draw_props) = last_updated_draw_props { last_render_pass_draw_props = last_updated_draw_props; } } active_draw_props.draw_properties = last_render_pass_draw_props; drop(active_draw_props); for (mut command_queue, used_by_windows, _) in &renderer_ctx_query { 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(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::SwapBuffers(surface_spec.id)); } } } #[tracing::instrument(skip_all)] fn handle_window_changed( observe: Observe>, entity_obtainer: EntityObtainer, ) { for evt_match in &observe { let window_ent = evt_match.get_ent_infallible(); let Some(window_surface_spec) = window_ent.get::() else { continue; }; let Some(renderer_ctx_ent_id) = window_ent .get_matching_components( Pair::builder() .relation::() .target_id(Wildcard::uid()) .build() .id(), ) .next() .map(|comp_ref| comp_ref.id().target_entity()) else { continue; }; let Some(renderer_ctx_ent) = entity_obtainer.get_entity(renderer_ctx_ent_id) else { tracing::error!("Renderer context entity does not exist"); continue; }; let Some(mut command_queue) = renderer_ctx_ent.get_mut::() else { tracing::error!( "Renderer context entity does not have a {} component", type_name::() ); continue; }; command_queue.queue.push_front(Command::SetViewport { position: Vec2 { x: 0, y: 0 }, size: *evt_match.get_changed_comp().inner_size(), }); command_queue .queue .push_front(Command::MakeCurrent(window_surface_spec.id)); } } // TODO: Maybe move this struct to somewhere more appropriate #[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) }, )) } }