summaryrefslogtreecommitdiff
path: root/engine/src/rendering.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/rendering.rs')
-rw-r--r--engine/src/rendering.rs722
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,
+}