diff options
Diffstat (limited to 'engine/src/rendering.rs')
| -rw-r--r-- | engine/src/rendering.rs | 722 |
1 files changed, 722 insertions, 0 deletions
diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs new file mode 100644 index 0000000..747fdfa --- /dev/null +++ b/engine/src/rendering.rs @@ -0,0 +1,722 @@ +use std::collections::VecDeque; +use std::sync::atomic::{AtomicU64, Ordering}; + +use bitflags::bitflags; +use engine_macros::Reflection; + +use crate::asset::{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::{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, pair, Component, Query, Sole}; +use crate::image::{ColorType as ImageColorType, Image}; +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::texture::{CubeMapFace, Properties as TextureProperties}; +use crate::vector::Vec2; +use crate::windowing::dpi::PhysicalSize; +use crate::windowing::window::Window; + +pub mod backend; +pub mod blending; +pub mod main_render_pass; +pub mod object; +pub mod shader; + +static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); + +declare_entity! { +pub PRE_RENDER_PHASE: (Phase, pair!(ChildOf, { *POST_UPDATE_PHASE })); + +pub RENDER_PHASE: (Phase, pair!(ChildOf, { *PRE_RENDER_PHASE })); + +pub POST_RENDER_PHASE: (Phase, pair!(ChildOf, { *RENDER_PHASE })); +} + +builder! { +#[builder(name=ExtensionBuilder, derives=(Debug, Clone, Default))] +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Extension { + pub graphics_props: GraphicsProperties, +} +} + +impl Extension +{ + #[must_use] + pub fn builder() -> ExtensionBuilder + { + ExtensionBuilder::default() + } +} + +impl crate::ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) + { + collector.spawn_declared_entity(&PRE_RENDER_PHASE); + collector.spawn_declared_entity(&RENDER_PHASE); + collector.spawn_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_pass + .into_system() + .initialize(Default::default()), + ); + + collector.add_system( + *RENDER_PHASE, + enqueue_commands_from_render_passes + .into_system() + .initialize((ActiveDrawProperties::default(),)), + ); + + collector.add_observer(handle_window_removed); + + crate::rendering::shader::prepare(&mut collector); + + crate::rendering::backend::get_default().collect(collector); + } +} + +impl Default for Extension +{ + fn default() -> Self + { + Self::builder().build() + } +} + +/// Marker component for windows that should be renderer to. +#[derive(Debug, Default, Clone, Copy, Component, Reflection)] +pub struct TargetWindow; + +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 +{ + #[must_use] + pub fn builder() -> GraphicsPropertiesBuilder + { + GraphicsPropertiesBuilder::default() + } +} + +impl Default for GraphicsProperties +{ + fn default() -> Self + { + Self::builder().build() + } +} + +impl GraphicsPropertiesBuilder +{ + #[must_use] + pub fn multisampling_sample_cnt(mut self, multisampling_sample_cnt: u8) -> Self + { + self.multisampling_sample_cnt = Some(multisampling_sample_cnt); + self + } + + #[must_use] + 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(4), + debug: false, + depth_test: true, + } + } +} + +#[derive(Debug, Component)] +pub struct Surface +{ + pub id: SurfaceId, + pub size: PhysicalSize<u32>, +} + +#[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, PhysicalSize<u32>), + ClearBuffers(BufferClearMask), + SwapBuffers(SurfaceId), + CreateShaderProgram(ObjectId, ShaderProgram), + ActivateShader(ObjectId), + SetShaderBinding(ObjectId, ShaderBinding), + CreateTexture + { + obj_id: ObjectId, + pixel_data_format: TexturePixelDataFormat, + creation: TextureCreation, + properties: TextureProperties, + }, + UpdateTexture + { + obj_id: ObjectId, + pixels: Box<[u8]>, + pixel_data_format: TexturePixelDataFormat, + update: TextureUpdate, + }, + RemoveTexture(ObjectId), + CreateFramebuffer(ObjectId), + RemoveFramebuffer(ObjectId), + UpdateFramebufferProperties + { + object_id: ObjectId, + properties: FramebufferProperties, + update_flags: FramebufferPropertiesUpdateFlags, + }, + CreateMesh + { + obj_id: ObjectId, + mesh: AssetOrValue<Mesh>, + usage: MeshUsage, + }, + UpdateMesh + { + obj_id: ObjectId, + mesh: Mesh, + usage: MeshUsage, + }, + RemoveMesh(ObjectId), + DrawMesh(ObjectId, DrawMeshOptions), + UpdateDrawProperties(DrawProperties, DrawPropertiesUpdateFlags), +} + +#[derive(Debug)] +pub enum AssetOrValue<T: Asset> +{ + Asset(AssetHandle<T>), + Value(T), +} + +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 +{ + #[must_use] + pub fn builder() -> DrawMeshOptionsBuilder + { + DrawMeshOptionsBuilder::default() + } +} + +impl DrawMeshOptionsBuilder +{ + #[must_use] + pub fn element_cnt(mut self, element_cnt: u32) -> Self + { + self.element_cnt = Some(element_cnt); + self + } +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + 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, Default, Clone, Copy, PartialEq, Eq)] +pub enum DepthFunction +{ + /// Never passes. + Never, + + /// Passes if the incoming depth value is less than the stored depth value. + #[default] + Less, + + /// Passes if the incoming depth value is equal to the stored depth value. + Equal, + + /// Passes if the incoming depth value is less than or equal to the stored depth + /// value. + LessOrEqual, + + /// Passes if the incoming depth value is greater than the stored depth value. + Greater, + + /// Passes if the incoming depth value is not equal to the stored depth value. + NotEqual, + + /// Passes if the incoming depth value is greater than or equal to the stored depth + /// value. + GreaterOrEqual, + + /// Always passes. + Always, +} + +#[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 depth_function: DepthFunction, + pub scissor_test_enabled: bool, + pub scissor_box: ScissorBox, + pub face_culling_enabled: bool, + pub face_culling_mode: FaceCullingMode, + + /// `None` for default framebuffer + pub draw_framebuffer: Option<ObjectId>, + + /// `None` for default framebuffer + pub read_framebuffer: Option<ObjectId>, +} + +impl Default for DrawProperties +{ + fn default() -> Self + { + Self { + polygon_mode_config: PolygonModeConfig::default(), + blending_enabled: false, + blending_config: BlendingConfig::default(), + depth_test_enabled: true, + depth_function: DepthFunction::default(), + scissor_test_enabled: false, + scissor_box: ScissorBox::default(), + face_culling_enabled: false, + face_culling_mode: FaceCullingMode::default(), + draw_framebuffer: None, + read_framebuffer: None, + } + } +} + +bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct DrawPropertiesUpdateFlags: u16 + { + const POLYGON_MODE_CONFIG = 1 << 0; + const BLENDING_CONFIG = 1 << 1; + const BLENDING_ENABLED = 1 << 2; + const DEPTH_TEST_ENABLED = 1 << 3; + const DEPTH_FUNCTION = 1 << 4; + const SCISSOR_TEST_ENABLED = 1 << 5; + const SCISSOR_BOX = 1 << 6; + const FACE_CULLING_ENABLED = 1 << 7; + const FACE_CULLING_MODE = 1 << 8; + const DRAW_FRAMEBUFFER = 1 << 9; + const READ_FRAMEBUFFER = 1 << 10; + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum FaceCullingMode +{ + Front, + + #[default] + Back, + + FrontAndBack, +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum TextureCreation +{ + Texture2D + { + size: Dimens<u32>, + image: Option<Box<[u8]>>, + }, + CubeMap + { + size: Dimens<u32>, + images: Option<Box<[(CubeMapFace, Box<[u8]>); 6]>>, + }, +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum TextureUpdate +{ + Texture2D + { + size: Dimens<u32>, + + /// Specifies texel index offsets in the x and y directions within the texture + /// pixels. + offset: Vec2<u32>, + }, + CubeMap + { + size: Dimens<u32>, + + /// Specifies texel index offsets in the x and y directions within the texture + /// pixels. + offset: Vec2<u32>, + + face: CubeMapFace, + }, +} + +/// 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_query: Query<(&mut Surface, &Window, With<TargetWindow>)>, + mut command_queue: Single<CommandQueue>, + mut render_passes: Single<RenderPasses>, + mut active_draw_props: Local<ActiveDrawProperties>, +) +{ + let Ok(command_queue) = command_queue.get_mut() else { + unreachable!(); + }; + + let Ok(render_passes) = render_passes.get_mut() else { + unreachable!(); + }; + + let mut last_render_pass_draw_props = active_draw_props.draw_properties.clone(); + + for (mut window_surface, window) in &window_surface_query { + if window_surface.size != window.inner_size { + command_queue.push(Command::SetSurfaceSize( + window_surface.id, + window.inner_size, + )); + + window_surface.size = window.inner_size; + } + } + + 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, + }) + .next_back(); + + 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, _) in &window_surface_query { + command_queue.push(Command::SwapBuffers(window_surface.id)); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_removed( + observe: Observe<Pair<Removed, Window>>, + mut command_queue: Single<CommandQueue>, + mut actions: Actions, +) +{ + let Ok(command_queue) = command_queue.get_mut() else { + unreachable!(); + }; + + for evt_match in &observe { + let window_ent_id = evt_match.entity_id(); + + let window_ent = evt_match.get_entity(); + + if window_ent.get::<TargetWindow>().is_none() { + continue; + } + + tracing::debug!( + entity_id = %window_ent_id, + title = %evt_match.get_ent_target_comp().title, + "Handling removal of window" + ); + + let Some(window_surface) = window_ent.get::<Surface>() else { + continue; + }; + + actions.remove_comps::<(Surface,)>(window_ent_id); + + command_queue + .queue + .push_front(Command::RemoveSurface(window_surface.id)); + } +} + +#[derive(Debug, Default, Clone, Component)] +struct ActiveDrawProperties +{ + pub draw_properties: DrawProperties, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum RgbTextureDataType +{ + UnsignedByte, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum RgbaTextureDataType +{ + UnsignedByte, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum SrgbTextureDataType +{ + UnsignedByte, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum SrgbaTextureDataType +{ + UnsignedByte, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum DepthTextureDataType +{ + Float32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum TexturePixelDataFormat +{ + Rgb(RgbTextureDataType), + Rgba(RgbaTextureDataType), + Srgb(SrgbTextureDataType), + Srgba(SrgbaTextureDataType), + Depth(DepthTextureDataType), +} + +impl TexturePixelDataFormat +{ + #[must_use] + pub fn for_image(image: &Image) -> Option<Self> + { + let is_srgb = image.color_space_is_srgb(); + + match image.color_type() { + ImageColorType::Rgb8 if is_srgb => Some(TexturePixelDataFormat::Srgb( + SrgbTextureDataType::UnsignedByte, + )), + ImageColorType::Rgb8 => Some(TexturePixelDataFormat::Rgb( + RgbTextureDataType::UnsignedByte, + )), + ImageColorType::Rgba8 if is_srgb => Some(TexturePixelDataFormat::Srgba( + SrgbaTextureDataType::UnsignedByte, + )), + ImageColorType::Rgba8 => Some(TexturePixelDataFormat::Rgba( + RgbaTextureDataType::UnsignedByte, + )), + _ => None, + } + } +} + +/// Index of a color texture attached to a framebuffer. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct FramebufferColorTextureIndex(pub u8); + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct FramebufferProperties +{ + pub depth_texture: Option<FramebufferTexture>, + pub color_draw_buffer: Option<FramebufferColorTextureIndex>, +} + +bitflags! { + #[derive(Debug, Default, Clone)] + pub struct FramebufferPropertiesUpdateFlags: u8 + { + const DEPTH_TEXTURE = 1 << 0; + const COLOR_DRAW_BUFFER = 1 << 1; + } +} + +#[derive(Debug, Clone)] +pub struct FramebufferTexture +{ + pub object_id: ObjectId, + pub mipmap_level: u16, +} |
