diff options
Diffstat (limited to 'engine/src/rendering.rs')
| -rw-r--r-- | engine/src/rendering.rs | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs new file mode 100644 index 0000000..55b5869 --- /dev/null +++ b/engine/src/rendering.rs @@ -0,0 +1,533 @@ +use std::collections::VecDeque; +use std::sync::atomic::{AtomicU64, Ordering}; + +use bitflags::bitflags; +use engine_macros::Reflection; + +use crate::asset::Handle as AssetHandle; +use crate::builder; +use crate::data_types::dimens::Dimens; +use crate::draw_flags::PolygonModeConfig; +use crate::ecs::actions::Actions; +use crate::ecs::component::local::Local; +use crate::ecs::event::component::{Changed, EventMatchExt, Removed}; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE}; +use crate::ecs::query::term::With; +use crate::ecs::sole::Single; +use crate::ecs::system::initializable::Initializable; +use crate::ecs::system::observer::Observe; +use crate::ecs::system::Into; +use crate::ecs::{declare_entity, Component, Query, Sole}; +use crate::mesh::Mesh; +use crate::rendering::blending::Config as BlendingConfig; +use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; +use crate::shader::cursor::{ + BindingLocation as ShaderBindingLocation, + BindingValue as ShaderBindingValue, + Cursor as ShaderCursor, +}; +use crate::shader::Program as ShaderProgram; +use crate::texture::Texture; +use crate::vector::Vec2; +use crate::windowing::window::Window; + +pub mod backend; +pub mod blending; +pub mod main_render_pass; +pub mod object; + +static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); + +declare_entity!( + pub PRE_RENDER_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*POST_UPDATE_PHASE) + .build() + ) +); + +declare_entity!( + pub RENDER_PHASE, + ( + Phase, + Pair::builder() + .relation::<ChildOf>() + .target_id(*PRE_RENDER_PHASE) + .build() + ) +); + +declare_entity!( + pub POST_RENDER_PHASE, + (Phase, Pair::builder().relation::<ChildOf>().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 crate::ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: crate::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(CommandQueue::default()); + let _ = collector.add_sole(ObjectStore::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 + .into_system() + .initialize((ActiveDrawProperties::default(),)), + ); + + collector.add_observer(handle_window_changed); + collector.add_observer(handle_window_removed); + + crate::rendering::backend::get_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<u8>, + + /// 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<RenderPass>, +} + +#[derive(Debug)] +pub struct RenderPass +{ + pub commands: Vec<Command>, + pub draw_properties: DrawProperties, +} + +#[derive(Debug, Reflection)] +#[non_exhaustive] +pub enum Command +{ + RemoveSurface(SurfaceId), + MakeCurrent(SurfaceId), + SetSurfaceSize(SurfaceId, Dimens<u32>), + ClearBuffers(BufferClearMask), + SwapBuffers(SurfaceId), + CreateShaderProgram(ObjectId, ShaderProgram), + ActivateShader(ObjectId), + SetShaderBinding(ShaderBindingLocation, ShaderBindingValue), + CreateTexture(AssetHandle<Texture>), + 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<Mesh>, + + usage: MeshUsage, + }, + UpdateMesh + { + obj_id: ObjectId, + mesh: Mesh, + usage: MeshUsage, + }, + RemoveMesh(ObjectId), + DrawMesh(ObjectId, DrawMeshOptions), + UpdateDrawProperties(DrawProperties, DrawPropertiesUpdateFlags), +} + +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<u32>, + } +} + +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<Dimens<u16>>, + + /// Position (in window coordinates) of the lower left corner of the scissor box. + pub lower_left_corner_pos: Vec2<u16>, +} + +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; + } +} + +/// Rendering command FIFO queue. +#[derive(Debug, Sole)] +pub struct CommandQueue +{ + queue: VecDeque<Command>, +} + +impl CommandQueue +{ + pub fn push(&mut self, command: Command) + { + self.queue.push_back(command); + } + + pub fn drain(&mut self) -> impl Iterator<Item = Command> + use<'_> + { + self.queue.drain(..) + } +} + +impl Default for CommandQueue +{ + fn default() -> Self + { + CommandQueue { queue: VecDeque::with_capacity(100) } + } +} + +#[tracing::instrument(skip_all)] +fn enqueue_commands_from_render_passes( + window_surface_spec_query: Query<(&SurfaceSpec,), (With<Window>,)>, + mut command_queue: Single<CommandQueue>, + mut render_passes: Single<RenderPasses>, + mut active_draw_props: Local<ActiveDrawProperties>, +) +{ + let mut last_render_pass_draw_props = active_draw_props.draw_properties.clone(); + + for render_pass in render_passes.passes.drain(..) { + if render_pass.draw_properties != last_render_pass_draw_props { + 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(); + + 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; + + for (window_surface_spec,) in &window_surface_spec_query { + command_queue.push(Command::SwapBuffers(window_surface_spec.id)); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_changed( + observe: Observe<Pair<Changed, Window>>, + mut command_queue: Single<CommandQueue>, +) +{ + for evt_match in &observe { + let window_ent = evt_match.get_entity(); + + let Some(window_surface_spec) = window_ent.get::<SurfaceSpec>() else { + continue; + }; + + let window = evt_match.get_ent_target_comp(); + + tracing::debug!( + window_id=?window.wid(), + window_title=&*window.title, + "Handling potential resize of window" + ); + + command_queue.queue.push_front(Command::SetSurfaceSize( + window_surface_spec.id, + evt_match.get_ent_target_comp().inner_size().clone(), + )); + + command_queue + .queue + .push_front(Command::MakeCurrent(window_surface_spec.id)); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_removed( + observe: Observe<Pair<Removed, Window>>, + mut command_queue: Single<CommandQueue>, + mut actions: Actions, +) +{ + for evt_match in &observe { + let window_ent_id = evt_match.entity_id(); + + let window_ent = evt_match.get_entity(); + + tracing::debug!( + entity_id = %window_ent_id, + title = %evt_match.get_ent_target_comp().title, + "Handling removal of window" + ); + + let Some(window_surface_spec) = window_ent.get::<SurfaceSpec>() else { + continue; + }; + + actions.remove_comps::<(SurfaceSpec,)>(window_ent_id); + + command_queue + .queue + .push_front(Command::RemoveSurface(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)>, + pub surface_specific_bindings: + Vec<(SurfaceId, ShaderBindingLocation, ShaderBindingValue)>, +} + +impl<'a> Extend<(ShaderCursor<'a>, ShaderBindingValue)> for PendingShaderBindings +{ + fn extend<Iter: IntoIterator<Item = (ShaderCursor<'a>, 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) + }, + )) + } +} + +#[derive(Debug, Default, Clone, Component)] +struct ActiveDrawProperties +{ + pub draw_properties: DrawProperties, +} |
