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.rs533
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,
+}