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