diff options
Diffstat (limited to 'engine/src/renderer.rs')
| -rw-r--r-- | engine/src/renderer.rs | 374 |
1 files changed, 371 insertions, 3 deletions
diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs index 6d25f6b..2a66a68 100644 --- a/engine/src/renderer.rs +++ b/engine/src/renderer.rs @@ -1,11 +1,34 @@ -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE}; -use ecs::{declare_entity, Component}; +use std::any::type_name; +use std::collections::VecDeque; +use std::sync::atomic::{AtomicU64, Ordering}; +use bitflags::bitflags; +use ecs::actions::Actions; +use ecs::pair::{ChildOf, Pair, Wildcard}; +use ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase}; +use ecs::query::term::Without; +use ecs::sole::Single; +use ecs::{Component, Query, declare_entity}; + +use crate::asset::{Assets, Handle as AssetHandle}; use crate::builder; +use crate::camera::{Active as ActiveCamera, Camera}; +use crate::data_types::dimens::Dimens; +use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; +use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; +use crate::material::{Flags as MaterialFlags, Material}; +use crate::mesh::Mesh; +use crate::model::{Materials as ModelMaterials, Model, Spec as ModelSpec}; +use crate::renderer::object::{Id as ObjectId, Store as ObjectStore}; +use crate::texture::Texture; +use crate::transform::{Scale, Transform, WorldPosition}; +use crate::windowing::window::Window; +pub mod object; pub mod opengl; +static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); + declare_entity!( pub RENDER_PHASE, ( @@ -78,3 +101,348 @@ impl Default for GraphicsPropertiesBuilder } } } + +#[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)] +#[non_exhaustive] +pub enum Command +{ + MakeCurrent(SurfaceId), + ClearBuffers(BufferClearMask), + SwapBuffers(SurfaceId), + UseShader, // TODO: Add ability to specify shader + ActivateShader, + UseCamera(Camera, WorldPosition), + ApplyTransform + { + transform: Transform, + window_size: Dimens<u32>, + }, + SetShaderDirectionalLights(Vec<DirectionalLight>), + SetShaderPointLights(Vec<(PointLight, WorldPosition)>), + CreateTexture(Texture), + UseMaterial + { + material_asset: Option<AssetHandle<Material>>, + material_flags: MaterialFlags, + global_light: GlobalLight, + }, + DrawMesh + { + mesh_asset: AssetHandle<Mesh>, + }, + SetPolygonModeConfig(PolygonModeConfig), +} + +bitflags! { + #[derive(Debug)] + pub struct BufferClearMask: u8 { + const COLOR = 1; + const DEPTH = 2; + const STENCIL = 3; + } +} + +/// Renderer command FIFO queue. +/// +/// This component is present in renderer context entities. +#[derive(Debug, Component)] +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) } + } +} + +#[derive(Debug, Component)] +pub struct WindowUsingRendererCtx; + +#[derive(Debug, Component)] +pub struct CtxUsedByWindow; + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a MaterialFlags>, + Option<&'a WorldPosition>, + Option<&'a Scale>, + Option<&'a DrawFlags>, +); + +#[tracing::instrument(skip_all)] +pub fn enqueue_commands( + renderer_ctx_query: Query<( + &mut CommandQueue, + &ObjectStore, + &[Pair<CtxUsedByWindow, Wildcard>], + )>, + renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, + point_light_query: Query<(&PointLight, &WorldPosition)>, + directional_light_query: Query<(&DirectionalLight,)>, + camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, + global_light: Single<GlobalLight>, + assets: Single<Assets>, + mut actions: Actions, +) +{ + let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { + tracing::warn!("No current camera. Nothing will be rendered"); + return; + }; + + for (renderer_ctx_ent_id, (mut command_queue, object_store, used_by_windows)) in + renderer_ctx_query.iter_with_euids() + { + 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(window) = window_ent.get::<Window>() else { + tracing::debug!( + window_entity_id=%window_ent_id, + "Window entity does not have a {} component", + type_name::<Window>() + ); + + actions.remove_components( + renderer_ctx_ent_id, + [Pair::builder() + .relation::<CtxUsedByWindow>() + .target_id(window_ent_id) + .build() + .id()], + ); + + continue; + }; + + let Some(surface_spec) = window_ent.get::<SurfaceSpec>() else { + tracing::debug!( + window_entity_id=%window_ent_id, + "Window entity does not have a {} component", + type_name::<SurfaceSpec>() + ); + continue; + }; + + command_queue.push(Command::MakeCurrent(surface_spec.id)); + + command_queue + .push(Command::UseCamera(camera.clone(), camera_world_pos.clone())); + + command_queue.push(Command::ClearBuffers( + BufferClearMask::COLOR | BufferClearMask::DEPTH, + )); + + for (model, material_flags, world_pos, scale, draw_flags) in &renderable_query + { + let Some(model_spec) = assets.get(&model.spec_asset) else { + continue; + }; + + let Some(mesh_asset) = &model_spec.mesh_asset else { + continue; + }; + + debug_assert!(model_spec.material_names.len() <= 1); + + let model_material_asset = + match find_first_model_material(model_spec, &assets) { + MaterialSearchResult::Found(model_material_asset) => { + Some(model_material_asset.clone()) + } + MaterialSearchResult::NotFound => { + continue; + } + MaterialSearchResult::NoMaterials => None, + }; + + if let Some(model_material_asset) = &model_material_asset { + let Some(model_material) = assets.get(model_material_asset) else { + unreachable!(); + }; + + if let Some(ambient_map) = &model_material.ambient_map { + if assets.get(&ambient_map.asset_handle).is_none() { + continue; + } + } + + if let Some(diffuse_map) = &model_material.diffuse_map { + if assets.get(&diffuse_map.asset_handle).is_none() { + continue; + } + } + + if let Some(specular_map) = &model_material.specular_map { + if assets.get(&specular_map.asset_handle).is_none() { + continue; + } + } + } + + command_queue.push(Command::UseShader); + + command_queue.push(Command::ApplyTransform { + transform: Transform { + position: world_pos + .as_deref() + .cloned() + .unwrap_or_default() + .position, + scale: scale.as_deref().cloned().unwrap_or_default().scale, + }, + window_size: *window.inner_size(), + }); + + command_queue.push(Command::SetShaderDirectionalLights( + directional_light_query + .iter() + .map(|(dir_light,)| dir_light.clone()) + .collect::<Vec<_>>(), + )); + + command_queue.push(Command::SetShaderPointLights( + point_light_query + .iter() + .map(|(point_light, point_light_world_pos)| { + (point_light.clone(), point_light_world_pos.clone()) + }) + .collect::<Vec<_>>(), + )); + + if let Some(model_material_asset) = &model_material_asset { + let Some(model_material) = assets.get(model_material_asset) else { + unreachable!(); + }; + + for texture in [ + &model_material.specular_map, + &model_material.diffuse_map, + &model_material.specular_map, + ] + .into_iter() + .flatten() + { + if !object_store + .contains_with_id(&ObjectId::Asset(texture.asset_handle.id())) + { + command_queue.push(Command::CreateTexture(texture.clone())); + } + } + } + + command_queue.push(Command::UseMaterial { + material_asset: model_material_asset, + material_flags: material_flags + .as_deref() + .cloned() + .unwrap_or_default(), + global_light: global_light.clone(), + }); + + command_queue.push(Command::ActivateShader); + + if let Some(draw_flags) = draw_flags.as_deref() + && draw_flags.polygon_mode_config != PolygonModeConfig::default() + { + command_queue.push(Command::SetPolygonModeConfig( + draw_flags.polygon_mode_config.clone(), + )); + } + + command_queue.push(Command::DrawMesh { mesh_asset: mesh_asset.clone() }); + + if let Some(draw_flags) = draw_flags.as_deref() + && draw_flags.polygon_mode_config != PolygonModeConfig::default() + { + command_queue.push(Command::SetPolygonModeConfig( + PolygonModeConfig::default(), + )); + } + } + + command_queue.push(Command::SwapBuffers(surface_spec.id)); + } + } +} + +enum MaterialSearchResult<'a> +{ + Found(&'a AssetHandle<Material>), + NotFound, + NoMaterials, +} + +fn find_first_model_material<'assets>( + model_spec: &'assets ModelSpec, + assets: &'assets Assets, +) -> MaterialSearchResult<'assets> +{ + let Some(material_name) = model_spec.material_names.first() else { + return MaterialSearchResult::NoMaterials; + }; + + let Some(material_asset) = (match &model_spec.materials { + ModelMaterials::Maps(material_asset_map_assets) => material_asset_map_assets + .iter() + .find_map(|mat_asset_map_asset| { + let mat_asset_map = assets.get(mat_asset_map_asset)?; + + mat_asset_map.assets.get(material_name) + }), + ModelMaterials::Direct(material_assets) => material_assets.get(material_name), + }) else { + return MaterialSearchResult::NotFound; + }; + + if assets.get(material_asset).is_none() { + tracing::trace!("Missing material asset"); + return MaterialSearchResult::NotFound; + } + + MaterialSearchResult::Found(material_asset) +} |
