From 76b84cc648ff2bcb2bf2f504e4c5b36d47e72684 Mon Sep 17 00:00:00 2001 From: HampusM Date: Mon, 1 Jun 2026 17:00:40 +0200 Subject: refactor(engine): rename 'renderer' module to 'rendering' --- engine/src/lib.rs | 2 +- engine/src/renderer.rs | 535 ---------- engine/src/renderer/blending.rs | 89 -- engine/src/renderer/main_render_pass.rs | 247 ----- engine/src/renderer/object.rs | 136 --- engine/src/renderer/opengl.rs | 1445 -------------------------- engine/src/renderer/opengl/glutin_compat.rs | 268 ----- engine/src/renderer/opengl/graphics_mesh.rs | 216 ---- engine/src/rendering.rs | 533 ++++++++++ engine/src/rendering/blending.rs | 89 ++ engine/src/rendering/main_render_pass.rs | 239 +++++ engine/src/rendering/object.rs | 136 +++ engine/src/rendering/opengl.rs | 1438 +++++++++++++++++++++++++ engine/src/rendering/opengl/glutin_compat.rs | 268 +++++ engine/src/rendering/opengl/graphics_mesh.rs | 216 ++++ engine/src/shader/default.rs | 2 +- 16 files changed, 2921 insertions(+), 2938 deletions(-) delete mode 100644 engine/src/renderer.rs delete mode 100644 engine/src/renderer/blending.rs delete mode 100644 engine/src/renderer/main_render_pass.rs delete mode 100644 engine/src/renderer/object.rs delete mode 100644 engine/src/renderer/opengl.rs delete mode 100644 engine/src/renderer/opengl/glutin_compat.rs delete mode 100644 engine/src/renderer/opengl/graphics_mesh.rs create mode 100644 engine/src/rendering.rs create mode 100644 engine/src/rendering/blending.rs create mode 100644 engine/src/rendering/main_render_pass.rs create mode 100644 engine/src/rendering/object.rs create mode 100644 engine/src/rendering/opengl.rs create mode 100644 engine/src/rendering/opengl/glutin_compat.rs create mode 100644 engine/src/rendering/opengl/graphics_mesh.rs diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 4b06f5a..d6ca6df 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -33,7 +33,7 @@ pub mod mesh; pub mod model; pub mod projection; pub mod reflection; -pub mod renderer; +pub mod rendering; pub mod shader; pub mod texture; pub mod transform; diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs deleted file mode 100644 index 1814391..0000000 --- a/engine/src/renderer.rs +++ /dev/null @@ -1,535 +0,0 @@ -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::renderer::blending::Config as BlendingConfig; -use crate::renderer::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 blending; -pub mod main_render_pass; -pub mod object; -pub mod opengl; - -static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); - -declare_entity!( - pub PRE_RENDER_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*POST_UPDATE_PHASE) - .build() - ) -); - -declare_entity!( - pub RENDER_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*PRE_RENDER_PHASE) - .build() - ) -); - -declare_entity!( - pub POST_RENDER_PHASE, - (Phase, Pair::builder().relation::().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); - - opengl::Extension::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, - - /// 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, -} - -#[derive(Debug)] -pub struct RenderPass -{ - pub commands: Vec, - pub draw_properties: DrawProperties, -} - -#[derive(Debug, Reflection)] -#[non_exhaustive] -pub enum Command -{ - RemoveSurface(SurfaceId), - MakeCurrent(SurfaceId), - SetSurfaceSize(SurfaceId, Dimens), - ClearBuffers(BufferClearMask), - SwapBuffers(SurfaceId), - CreateShaderProgram(ObjectId, ShaderProgram), - ActivateShader(ObjectId), - SetShaderBinding(ShaderBindingLocation, ShaderBindingValue), - CreateTexture(AssetHandle), - 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, - - 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, - } -} - -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>, - - /// Position (in window coordinates) of the lower left corner of the scissor box. - pub lower_left_corner_pos: Vec2, -} - -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; - } -} - -/// Renderer command FIFO queue. -/// -/// This component is present in renderer context entities. -#[derive(Debug, Sole)] -pub struct CommandQueue -{ - queue: VecDeque, -} - -impl CommandQueue -{ - pub fn push(&mut self, command: Command) - { - self.queue.push_back(command); - } - - pub fn drain(&mut self) -> impl Iterator + 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,)>, - mut command_queue: Single, - mut render_passes: Single, - mut active_draw_props: Local, -) -{ - 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>, - mut command_queue: Single, -) -{ - for evt_match in &observe { - let window_ent = evt_match.get_entity(); - - let Some(window_surface_spec) = window_ent.get::() 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>, - mut command_queue: Single, - 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::() 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, 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, -} diff --git a/engine/src/renderer/blending.rs b/engine/src/renderer/blending.rs deleted file mode 100644 index 9ae2f82..0000000 --- a/engine/src/renderer/blending.rs +++ /dev/null @@ -1,89 +0,0 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Config -{ - pub source_factor: Factor, - pub destination_factor: Factor, - pub equation: Equation, -} - -impl Default for Config -{ - fn default() -> Self - { - Self { - source_factor: Factor::One, - destination_factor: Factor::Zero, - equation: Equation::default(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[non_exhaustive] -pub enum Factor -{ - /// Factor will be the RGBA color `(0,0,0,0)` - Zero, - - /// Factor will be the RGBA color `(1,1,1,1)` - One, - - /// Factor will be the source color - SrcColor, - - /// Factor will be the RGBA color `(1,1,1,1) - source color` - OneMinusSrcColor, - - /// Factor will be the destination color - DstColor, - - /// Factor will be the RGBA color `(1,1,1,1) - destination color` - OneMinusDstColor, - - /// Factor will be the alpha component of the source color. - SrcAlpha, - - /// Factor will be the RGBA color `(1,1,1,1) - source color alpha` - OneMinusSrcAlpha, - - /// Factor will be the alpha component of the destination color. - DstAlpha, - - /// Factor will be the RGBA color `(1,1,1,1) - destination color alpha` - OneMinusDstAlpha, - - /// Factor will be the constant color - ConstantColor, - - /// Factor will be the RGBA color `(1,1,1,1) - constant color` - OneMinusConstantColor, - - /// Factor will be the alpha component of the constant color. - ConstantAlpha, - - /// Factor will be the RGBA color `(1,1,1,1) - constant color alpha` - OneMinusConstantAlpha, -} - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub enum Equation -{ - /// The destination color and source color is added to each other in the blend - /// function - #[default] - Add, - - /// The destination color is subtracted from the source color in the blend function - Subtract, - - /// The source color is subtracted from the destination color in the blend function - ReverseSubtract, - - /// The blend function will take the component-wise minimum of the destination color - /// and the source color - Min, - - /// The blend function will take the component-wise maximum of the destination color - /// and the source color - Max, -} diff --git a/engine/src/renderer/main_render_pass.rs b/engine/src/renderer/main_render_pass.rs deleted file mode 100644 index 0926d85..0000000 --- a/engine/src/renderer/main_render_pass.rs +++ /dev/null @@ -1,247 +0,0 @@ -use crate::asset::Assets; -use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; -use crate::ecs::query::term::{With, Without}; -use crate::ecs::sole::Single; -use crate::ecs::Query; -use crate::model::{MaterialSearchResult, Model}; -use crate::renderer::object::{Id as RendererObjectId, Store as RendererObjectStore}; -use crate::renderer::{ - BufferClearMask as RendererBufferClearMask, - Command as RendererCommand, - DrawMeshOptions as RendererDrawMeshOptions, - DrawProperties as RendererDrawProperties, - DrawPropertiesUpdateFlags as RendererDrawPropertiesUpdateFlags, - MeshUsage as RendererMeshUsage, - PendingShaderBindings, - RenderPass, - RenderPasses as RendererRenderPasses, - SurfaceSpec, -}; -use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; -use crate::shader::{ - Context as ShaderContext, - ModuleSource as ShaderModuleSource, - Shader, -}; -use crate::texture::{Texture, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL}; -use crate::windowing::window::Window; - -type RenderableEntity<'a> = ( - &'a Model, - Option<&'a DrawFlags>, - Option<&'a Shader>, - Option<&'a mut PendingShaderBindings>, -); - -#[tracing::instrument(skip_all)] -pub fn add_main_render_passes( - renderable_query: Query, (Without,)>, - window_surface_spec_query: Query<(&SurfaceSpec,), (With,)>, - assets: Single, - shader_context: Single, - mut render_passes: Single, - mut object_store: Single, -) -{ - let Some(default_shader_asset) = assets - .get_handle_to_loaded::(DEFAULT_SHADER_ASSET_LABEL.clone()) - else { - tracing::error!("Default shader asset is not loaded"); - return; - }; - - let render_pass = render_passes.passes.push_front_mut(RenderPass { - commands: Vec::with_capacity(30), - draw_properties: RendererDrawProperties::default(), - }); - - for (surface_spec,) in &window_surface_spec_query { - render_pass - .commands - .push(RendererCommand::MakeCurrent(surface_spec.id)); - - let default_texture_asset = assets - .get_handle_to_loaded::(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) - .expect("Not possible"); - - if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( - default_texture_asset.id(), - )) { - object_store - .insert_pending(RendererObjectId::Asset(default_texture_asset.id())); - - render_pass - .commands - .push(RendererCommand::CreateTexture(default_texture_asset)); - } - - render_pass.commands.push(RendererCommand::ClearBuffers( - RendererBufferClearMask::COLOR | RendererBufferClearMask::DEPTH, - )); - - for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query - { - let shader_asset = match &shader { - Some(shader) => &shader.asset_handle, - None => &default_shader_asset, - }; - - let Some(pending_shader_bindings) = pending_shader_bindings.as_mut() else { - continue; - }; - - if pending_shader_bindings.bindings.is_empty() - && pending_shader_bindings.surface_specific_bindings.is_empty() - { - continue; - } - - let Some(model_spec) = assets.get(&model.spec_asset) else { - continue; - }; - - let Some(mesh_asset) = &model_spec.mesh_asset else { - continue; - }; - - if assets.get(mesh_asset).is_none() { - continue; - } - - debug_assert!(model_spec.material_names.len() <= 1); - - let model_material_asset = match model_spec.find_first_material(&assets) { - MaterialSearchResult::Found(model_material_asset) => { - model_material_asset.clone() - // Some(model_material_asset.clone()) - } - MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => { - // MaterialSearchResult::NotFound => { - continue; - } // MaterialSearchResult::NoMaterials => None, - }; - - if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( - shader_asset.id(), - )) { - let Some(shader_program) = shader_context.get_program(&shader_asset.id()) - else { - tracing::error!( - "Shader context doesn't have a program for shader asset {:?}", - assets.get_label(&shader_asset) - ); - continue; - }; - - object_store.insert_pending(RendererObjectId::Asset(shader_asset.id())); - - render_pass - .commands - .push(RendererCommand::CreateShaderProgram( - RendererObjectId::Asset(shader_asset.id()), - shader_program.clone(), - )); - } - - render_pass.commands.push(RendererCommand::ActivateShader( - RendererObjectId::Asset(shader_asset.id()), - )); - - let Some(model_material) = assets.get(&model_material_asset) else { - // TODO: Handle this case since it may occur - unreachable!(); - }; - - for texture_asset in [ - &model_material.ambient_map, - &model_material.diffuse_map, - &model_material.specular_map, - ] - .into_iter() - .flatten() - { - if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( - texture_asset.id(), - )) { - object_store - .insert_pending(RendererObjectId::Asset(texture_asset.id())); - - render_pass - .commands - .push(RendererCommand::CreateTexture(texture_asset.clone())); - } - } - - for (shader_binding_loc, shader_binding_val) in - &pending_shader_bindings.bindings - { - render_pass.commands.push(RendererCommand::SetShaderBinding( - shader_binding_loc.clone(), - shader_binding_val.clone(), - )); - } - - for (shader_binding_surface_id, shader_binding_loc, shader_binding_val) in - &pending_shader_bindings.surface_specific_bindings - { - if *shader_binding_surface_id != surface_spec.id { - continue; - } - - render_pass.commands.push(RendererCommand::SetShaderBinding( - shader_binding_loc.clone(), - shader_binding_val.clone(), - )); - } - - if let Some(draw_flags) = draw_flags.as_deref().and_then(|draw_flags| { - if draw_flags.polygon_mode_config != PolygonModeConfig::default() { - Some(draw_flags) - } else { - None - } - }) { - render_pass - .commands - .push(RendererCommand::UpdateDrawProperties( - RendererDrawProperties { - polygon_mode_config: draw_flags.polygon_mode_config.clone(), - ..Default::default() - }, - RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, - )); - } - - if !object_store - .contains_maybe_pending_with_id(&RendererObjectId::Asset(mesh_asset.id())) - { - object_store.insert_pending(RendererObjectId::Asset(mesh_asset.id())); - - render_pass.commands.push(RendererCommand::CreateMesh { - obj_id: RendererObjectId::Asset(mesh_asset.id()), - mesh: None, - usage: RendererMeshUsage::Static, - }); - } - - render_pass.commands.push(RendererCommand::DrawMesh( - RendererObjectId::Asset(mesh_asset.id()), - RendererDrawMeshOptions::default(), - )); - - if draw_flags.as_deref().is_some_and(|draw_flags| { - draw_flags.polygon_mode_config != PolygonModeConfig::default() - }) { - render_pass - .commands - .push(RendererCommand::UpdateDrawProperties( - RendererDrawProperties { - polygon_mode_config: PolygonModeConfig::default(), - ..Default::default() - }, - RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, - )); - } - } - } -} diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs deleted file mode 100644 index e10011d..0000000 --- a/engine/src/renderer/object.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::collections::HashMap; -use std::sync::atomic::{AtomicU64, Ordering}; - -use crate::asset::Id as AssetId; -use crate::ecs::Sole; - -pub type RawValue = u32; - -/// Renderer object ID. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Id -{ - Asset(AssetId), - Sequential(SequentialId), -} - -impl Id -{ - pub fn new_sequential() -> Self - { - static NEXT_SEQUENTIAL_ID: AtomicU64 = AtomicU64::new(0); - - Self::Sequential(SequentialId( - NEXT_SEQUENTIAL_ID.fetch_add(1, Ordering::Relaxed), - )) - } - - pub fn into_asset_id(self) -> Option - { - match self { - Self::Asset(asset_id) => Some(asset_id), - Self::Sequential(_) => None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SequentialId(u64); - -/// Renderer object store. -#[derive(Debug, Default, Sole)] -pub struct Store -{ - objects: HashMap>, -} - -impl Store -{ - pub fn get_obj(&self, id: &Id) -> Option<&Object> - { - self.objects.get(id).and_then(|obj| obj.as_ref()) - } - - pub fn get_texture_obj(&self, id: &Id) -> Option<&Object> - { - let obj = self.get_obj(id)?; - - if !matches!(obj.kind(), Kind::Texture) { - return None; - } - - Some(obj) - } - - pub fn get_shader_program_obj(&self, id: &Id) -> Option<&Object> - { - let obj = self.get_obj(id)?; - - if !matches!(obj.kind(), Kind::ShaderProgram) { - return None; - } - - Some(obj) - } - - pub fn contains_maybe_pending_with_id(&self, id: &Id) -> bool - { - self.objects.contains_key(id) - } - - pub fn contains_non_pending_with_id(&self, id: &Id) -> bool - { - self.objects.get(id).and_then(|obj| obj.as_ref()).is_some() - } - - pub fn insert(&mut self, id: Id, object: Object) - { - self.objects.insert(id, Some(object)); - } - - pub fn insert_pending(&mut self, id: Id) - { - self.objects.insert(id, None); - } - - pub fn remove(&mut self, id: &Id) -> Option> - { - self.objects.remove(id) - } -} - -/// Renderer object. -#[derive(Debug, Clone)] -pub struct Object -{ - raw: RawValue, - kind: Kind, -} - -impl Object -{ - pub fn from_raw(raw: RawValue, kind: Kind) -> Self - { - Self { raw, kind } - } - - pub fn as_raw(&self) -> RawValue - { - self.raw - } - - pub fn kind(&self) -> Kind - { - self.kind - } -} - -/// Renderer object kind. -#[derive(Debug, Clone, Copy)] -#[non_exhaustive] -pub enum Kind -{ - Texture, - ShaderProgram, - ImplementationSpecific, -} diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs deleted file mode 100644 index d77956e..0000000 --- a/engine/src/renderer/opengl.rs +++ /dev/null @@ -1,1445 +0,0 @@ -//! OpenGL renderer. - -use std::borrow::Cow; -use std::collections::HashMap; - -use glutin::config::Config as GlutinConfig; -use glutin::display::GetGlDisplay; -use glutin::error::Error as GlutinError; -use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext}; -use glutin::surface::{ - GlSurface as _, - Surface as GlutinSurface, - WindowSurface as GlutinWindowSurface, -}; -use opengl_bindings::blending::{ - configure as gl_blending_configure, - Configuration as GlBlendingConfig, - Equation as GlBlendingEquation, - Factor as GlBlendingFactor, -}; -use opengl_bindings::debug::{ - set_debug_message_callback, - set_debug_message_control, - MessageIdsAction, - MessageSeverity, - MessageSource, - MessageType, - SetDebugMessageControlError as GlSetDebugMessageControlError, -}; -use opengl_bindings::misc::{ - clear_buffers, - define_scissor_box as gl_define_scissor_box, - enable, - get_viewport as gl_get_viewport, - set_enabled, - set_viewport as gl_set_viewport, - BufferClearMask as GlBufferClearMask, - Capability, -}; -use opengl_bindings::shader::{ - Error as GlShaderError, - Kind as ShaderKind, - Program as GlShaderProgram, - Shader as GlShader, - // UniformLocation as GlUniformLocation, -}; -use opengl_bindings::texture::{ - ColorSpace as GlTextureColorSpace, - Filtering as GlTextureFiltering, - GenerateError as GlTextureGenerateError, - PixelDataFormat as GlTexturePixelDataFormat, - Texture as GlTexture, - Wrapping as GlTextureWrapping, -}; -use opengl_bindings::vertex_array::{ - DrawError as GlDrawError, - PrimitiveKind, - VertexArray, -}; -use opengl_bindings::{ - MakeContextCurrentError as GlMakeContextCurrentError, - MaybeCurrentContextWithFns, -}; -use raw_window_handle::WindowHandle; -use safer_ffi::layout::ReprC; -use zerocopy::{Immutable, IntoBytes}; - -use crate::asset::{Assets, Handle as AssetHandle}; -use crate::data_types::dimens::Dimens; -use crate::ecs::actions::Actions; -use crate::ecs::query::term::Without; -use crate::ecs::sole::Single; -use crate::ecs::{Component, Query, Sole}; -use crate::image::{ColorType as ImageColorType, Image}; -use crate::matrix::Matrix; -use crate::reflection::EnumReflectionExt; -use crate::renderer::blending::{Equation as BlendingEquation, Factor as BlendingFactor}; -use crate::renderer::object::{ - Id as RendererObjectId, - Kind as RendererObjectKind, - Object as RendererObject, - RawValue as RendererObjectRawValue, - Store as RendererObjectStore, -}; -use crate::renderer::opengl::glutin_compat::{ - DisplayBuilder, - Error as GlutinCompatError, -}; -use crate::renderer::opengl::graphics_mesh::GraphicsMesh; -use crate::renderer::{ - BufferClearMask, - Command as RendererCommand, - CommandQueue as RendererCommandQueue, - DrawMeshOptions, - DrawPropertiesUpdateFlags, - GraphicsProperties, - SurfaceId, - SurfaceSpec, - POST_RENDER_PHASE, - RENDER_PHASE, -}; -use crate::shader::cursor::BindingValue as ShaderBindingValue; -use crate::shader::{ - Context as ShaderContext, - Error as ShaderError, - Program as ShaderProgram, - Stage as ShaderStage, -}; -use crate::texture::{ - Filtering as TextureFiltering, - Properties as TextureProperties, - Texture, - Wrapping as TextureWrapping, -}; -use crate::util::OptionExt; -use crate::vector::{Vec2, Vec3}; -use crate::windowing::window::{ - Closed as WindowClosed, - CreationAttributes as WindowCreationAttributes, - CreationReady, - Window, -}; -use crate::windowing::Context as WindowingContext; - -mod glutin_compat; -mod graphics_mesh; - -#[derive(Debug, Component)] -struct WindowGlConfig -{ - gl_config: GlutinConfig, -} - -#[derive(Sole, Default)] -struct GraphicsContext -{ - gl_context: Option, - surfaces: HashMap, - shader_uniform_buffer_objs: - HashMap>>, - objects: HashMap, - next_object_key: RendererObjectRawValue, -} - -#[derive(Debug)] -struct GraphicsContextSurface -{ - window_surface: GlutinSurface, - size: Dimens, -} - -#[derive(Debug)] -enum GraphicsContextObject -{ - Mesh - { - mesh: GraphicsMesh, - compatible_shader_program_obj_id: RendererObjectId, - }, -} - -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct Extension {} - -impl crate::ecs::extension::Extension for Extension -{ - fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) - { - collector.add_system(*RENDER_PHASE, handle_commands); - - collector.add_system(*POST_RENDER_PHASE, prepare_windows); - collector.add_system(*POST_RENDER_PHASE, init_window_graphics); - - let _ = collector.add_sole(GraphicsContext::default()); - } -} - -fn prepare_windows( - window_query: Query< - (Option<&Window>, &mut WindowCreationAttributes), - ( - Without, - Without, - Without, - ), - >, - windowing_context: Single, - graphics_props: Single, - mut actions: Actions, -) -{ - let Some(display_handle) = windowing_context.display_handle() else { - return; - }; - - for (window_ent_id, (window, mut window_creation_attrs)) in - window_query.iter_with_euids() - { - tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); - - let mut glutin_config_template_builder = - glutin::config::ConfigTemplateBuilder::new(); - - if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { - glutin_config_template_builder = glutin_config_template_builder - .with_multisampling(multisampling_sample_cnt); - } - - let window_handle = match window - .as_ref() - .map(|window| unsafe { - windowing_context.get_window_as_handle(&window.wid()) - }) - .flatten() - .transpose() - { - Ok(window_handle) => window_handle, - Err(err) => { - tracing::error!("Failed to get window handle: {err}"); - continue; - } - }; - - let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new() - .with_window_attributes(window_creation_attrs.clone()) - .build( - window_handle, - &display_handle, - glutin_config_template_builder, - |mut cfgs| cfgs.next(), - ) { - Ok((new_window_creation_attrs, gl_config)) => { - (new_window_creation_attrs, gl_config) - } - Err(GlutinCompatError::WindowRequired) => { - actions.add_components(window_ent_id, (CreationReady,)); - continue; - } - Err(err) => { - tracing::error!("Failed to create platform graphics display: {err}"); - continue; - } - }; - - *window_creation_attrs = new_window_creation_attrs; - - actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); - - if window.is_none() { - actions.add_components(window_ent_id, (CreationReady,)); - } - } -} - -#[tracing::instrument(skip_all)] -fn init_window_graphics( - window_query: Query<(&Window, &WindowGlConfig), (Without,)>, - windowing_context: Single, - graphics_props: Single, - mut graphics_ctx: Single, - mut actions: Actions, -) -{ - for (window_ent_id, (window, window_gl_config)) in window_query.iter_with_euids() { - tracing::info!( - window_entity_id=%window_ent_id, - window_title=&*window.title, - "Initializing graphics for window" - ); - - let display = window_gl_config.gl_config.display(); - - let window_handle = - match unsafe { windowing_context.get_window_as_handle(&window.wid()) } - .transpose() - { - Ok(Some(window_handle)) => window_handle, - Ok(None) => { - tracing::error!( - wid = ?window.wid(), - entity_id = %window_ent_id, - "Windowing context does not contain window" - ); - continue; - } - Err(err) => { - tracing::error!("Failed to get window handle: {err}"); - continue; - } - }; - - let Some(window_inner_size) = window.inner_size().try_into_nonzero() else { - tracing::error!( - "Cannot create a surface for a window with a width/height of 0", - ); - continue; - }; - - let window_surface = match unsafe { - display.create_window_surface( - &window_gl_config.gl_config, - &glutin::surface::SurfaceAttributesBuilder::< - glutin::surface::WindowSurface, - >::new() - .build( - window_handle.as_raw(), - window_inner_size.width, - window_inner_size.height, - ), - ) - } { - Ok(window_surface) => window_surface, - Err(err) => { - tracing::error!("Failed to create window surface: {err}"); - continue; - } - }; - - let gl_context = match graphics_ctx.gl_context.get_or_try_insert_with_fn(|| { - create_gl_context( - &window_gl_config.gl_config, - &graphics_props, - window_handle, - &window_surface, - ) - }) { - Ok(gl_context) => gl_context, - Err(err) => { - tracing::error!("Failed to create GL context: {err}"); - continue; - } - }; - - if let Err(err) = gl_context.make_current(&window_surface) { - tracing::error!("Failed to make GL context current: {err}"); - continue; - }; - - if let Err(err) = gl_set_viewport( - &gl_context, - &Vec2 { x: 0, y: 0 }.into(), - &window.inner_size().clone().into(), - ) { - tracing::error!("Failed to set viewport: {err}"); - } - - set_enabled( - &gl_context, - Capability::DepthTest, - graphics_props.depth_test, - ); - - set_enabled( - &gl_context, - Capability::MultiSample, - graphics_props.multisampling_sample_cnt.is_some(), - ); - - if graphics_props.debug { - enable(&gl_context, Capability::DebugOutput); - enable(&gl_context, Capability::DebugOutputSynchronous); - - set_debug_message_callback(&gl_context, opengl_debug_message_cb); - - match set_debug_message_control( - &gl_context, - None, - None, - None, - &[], - MessageIdsAction::Disable, - ) { - Ok(()) => {} - Err(GlSetDebugMessageControlError::TooManyIds { - id_cnt: _, - max_id_cnt: _, - }) => { - unreachable!() // No ids are given - } - } - } - - let surface_id = SurfaceId::new_unique(); - - actions.add_components(window_ent_id, (SurfaceSpec { id: surface_id },)); - - graphics_ctx.surfaces.insert( - surface_id, - GraphicsContextSurface { - window_surface, - size: window.inner_size().clone(), - }, - ); - } -} - -#[tracing::instrument(skip_all)] -fn handle_commands( - mut graphics_ctx: Single, - mut object_store: Single, - mut command_queue: Single, - assets: Single, - shader_context: Single, -) -{ - let GraphicsContext { - ref gl_context, - ref mut surfaces, - ref mut shader_uniform_buffer_objs, - objects: ref mut graphics_ctx_objects, - next_object_key: ref mut next_graphics_ctx_object_key, - } = *graphics_ctx; - - let Some(gl_context) = gl_context else { - return; - }; - - let mut activated_gl_shader_program: Option<(RendererObjectId, GlShaderProgram)> = - None; - - for command in command_queue.drain() { - let tracing_span = tracing::info_span!( - "handle_cmd", - command = %command.get_variant_reflection().name, - ); - let _tracing_span_enter = tracing_span.enter(); - - match command { - RendererCommand::RemoveSurface(surface_id) => { - let Some(surface) = surfaces.remove(&surface_id) else { - tracing::error!(surface_id=?surface_id, "Surface does not exist"); - continue; - }; - - if surface.window_surface.is_current(gl_context.context()) { - if let Err(err) = gl_context.context().make_not_current_in_place() { - tracing::error!("Failed to make GL context not current: {err}"); - } - - if let Err(err) = gl_context.make_current_surfaceless() { - tracing::error!("Failed to make GL context current: {err}"); - } - } - - drop(surface); - } - RendererCommand::MakeCurrent(surface_id) => { - let Some(surface) = surfaces.get(&surface_id) else { - tracing::error!(surface_id=?surface_id, "Surface does not exist"); - continue; - }; - - if let Err(err) = gl_context.make_current(&surface.window_surface) { - tracing::error!("Failed to make graphics context current: {err}"); - continue; - } - - if let Err(err) = gl_set_viewport( - gl_context, - &Vec2 { x: 0, y: 0 }.into(), - &surface.size.into(), - ) { - tracing::error!("Failed to set viewport: {err}"); - } - } - RendererCommand::SetSurfaceSize(surface_id, new_surface_size) => { - let Some(surface) = surfaces.get_mut(&surface_id) else { - tracing::error!(surface_id=?surface_id, "Surface does not exist"); - continue; - }; - - surface.size = new_surface_size; - - if !surface.window_surface.is_current(gl_context.context()) { - continue; - } - - if let Err(err) = gl_set_viewport( - gl_context, - &Vec2 { x: 0, y: 0 }.into(), - &surface.size.into(), - ) { - tracing::error!("Failed to set viewport: {err}"); - } - } - RendererCommand::ClearBuffers(buffer_clear_mask) => { - let mut clear_mask = GlBufferClearMask::empty(); - - clear_mask.set( - GlBufferClearMask::COLOR, - buffer_clear_mask.contains(BufferClearMask::COLOR), - ); - - clear_mask.set( - GlBufferClearMask::DEPTH, - buffer_clear_mask.contains(BufferClearMask::DEPTH), - ); - - clear_mask.set( - GlBufferClearMask::STENCIL, - buffer_clear_mask.contains(BufferClearMask::STENCIL), - ); - - clear_buffers(gl_context, clear_mask); - } - RendererCommand::SwapBuffers(surface_id) => { - let Some(surface) = surfaces.get(&surface_id) else { - tracing::error!(surface_id=?surface_id, "Surface does not exist"); - continue; - }; - - if let Err(err) = - surface.window_surface.swap_buffers(gl_context.context()) - { - tracing::error!("Failed to swap buffers: {err}"); - } - } - RendererCommand::CreateShaderProgram( - shader_program_obj_id, - shader_program, - ) => { - if object_store.contains_non_pending_with_id(&shader_program_obj_id) { - tracing::error!( - shader_program_object_id=?shader_program_obj_id, - shader_asset_label=?shader_program_obj_id - .into_asset_id() - .and_then(|asset_id| assets.get_label_by_id(asset_id)), - "Object store already contains a object with this ID" - ); - continue; - } - - let gl_shader_program = - match create_shader_program(gl_context, &shader_program) { - Ok(gl_shader_program) => gl_shader_program, - Err(err) => { - tracing::error!("Failed to create shader program: {err}"); - continue; - } - }; - - object_store.insert( - shader_program_obj_id, - RendererObject::from_raw( - gl_shader_program.into_raw(), - RendererObjectKind::ShaderProgram, - ), - ); - } - RendererCommand::ActivateShader(shader_program_obj_id) => { - let Some(shader_program_obj) = - object_store.get_shader_program_obj(&shader_program_obj_id) - else { - tracing::error!("Shader object does not exist or has a wrong kind"); - continue; - }; - - let gl_shader_program = - GlShaderProgram::from_raw(shader_program_obj.as_raw()); - - gl_shader_program.activate(gl_context); - - activated_gl_shader_program = - Some((shader_program_obj_id, gl_shader_program)); - } - RendererCommand::SetShaderBinding(binding_location, binding_value) => { - let Some((activated_gl_shader_program_obj_id, _)) = - &activated_gl_shader_program - else { - tracing::error!("No shader program is activated"); - continue; - }; - - if let ShaderBindingValue::Texture(texture_asset) = &binding_value { - let Some(texture_obj) = object_store - .get_texture_obj(&RendererObjectId::Asset(texture_asset.id())) - else { - tracing::error!( - "Texture {:?} does not exist in renderer object store", - assets.get_label(texture_asset) - ); - continue; - }; - - let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); - - gl_texture - .bind_to_texture_unit(gl_context, binding_location.binding_index); - - // gl_shader_program.set_uniform_at_location( - // curr_gl_ctx, - // GlUniformLocation::from_number( - // binding_location.binding_index as i32, - // ), - // &binding_location.binding_index, - // ); - - continue; - } - - let binding_index = binding_location.binding_index; - - let uniform_buffer_objs = shader_uniform_buffer_objs - .entry(*activated_gl_shader_program_obj_id) - .or_default(); - - let uniform_buffer = - uniform_buffer_objs.entry(binding_index).or_insert_with(|| { - let uniform_buf = - opengl_bindings::buffer::Buffer::::new(gl_context); - - uniform_buf - .init( - gl_context, - binding_location.binding_size, - opengl_bindings::buffer::Usage::Dynamic, - ) - .unwrap(); - - uniform_buf - }); - - // The index into the uniform buffer binding target is for whatever - // shader program is currently bound so the uniform buffer object has - // to be re-bound so that a uniform buffer from another shader isn't - // used - uniform_buffer.bind_to_indexed_target( - gl_context, - opengl_bindings::buffer::BindingTarget::UniformBuffer, - binding_index as u32, - ); - - let fvec3_value; - - uniform_buffer - .store_at_byte_offset( - gl_context, - binding_location.byte_offset, - match binding_value { - ShaderBindingValue::Uint(ref value) => value.as_bytes(), - ShaderBindingValue::Int(ref value) => value.as_bytes(), - ShaderBindingValue::Float(ref value) => value.as_bytes(), - ShaderBindingValue::FVec3(value) => { - fvec3_value = CF32Vec3::from(value); - - fvec3_value.as_bytes() - } - ShaderBindingValue::FMat4x4(ref value) => { - value.items().as_bytes() - } - ShaderBindingValue::Texture(_) => unreachable!(), - }, - ) - .unwrap(); - } - RendererCommand::CreateTexture(texture_asset) => { - if let Err(err) = create_texture_object( - gl_context, - &mut object_store, - &assets, - &texture_asset, - ) { - tracing::error!("Failed to create texture object: {err}"); - } - } - RendererCommand::CreateMesh { - obj_id: mesh_object_id, - mesh, - usage: mesh_usage, - } => { - if object_store.contains_non_pending_with_id(&mesh_object_id) { - tracing::error!( - mesh_object_id=?mesh_object_id, - mesh_asset_label=?mesh_object_id - .into_asset_id() - .and_then(|asset_id| assets.get_label_by_id(asset_id)), - "Object store already contains a object with this ID" - ); - continue; - } - - let Some((RendererObjectId::Asset(curr_shader_program_asset_id), _)) = - &activated_gl_shader_program - else { - tracing::error!("No shader program is activated"); - continue; - }; - - let curr_shader_program_metadata = shader_context - .get_program_metadata(curr_shader_program_asset_id) - .expect("Not possible"); - - let Some(vertex_desc) = &curr_shader_program_metadata.vertex_desc else { - tracing::error!( - "Current shader program does not have a vertex description" - ); - continue; - }; - - let key = *next_graphics_ctx_object_key; - - let mesh = match mesh_object_id { - RendererObjectId::Asset(mesh_asset_id) => match mesh.as_ref() { - Some(mesh) => mesh, - None => { - let Some(mesh) = - assets.get(&AssetHandle::from_id(mesh_asset_id)) - else { - tracing::error!( - asset_id=?mesh_asset_id, - "Mesh asset does not exist" - ); - continue; - }; - - mesh - } - }, - RendererObjectId::Sequential(_) => { - let Some(mesh) = mesh.as_ref() else { - tracing::error!( - object_id=?mesh_object_id, - "Object ID is sequential but no mesh data is given" - ); - continue; - }; - - mesh - } - }; - - let graphics_mesh = match GraphicsMesh::new( - gl_context, - &mesh, - mesh_usage, - &vertex_desc, - ) { - Ok(graphics_mesh) => graphics_mesh, - Err(err) => { - tracing::error!("Failed to create mesh: {err}"); - continue; - } - }; - - graphics_ctx_objects.insert( - key, - GraphicsContextObject::Mesh { - mesh: graphics_mesh, - compatible_shader_program_obj_id: RendererObjectId::Asset( - *curr_shader_program_asset_id, - ), - }, - ); - - object_store.insert( - mesh_object_id, - RendererObject::from_raw( - key, - RendererObjectKind::ImplementationSpecific, - ), - ); - - *next_graphics_ctx_object_key += 1; - } - RendererCommand::UpdateMesh { - obj_id: mesh_object_id, - mesh, - usage: mesh_usage, - } => { - let Some(mesh_graphics_ctx_obj_key) = object_store - .get_obj(&mesh_object_id) - .map(|obj| obj.as_raw()) - else { - tracing::error!( - object_id=?mesh_object_id, - "Object store does not contain a mesh object with this ID" - ); - continue; - }; - - let Some(mesh_graphics_ctx_obj) = - graphics_ctx_objects.get_mut(&mesh_graphics_ctx_obj_key) - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context does not contain a mesh object with this key" - ); - continue; - }; - - #[allow(irrefutable_let_patterns)] - let GraphicsContextObject::Mesh { - mesh: graphics_mesh, - compatible_shader_program_obj_id: _, - } = mesh_graphics_ctx_obj - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context object with this key is not a mesh" - ); - continue; - }; - - if let Err(err) = graphics_mesh.update(gl_context, &mesh, mesh_usage) { - tracing::error!("Failed to update mesh: {err}"); - } - } - RendererCommand::RemoveMesh(mesh_object_id) => { - let Some(mesh_graphics_ctx_obj_key) = object_store - .remove(&mesh_object_id) - .flatten() - .map(|obj| obj.as_raw()) - else { - tracing::error!( - object_id=?mesh_object_id, - "Object store does not contain a mesh object with this ID" - ); - continue; - }; - - let Some(mesh_graphics_ctx_obj) = - graphics_ctx_objects.remove(&mesh_graphics_ctx_obj_key) - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context does not contain a mesh object with this key" - ); - continue; - }; - - #[allow(irrefutable_let_patterns)] - let GraphicsContextObject::Mesh { mesh: mut graphics_mesh, .. } = - mesh_graphics_ctx_obj - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context object with this key is not a mesh" - ); - continue; - }; - - graphics_mesh.destroy(gl_context); - } - RendererCommand::DrawMesh(mesh_object_id, draw_mesh_opts) => { - let Some(mesh_graphics_ctx_obj_key) = object_store - .get_obj(&mesh_object_id) - .map(|obj| obj.as_raw()) - else { - tracing::error!( - object_id=?mesh_object_id, - "Object store does not contain a mesh object with this ID" - ); - continue; - }; - - let Some(mesh_graphics_ctx_obj) = - graphics_ctx_objects.get(&mesh_graphics_ctx_obj_key) - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context does not contain a mesh object with this key" - ); - continue; - }; - - #[allow(irrefutable_let_patterns)] - let GraphicsContextObject::Mesh { - mesh: graphics_mesh, - compatible_shader_program_obj_id, - } = mesh_graphics_ctx_obj - else { - tracing::error!( - object_id=?mesh_object_id, - key=mesh_graphics_ctx_obj_key, - "Graphics context object with this key is not a mesh" - ); - continue; - }; - - if Some(compatible_shader_program_obj_id) - != activated_gl_shader_program.as_ref().map( - |(activated_gl_shader_program_obj_id, _)| { - activated_gl_shader_program_obj_id - }, - ) - { - tracing::error!(concat!( - "Activated shader program is not the ", - "compatible shader program of the mesh" - )); - continue; - } - - if let Err(err) = draw_mesh(gl_context, graphics_mesh, &draw_mesh_opts) { - tracing::error!("Failed to draw mesh: {err}"); - }; - } - RendererCommand::UpdateDrawProperties( - draw_props, - draw_props_update_flags, - ) => { - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) - { - opengl_bindings::misc::set_polygon_mode( - gl_context, - draw_props.polygon_mode_config.face, - draw_props.polygon_mode_config.mode, - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) - { - set_enabled( - gl_context, - Capability::Blend, - draw_props.blending_enabled, - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) - { - gl_blending_configure( - gl_context, - GlBlendingConfig::default() - .with_source_factor(blending_factor_to_gl( - draw_props.blending_config.source_factor, - )) - .with_destination_factor(blending_factor_to_gl( - draw_props.blending_config.destination_factor, - )) - .with_equation(blending_equation_to_gl( - draw_props.blending_config.equation, - )), - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) - { - set_enabled( - gl_context, - Capability::DepthTest, - draw_props.depth_test_enabled, - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) - { - set_enabled( - gl_context, - Capability::ScissorTest, - draw_props.scissor_test_enabled, - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) - { - gl_define_scissor_box( - gl_context, - draw_props.scissor_box.lower_left_corner_pos.into(), - draw_props - .scissor_box - .size - .unwrap_or_else(|| { - let (_, viewport_size) = gl_get_viewport(gl_context); - - Dimens:: { - width: viewport_size - .width - .try_into() - .expect("Viewport width too large"), - height: viewport_size - .height - .try_into() - .expect("Viewport height too large"), - } - }) - .into(), - ); - } - - if draw_props_update_flags - .contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) - { - set_enabled( - gl_context, - Capability::CullFace, - draw_props.face_culling_enabled, - ); - } - } - } - } -} - -fn create_gl_context( - gl_config: &GlutinConfig, - graphics_props: &GraphicsProperties, - window_handle: WindowHandle<'_>, - surface: &GlutinSurface, -) -> Result -{ - let display = gl_config.display(); - - let glutin_context = unsafe { - display.create_context( - gl_config, - &glutin::context::ContextAttributesBuilder::new() - .with_debug(graphics_props.debug) - .build(Some(window_handle.as_raw())), - ) - } - .map_err(CreateGlContextError::CreateGlutinContext)?; - - MaybeCurrentContextWithFns::new(glutin_context, &surface) - .map_err(CreateGlContextError::MakeContextCurrent) -} - -#[derive(Debug, thiserror::Error)] -enum CreateGlContextError -{ - #[error("Glutin context creation failed")] - CreateGlutinContext(#[source] GlutinError), - - #[error("Making GL context current failed")] - MakeContextCurrent(#[source] GlMakeContextCurrentError), -} - -#[tracing::instrument(skip_all)] -fn create_texture_object( - curr_gl_ctx: &MaybeCurrentContextWithFns, - renderer_object_store: &mut RendererObjectStore, - assets: &Assets, - texture_asset: &AssetHandle, -) -> Result<(), GlTextureGenerateError> -{ - let object_id = RendererObjectId::Asset(texture_asset.id()); - - if renderer_object_store.contains_non_pending_with_id(&object_id) { - tracing::error!( - texture_object_id=?object_id, - texture_asset_label=?assets.get_label(texture_asset), - "Renderer object store already contains object with this ID" - ); - return Ok(()); - } - - let Some(texture) = assets.get(&texture_asset) else { - tracing::error!("Texture asset is not loaded",); - return Ok(()); - }; - - let texture_image = match texture.image.color_type() { - ImageColorType::Rgb8 if (texture.image.dimensions().width * 3) % 4 != 0 => { - // The texture will be corrupted if the alignment of each horizontal line of - // the texture pixel array is not multiple of 4. - // - // Read more about this at - // wikis.khronos.org/opengl/Common_Mistakes#Texture_upload_and_pixel_reads - // - // To prevent this, the image is converted to RGBA8. RGBA8 images have a pixel - // size of 4 bytes so they cannot have any alignment problems - - tracing::warn!( - texture_asset = %assets - .get_label(&texture_asset) - .expect("Not possible"), - concat!( - "Converting texture image from RGB8 to RGBA8 to prevent alignment ", - "problems. This conversion may be slow. Consider changing the ", - "texture image's pixel format to RGBA8" - ) - ); - - &texture.image.to_rgba8() - } - _ => &texture.image, - }; - - renderer_object_store.insert( - object_id, - RendererObject::from_raw( - create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)? - .into_raw(), - RendererObjectKind::Texture, - ), - ); - - Ok(()) -} - -fn draw_mesh( - current_context: &MaybeCurrentContextWithFns, - graphics_mesh: &GraphicsMesh, - opts: &DrawMeshOptions, -) -> Result<(), GlDrawError> -{ - graphics_mesh.vertex_arr.bind(current_context); - - if graphics_mesh.index_buffer.is_some() { - VertexArray::draw_elements( - current_context, - opengl_bindings::vertex_array::DrawElementsOptions { - primitive_kind: PrimitiveKind::Triangles, - element_offset: opts.element_offset, - element_cnt: opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), - vertex_offset: opts.vertex_offset, - }, - )?; - } else { - VertexArray::draw_arrays( - current_context, - PrimitiveKind::Triangles, - opts.vertex_offset, - opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), - )?; - } - - Ok(()) -} - -fn create_gl_texture( - current_context: &MaybeCurrentContextWithFns, - image: &Image, - texture_properties: &TextureProperties, -) -> Result -{ - let gl_texture = GlTexture::new(current_context); - - gl_texture.generate( - current_context, - &image.dimensions().into(), - image.as_bytes(), - match image.color_type() { - ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, - ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, - _ => { - unimplemented!(); - } - }, - if image.color_space_is_srgb() { - GlTextureColorSpace::Srgb - } else { - GlTextureColorSpace::Linear - }, - )?; - - gl_texture.set_wrap( - current_context, - texture_wrapping_to_gl(texture_properties.wrap), - ); - - gl_texture.set_magnifying_filter( - current_context, - texture_filtering_to_gl(texture_properties.magnifying_filter), - ); - - gl_texture.set_minifying_filter( - current_context, - texture_filtering_to_gl(texture_properties.minifying_filter), - ); - - Ok(gl_texture) -} - -fn create_shader_program( - current_context: &MaybeCurrentContextWithFns, - shader_program: &ShaderProgram, -) -> Result -{ - let shader_program_reflection = shader_program.reflection(0).expect("Not possible"); - - let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection - .entry_points() - .enumerate() - .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex) - .ok_or_else(|| { - CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex) - })?; - - let vertex_shader_entry_point_code = shader_program - .get_entry_point_code(vs_entry_point_index.try_into().expect( - "Vertex shader entry point index does not fit in 32-bit unsigned int", - )) - .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { - err, - stage: ShaderStage::Vertex, - entrypoint: vs_entry_point_reflection - .name() - .map(|name| name.to_string().into()) - .unwrap_or("(none)".into()), - })?; - - let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection - .entry_points() - .enumerate() - .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment) - .ok_or_else(|| { - CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment) - })?; - - let fragment_shader_entry_point_code = shader_program - .get_entry_point_code(fs_entry_point_index.try_into().expect( - "Fragment shader entry point index does not fit in 32-bit unsigned int", - )) - .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { - err, - stage: ShaderStage::Fragment, - entrypoint: fs_entry_point_reflection - .name() - .map(|name| name.to_string().into()) - .unwrap_or("(none)".into()), - })?; - - let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); - - vertex_shader.set_source( - current_context, - &vertex_shader_entry_point_code.as_str().unwrap(), - )?; - - vertex_shader.compile(current_context)?; - - let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); - - fragment_shader.set_source( - current_context, - &fragment_shader_entry_point_code.as_str().unwrap(), - )?; - - fragment_shader.compile(current_context)?; - - let gl_shader_program = GlShaderProgram::new(current_context); - - gl_shader_program.attach(current_context, &vertex_shader); - gl_shader_program.attach(current_context, &fragment_shader); - - gl_shader_program.link(current_context)?; - - Ok(gl_shader_program) -} - -#[derive(Debug, thiserror::Error)] -enum CreateShaderError -{ - #[error( - "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}" - )] - GetShaderEntryPointCodeFailed - { - #[source] - err: ShaderError, - stage: ShaderStage, - entrypoint: Cow<'static, str>, - }, - - #[error("No entrypoint was found for shader stage {0:?}")] - NoShaderStageEntrypointFound(ShaderStage), - - #[error(transparent)] - ShaderError(#[from] GlShaderError), -} - -#[tracing::instrument(skip_all)] -fn opengl_debug_message_cb( - source: MessageSource, - ty: MessageType, - id: u32, - severity: MessageSeverity, - message: &str, -) -{ - use std::backtrace::{Backtrace, BacktraceStatus}; - - use tracing::{event, Level}; - - macro_rules! create_event { - ($level: expr) => { - event!($level, ?source, ?ty, id, ?severity, message); - }; - } - - if matches!(severity, MessageSeverity::Notification) { - return; - } - - match ty { - MessageType::Error => { - create_event!(Level::ERROR); - - let backtrace = Backtrace::capture(); - - if matches!(backtrace.status(), BacktraceStatus::Captured) { - tracing::error!("{backtrace}"); - // event!(Level::TRACE, "{backtrace}"); - } - } - MessageType::Other => { - create_event!(Level::INFO); - } - _ => { - create_event!(Level::WARN); - } - }; -} - -#[inline] -fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping -{ - match texture_wrapping { - TextureWrapping::Repeat => GlTextureWrapping::Repeat, - TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, - TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, - TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, - } -} - -#[inline] -fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering -{ - match texture_filtering { - TextureFiltering::Linear => GlTextureFiltering::Linear, - TextureFiltering::Nearest => GlTextureFiltering::Nearest, - } -} - -impl From> for opengl_bindings::data_types::Vec2 -{ - fn from(vec2: Vec2) -> Self - { - Self { x: vec2.x, y: vec2.y } - } -} - -impl From> - for opengl_bindings::data_types::Vec3 -{ - fn from(vec3: Vec3) -> Self - { - Self { x: vec3.x, y: vec3.y, z: vec3.z } - } -} - -impl From> - for opengl_bindings::data_types::Matrix -{ - fn from(matrix: Matrix) -> Self - { - Self { items: matrix.items } - } -} - -impl From> for opengl_bindings::data_types::Dimens -{ - fn from(dimens: Dimens) -> Self - { - Self { - width: dimens.width, - height: dimens.height, - } - } -} - -impl From for opengl_bindings::misc::PolygonMode -{ - fn from(mode: crate::draw_flags::PolygonMode) -> Self - { - match mode { - crate::draw_flags::PolygonMode::Point => Self::Point, - crate::draw_flags::PolygonMode::Fill => Self::Fill, - crate::draw_flags::PolygonMode::Line => Self::Line, - } - } -} - -impl From for opengl_bindings::misc::PolygonModeFace -{ - fn from(face: crate::draw_flags::PolygonModeFace) -> Self - { - match face { - crate::draw_flags::PolygonModeFace::Front => Self::Front, - crate::draw_flags::PolygonModeFace::Back => Self::Back, - crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, - } - } -} - -#[derive(Debug, IntoBytes, Immutable)] -#[repr(C)] -pub struct CF32Vec3 -{ - x: f32, - y: f32, - z: f32, -} - -impl From> for CF32Vec3 -{ - fn from(src: Vec3) -> Self - { - Self { x: src.x, y: src.y, z: src.z } - } -} - -fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor -{ - match blending_factor { - BlendingFactor::Zero => GlBlendingFactor::Zero, - BlendingFactor::One => GlBlendingFactor::One, - BlendingFactor::SrcColor => GlBlendingFactor::SrcColor, - BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor, - BlendingFactor::DstColor => GlBlendingFactor::DstColor, - BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor, - BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha, - BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha, - BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha, - BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha, - BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor, - BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor, - BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha, - BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha, - } -} - -fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation -{ - match blending_equation { - BlendingEquation::Add => GlBlendingEquation::Add, - BlendingEquation::Subtract => GlBlendingEquation::Subtract, - BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract, - BlendingEquation::Min => GlBlendingEquation::Min, - BlendingEquation::Max => GlBlendingEquation::Max, - } -} diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs deleted file mode 100644 index cfd6ea7..0000000 --- a/engine/src/renderer/opengl/glutin_compat.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Original file: -// https://github.com/rust-windowing/glutin/blob/ -// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs -// -// Copyright © 2022 Kirill Chibisov -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the “Software”), to deal -// in the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is furnished to do -// so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -//! This library provides helpers for cross-platform [`glutin`] bootstrapping -//! with [`winit`]. - -#![deny(rust_2018_idioms)] -#![deny(rustdoc::broken_intra_doc_links)] -#![deny(clippy::all)] -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] -#![cfg_attr(clippy, deny(warnings))] - -use glutin::config::{Config, ConfigTemplateBuilder}; -use glutin::display::{Display, DisplayApiPreference}; -use glutin::error::Error as GlutinError; -#[cfg(x11_platform)] -use glutin::platform::x11::X11GlConfigExt; -use glutin::prelude::*; -use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; - -use crate::windowing::window::CreationAttributes as WindowCreationAttributes; - -#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] -compile_error!("Please select at least one api backend"); - -/// The helper to perform [`Display`] creation and OpenGL platform -/// bootstrapping with the help of [`winit`] with little to no platform specific -/// code. -/// -/// This is only required for the initial setup. If you want to create -/// additional windows just use the [`finalize_window`] function and the -/// configuration you've used either for the original window or picked with the -/// existing [`Display`]. -/// -/// [`winit`]: winit -/// [`Display`]: glutin::display::Display -#[derive(Default, Debug, Clone)] -pub struct DisplayBuilder -{ - preference: ApiPreference, - window_attributes: WindowCreationAttributes, -} - -impl DisplayBuilder -{ - /// Create new display builder. - pub fn new() -> Self - { - Default::default() - } - - /// The preference in picking the configuration. - #[allow(dead_code)] - pub fn with_preference(mut self, preference: ApiPreference) -> Self - { - self.preference = preference; - self - } - - /// The window attributes to use when building a window. - /// - /// By default no window is created. - pub fn with_window_attributes( - mut self, - window_creation_attrs: WindowCreationAttributes, - ) -> Self - { - self.window_attributes = window_creation_attrs; - self - } - - /// Initialize the OpenGL platform and create a compatible window to use - /// with it when the [`WindowAttributes`] was passed with - /// [`Self::with_window_attributes()`]. It's optional, since on some - /// platforms like `Android` it is not available early on, so you want to - /// find configuration and later use it with the [`finalize_window`]. - /// But if you don't care about such platform you can always pass - /// [`WindowAttributes`]. - /// - /// # Api-specific - /// - /// **WGL:** - [`WindowAttributes`] **must** be passed in - /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, - /// otherwise only builtin functions like `glClear` will be available. - pub fn build( - self, - window_handle: Option>, - display_handle: &DisplayHandle<'_>, - template_builder: ConfigTemplateBuilder, - config_picker_fn: ConfigPickerFn, - ) -> Result<(WindowCreationAttributes, Config), Error> - where - ConfigPickerFn: FnOnce(Box + '_>) -> Option, - { - // XXX with WGL backend window should be created first. - let raw_window_handle = if cfg!(wgl_backend) { - let Some(window_handle) = window_handle else { - return Err(Error::WindowRequired); - }; - - Some(window_handle.as_raw()) - } else { - None - }; - - let gl_display = - create_display(display_handle, self.preference, raw_window_handle) - .map_err(Error::CreateDisplayFailed)?; - - // XXX the native window must be passed to config picker when WGL is used - // otherwise very limited OpenGL features will be supported. - #[cfg(wgl_backend)] - let template_builder = if let Some(raw_window_handle) = raw_window_handle { - template_builder.compatible_with_native_window(raw_window_handle) - } else { - template_builder - }; - - let template = template_builder.build(); - - // SAFETY: The RawWindowHandle passed on the config template - // (when cfg(wgl_backend)) will always point to a valid object since it is - // derived from the window_handle argument which when Some is a WindowHandle and - // WindowHandles always point to a valid object - let gl_configs = unsafe { gl_display.find_configs(template) } - .map_err(Error::FindConfigsFailed)?; - - let picked_gl_config = - config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; - - #[cfg(not(wgl_backend))] - let window_attrs = - { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) }; - - #[cfg(wgl_backend)] - let window_attrs = self.window_attributes; - - Ok((window_attrs, picked_gl_config)) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to create display")] - CreateDisplayFailed(#[source] GlutinError), - - #[error("Failed to find configs")] - FindConfigsFailed(#[source] GlutinError), - - #[error("No config was picked by config picker function")] - NoConfigPicked, - - #[error("Window required for building display on current platform")] - WindowRequired, -} - -fn create_display( - display_handle: &DisplayHandle<'_>, - _api_preference: ApiPreference, - _raw_window_handle: Option, -) -> Result -{ - #[cfg(egl_backend)] - let _preference = DisplayApiPreference::Egl; - - #[cfg(glx_backend)] - let _preference = DisplayApiPreference::Glx(Box::new( - crate::windowing::window::platform::x11::register_xlib_error_hook, - )); - - #[cfg(cgl_backend)] - let _preference = DisplayApiPreference::Cgl; - - #[cfg(wgl_backend)] - let _preference = DisplayApiPreference::Wgl(_raw_window_handle); - - #[cfg(all(egl_backend, glx_backend))] - let _preference = match _api_preference { - ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( - crate::windowing::window::platform::x11::register_xlib_error_hook, - )), - ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( - crate::windowing::window::platform::x11::register_xlib_error_hook, - )), - }; - - #[cfg(all(wgl_backend, egl_backend))] - let _preference = match _api_preference { - ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), - ApiPreference::FallbackEgl => { - DisplayApiPreference::WglThenEgl(_raw_window_handle) - } - }; - - let handle = display_handle.as_raw(); - unsafe { Ok(Display::new(handle, _preference)?) } -} - -/// Finalize [`Window`] creation by applying the options from the [`Config`], be -/// aware that it could remove incompatible options from the window builder like -/// `transparency`, when the provided config doesn't support it. -/// -/// [`Window`]: winit::window::Window -/// [`Config`]: glutin::config::Config -#[cfg(not(wgl_backend))] -fn finalize_window_creation_attrs( - mut attributes: WindowCreationAttributes, - gl_config: &Config, -) -> WindowCreationAttributes -{ - // Disable transparency if the end config doesn't support it. - if gl_config.supports_transparency() == Some(false) { - attributes = attributes.with_transparent(false); - } - - #[cfg(x11_platform)] - let attributes = if let Some(x11_visual) = gl_config.x11_visual() { - attributes.with_x11_visual(x11_visual.visual_id() as _) - } else { - attributes - }; - - attributes -} - -/// Simplified version of the [`DisplayApiPreference`] which is used to simplify -/// cross platform window creation. -/// -/// To learn about platform differences the [`DisplayApiPreference`] variants. -/// -/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum ApiPreference -{ - /// Prefer `EGL` over system provider like `GLX` and `WGL`. - PreferEgl, - - /// Fallback to `EGL` when failed to create the system profile. - /// - /// This behavior is used by default. However consider using - /// [`Self::PreferEgl`] if you don't care about missing EGL features. - #[default] - FallbackEgl, -} diff --git a/engine/src/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs deleted file mode 100644 index 78de80c..0000000 --- a/engine/src/renderer/opengl/graphics_mesh.rs +++ /dev/null @@ -1,216 +0,0 @@ -use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; -use opengl_bindings::vertex_array::{ - AttributeFormat as GlVertexArrayAttributeFormat, - BindVertexBufferError as GlVertexArrayBindVertexBufferError, - DataType as GlVertexArrayDataType, - VertexArray as GlVertexArray, - VertexBufferSpec as GlVertexArrayVertexBufferSpec, -}; -use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns; - -use crate::mesh::{Mesh, VertexAttrType}; -use crate::renderer::MeshUsage; -use crate::shader::VertexDescription as ShaderVertexDescription; - -#[derive(Debug)] -pub struct GraphicsMesh -{ - /// Vertex and index buffer has to live as long as the vertex array - vertex_buffer: GlBuffer, - pub index_buffer: Option>, - pub element_cnt: u32, - pub vertex_arr: GlVertexArray, -} - -impl GraphicsMesh -{ - #[tracing::instrument(skip_all)] - pub fn new( - current_context: &GlCurrentContextWithFns, - mesh: &Mesh, - mesh_usage: MeshUsage, - vertex_desc: &ShaderVertexDescription, - ) -> Result - { - let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); - - let vertex_arr = GlVertexArray::new(current_context); - let vertex_buffer = GlBuffer::new(current_context); - - vertex_buffer - .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) - .map_err(Error::StoreVerticesFailed)?; - - let vertex_buf_binding_index = 0; - - if let Err(err) = vertex_arr.bind_vertex_buffer( - current_context, - vertex_buf_binding_index, - &vertex_buffer, - GlVertexArrayVertexBufferSpec { - offset: 0, - vertex_size: mesh.vertex_buf().vertex_size(), - }, - ) { - match err { - GlVertexArrayBindVertexBufferError::OffsetValueTooLarge { - value: _, - max_value: _, - } => unreachable!(), - GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge { - value, - max_value, - } => { - panic!( - "Size of vertex ({}) is too large. Must be less than {max_value}", - value - ); - } - } - } - - for vertex_attr_props in mesh.vertex_buf().vertex_attr_props() { - let vertex_field_desc = vertex_desc - .fields - .iter() - .find(|vertex_field_desc| { - *vertex_field_desc.name == vertex_attr_props.name - }) - .unwrap(); - - let attrib_index: u32 = - vertex_field_desc.varying_input_offset.try_into().unwrap(); - - vertex_arr.enable_attrib(current_context, attrib_index); - - vertex_arr.set_attrib_format( - current_context, - attrib_index, - match &vertex_attr_props.ty { - VertexAttrType::Float32 => GlVertexArrayAttributeFormat { - data_type: GlVertexArrayDataType::Float, - count: 1, - normalized: false, - offset: vertex_attr_props.byte_offset.try_into().unwrap(), - }, - VertexAttrType::Float32Array { length } => { - GlVertexArrayAttributeFormat { - data_type: GlVertexArrayDataType::Float, - count: (*length).try_into().unwrap(), - normalized: false, - offset: vertex_attr_props.byte_offset.try_into().unwrap(), - } - } - }, - ); - - vertex_arr.set_attrib_vertex_buf_binding( - current_context, - attrib_index, - vertex_buf_binding_index, - ); - } - - if let Some(indices) = mesh.indices() { - let index_buffer = GlBuffer::new(current_context); - - index_buffer - .store(current_context, indices, buffer_usage) - .map_err(Error::StoreIndicesFailed)?; - - vertex_arr.bind_element_buffer(current_context, &index_buffer); - - return Ok(Self { - vertex_buffer: vertex_buffer, - index_buffer: Some(index_buffer), - element_cnt: indices - .len() - .try_into() - .expect("Mesh index count does not fit into a 32-bit unsigned int"), - vertex_arr, - }); - } - - Ok(Self { - vertex_buffer: vertex_buffer, - index_buffer: None, - element_cnt: mesh - .vertex_buf() - .len() - .try_into() - .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), - vertex_arr, - }) - } - - pub fn update( - &mut self, - current_context: &GlCurrentContextWithFns, - mesh: &Mesh, - mesh_usage: MeshUsage, - ) -> Result<(), Error> - { - let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); - - self.vertex_buffer - .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) - .map_err(Error::StoreVerticesFailed)?; - - if let Some(indices) = mesh.indices() { - let index_buffer = self - .index_buffer - .get_or_insert_with(|| GlBuffer::new(current_context)); - - index_buffer - .store(current_context, indices, buffer_usage) - .map_err(Error::StoreIndicesFailed)?; - - self.vertex_arr - .bind_element_buffer(current_context, &index_buffer); - - self.element_cnt = indices - .len() - .try_into() - .expect("Mesh index count does not fit into a 32-bit unsigned int"); - - return Ok(()); - } - - self.element_cnt = mesh - .vertex_buf() - .len() - .try_into() - .expect("Mesh vertex count does not fit into a 32-bit unsigned int"); - - Ok(()) - } - - pub fn destroy(&mut self, curr_gl_ctx: &GlCurrentContextWithFns) - { - self.vertex_arr.delete(curr_gl_ctx); - self.vertex_buffer.delete(curr_gl_ctx); - - if let Some(index_buffer) = &self.index_buffer { - index_buffer.delete(curr_gl_ctx); - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to store vertices in vertex buffer")] - StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), - - #[error("Failed to store indices in index buffer")] - StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), -} - -fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage -{ - match mesh_usage { - MeshUsage::Stream => GlBufferUsage::Stream, - MeshUsage::Static => GlBufferUsage::Static, - MeshUsage::Dynamic => GlBufferUsage::Dynamic, - } -} diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs new file mode 100644 index 0000000..d7d3007 --- /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 blending; +pub mod main_render_pass; +pub mod object; +pub mod opengl; + +static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0); + +declare_entity!( + pub PRE_RENDER_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*POST_UPDATE_PHASE) + .build() + ) +); + +declare_entity!( + pub RENDER_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*PRE_RENDER_PHASE) + .build() + ) +); + +declare_entity!( + pub POST_RENDER_PHASE, + (Phase, Pair::builder().relation::().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); + + opengl::Extension::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, + + /// 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, +} + +#[derive(Debug)] +pub struct RenderPass +{ + pub commands: Vec, + pub draw_properties: DrawProperties, +} + +#[derive(Debug, Reflection)] +#[non_exhaustive] +pub enum Command +{ + RemoveSurface(SurfaceId), + MakeCurrent(SurfaceId), + SetSurfaceSize(SurfaceId, Dimens), + ClearBuffers(BufferClearMask), + SwapBuffers(SurfaceId), + CreateShaderProgram(ObjectId, ShaderProgram), + ActivateShader(ObjectId), + SetShaderBinding(ShaderBindingLocation, ShaderBindingValue), + CreateTexture(AssetHandle), + 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, + + 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, + } +} + +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>, + + /// Position (in window coordinates) of the lower left corner of the scissor box. + pub lower_left_corner_pos: Vec2, +} + +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, +} + +impl CommandQueue +{ + pub fn push(&mut self, command: Command) + { + self.queue.push_back(command); + } + + pub fn drain(&mut self) -> impl Iterator + 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,)>, + mut command_queue: Single, + mut render_passes: Single, + mut active_draw_props: Local, +) +{ + 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>, + mut command_queue: Single, +) +{ + for evt_match in &observe { + let window_ent = evt_match.get_entity(); + + let Some(window_surface_spec) = window_ent.get::() 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>, + mut command_queue: Single, + 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::() 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, 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, +} diff --git a/engine/src/rendering/blending.rs b/engine/src/rendering/blending.rs new file mode 100644 index 0000000..9ae2f82 --- /dev/null +++ b/engine/src/rendering/blending.rs @@ -0,0 +1,89 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config +{ + pub source_factor: Factor, + pub destination_factor: Factor, + pub equation: Equation, +} + +impl Default for Config +{ + fn default() -> Self + { + Self { + source_factor: Factor::One, + destination_factor: Factor::Zero, + equation: Equation::default(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Factor +{ + /// Factor will be the RGBA color `(0,0,0,0)` + Zero, + + /// Factor will be the RGBA color `(1,1,1,1)` + One, + + /// Factor will be the source color + SrcColor, + + /// Factor will be the RGBA color `(1,1,1,1) - source color` + OneMinusSrcColor, + + /// Factor will be the destination color + DstColor, + + /// Factor will be the RGBA color `(1,1,1,1) - destination color` + OneMinusDstColor, + + /// Factor will be the alpha component of the source color. + SrcAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - source color alpha` + OneMinusSrcAlpha, + + /// Factor will be the alpha component of the destination color. + DstAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - destination color alpha` + OneMinusDstAlpha, + + /// Factor will be the constant color + ConstantColor, + + /// Factor will be the RGBA color `(1,1,1,1) - constant color` + OneMinusConstantColor, + + /// Factor will be the alpha component of the constant color. + ConstantAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - constant color alpha` + OneMinusConstantAlpha, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Equation +{ + /// The destination color and source color is added to each other in the blend + /// function + #[default] + Add, + + /// The destination color is subtracted from the source color in the blend function + Subtract, + + /// The source color is subtracted from the destination color in the blend function + ReverseSubtract, + + /// The blend function will take the component-wise minimum of the destination color + /// and the source color + Min, + + /// The blend function will take the component-wise maximum of the destination color + /// and the source color + Max, +} diff --git a/engine/src/rendering/main_render_pass.rs b/engine/src/rendering/main_render_pass.rs new file mode 100644 index 0000000..a733482 --- /dev/null +++ b/engine/src/rendering/main_render_pass.rs @@ -0,0 +1,239 @@ +use crate::asset::Assets; +use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; +use crate::ecs::query::term::{With, Without}; +use crate::ecs::sole::Single; +use crate::ecs::Query; +use crate::model::{MaterialSearchResult, Model}; +use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; +use crate::rendering::{ + BufferClearMask, + Command, + DrawMeshOptions, + DrawProperties, + DrawPropertiesUpdateFlags, + MeshUsage, + PendingShaderBindings, + RenderPass, + RenderPasses, + SurfaceSpec, +}; +use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; +use crate::shader::{ + Context as ShaderContext, + ModuleSource as ShaderModuleSource, + Shader, +}; +use crate::texture::{Texture, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL}; +use crate::windowing::window::Window; + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a DrawFlags>, + Option<&'a Shader>, + Option<&'a mut PendingShaderBindings>, +); + +#[tracing::instrument(skip_all)] +pub fn add_main_render_passes( + renderable_query: Query, (Without,)>, + window_surface_spec_query: Query<(&SurfaceSpec,), (With,)>, + assets: Single, + shader_context: Single, + mut render_passes: Single, + mut object_store: Single, +) +{ + let Some(default_shader_asset) = assets + .get_handle_to_loaded::(DEFAULT_SHADER_ASSET_LABEL.clone()) + else { + tracing::error!("Default shader asset is not loaded"); + return; + }; + + let render_pass = render_passes.passes.push_front_mut(RenderPass { + commands: Vec::with_capacity(30), + draw_properties: DrawProperties::default(), + }); + + for (surface_spec,) in &window_surface_spec_query { + render_pass + .commands + .push(Command::MakeCurrent(surface_spec.id)); + + let default_texture_asset = assets + .get_handle_to_loaded::(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) + .expect("Not possible"); + + if !object_store + .contains_maybe_pending_with_id(&ObjectId::Asset(default_texture_asset.id())) + { + object_store.insert_pending(ObjectId::Asset(default_texture_asset.id())); + + render_pass + .commands + .push(Command::CreateTexture(default_texture_asset)); + } + + render_pass.commands.push(Command::ClearBuffers( + BufferClearMask::COLOR | BufferClearMask::DEPTH, + )); + + for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query + { + let shader_asset = match &shader { + Some(shader) => &shader.asset_handle, + None => &default_shader_asset, + }; + + let Some(pending_shader_bindings) = pending_shader_bindings.as_mut() else { + continue; + }; + + if pending_shader_bindings.bindings.is_empty() + && pending_shader_bindings.surface_specific_bindings.is_empty() + { + continue; + } + + let Some(model_spec) = assets.get(&model.spec_asset) else { + continue; + }; + + let Some(mesh_asset) = &model_spec.mesh_asset else { + continue; + }; + + if assets.get(mesh_asset).is_none() { + continue; + } + + debug_assert!(model_spec.material_names.len() <= 1); + + let model_material_asset = match model_spec.find_first_material(&assets) { + MaterialSearchResult::Found(model_material_asset) => { + model_material_asset.clone() + // Some(model_material_asset.clone()) + } + MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => { + // MaterialSearchResult::NotFound => { + continue; + } // MaterialSearchResult::NoMaterials => None, + }; + + if !object_store + .contains_maybe_pending_with_id(&ObjectId::Asset(shader_asset.id())) + { + let Some(shader_program) = shader_context.get_program(&shader_asset.id()) + else { + tracing::error!( + "Shader context doesn't have a program for shader asset {:?}", + assets.get_label(&shader_asset) + ); + continue; + }; + + object_store.insert_pending(ObjectId::Asset(shader_asset.id())); + + render_pass.commands.push(Command::CreateShaderProgram( + ObjectId::Asset(shader_asset.id()), + shader_program.clone(), + )); + } + + render_pass + .commands + .push(Command::ActivateShader(ObjectId::Asset(shader_asset.id()))); + + let Some(model_material) = assets.get(&model_material_asset) else { + // TODO: Handle this case since it may occur + unreachable!(); + }; + + for texture_asset in [ + &model_material.ambient_map, + &model_material.diffuse_map, + &model_material.specular_map, + ] + .into_iter() + .flatten() + { + if !object_store + .contains_maybe_pending_with_id(&ObjectId::Asset(texture_asset.id())) + { + object_store.insert_pending(ObjectId::Asset(texture_asset.id())); + + render_pass + .commands + .push(Command::CreateTexture(texture_asset.clone())); + } + } + + for (shader_binding_loc, shader_binding_val) in + &pending_shader_bindings.bindings + { + render_pass.commands.push(Command::SetShaderBinding( + shader_binding_loc.clone(), + shader_binding_val.clone(), + )); + } + + for (shader_binding_surface_id, shader_binding_loc, shader_binding_val) in + &pending_shader_bindings.surface_specific_bindings + { + if *shader_binding_surface_id != surface_spec.id { + continue; + } + + render_pass.commands.push(Command::SetShaderBinding( + shader_binding_loc.clone(), + shader_binding_val.clone(), + )); + } + + if let Some(draw_flags) = draw_flags.as_deref().and_then(|draw_flags| { + if draw_flags.polygon_mode_config != PolygonModeConfig::default() { + Some(draw_flags) + } else { + None + } + }) { + render_pass.commands.push(Command::UpdateDrawProperties( + DrawProperties { + polygon_mode_config: draw_flags.polygon_mode_config.clone(), + ..Default::default() + }, + DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, + )); + } + + if !object_store + .contains_maybe_pending_with_id(&ObjectId::Asset(mesh_asset.id())) + { + object_store.insert_pending(ObjectId::Asset(mesh_asset.id())); + + render_pass.commands.push(Command::CreateMesh { + obj_id: ObjectId::Asset(mesh_asset.id()), + mesh: None, + usage: MeshUsage::Static, + }); + } + + render_pass.commands.push(Command::DrawMesh( + ObjectId::Asset(mesh_asset.id()), + DrawMeshOptions::default(), + )); + + if draw_flags.as_deref().is_some_and(|draw_flags| { + draw_flags.polygon_mode_config != PolygonModeConfig::default() + }) { + render_pass.commands.push(Command::UpdateDrawProperties( + DrawProperties { + polygon_mode_config: PolygonModeConfig::default(), + ..Default::default() + }, + DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, + )); + } + } + } +} diff --git a/engine/src/rendering/object.rs b/engine/src/rendering/object.rs new file mode 100644 index 0000000..ead20e6 --- /dev/null +++ b/engine/src/rendering/object.rs @@ -0,0 +1,136 @@ +use std::collections::HashMap; +use std::sync::atomic::{AtomicU64, Ordering}; + +use crate::asset::Id as AssetId; +use crate::ecs::Sole; + +pub type RawValue = u32; + +/// Rendering object ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Id +{ + Asset(AssetId), + Sequential(SequentialId), +} + +impl Id +{ + pub fn new_sequential() -> Self + { + static NEXT_SEQUENTIAL_ID: AtomicU64 = AtomicU64::new(0); + + Self::Sequential(SequentialId( + NEXT_SEQUENTIAL_ID.fetch_add(1, Ordering::Relaxed), + )) + } + + pub fn into_asset_id(self) -> Option + { + match self { + Self::Asset(asset_id) => Some(asset_id), + Self::Sequential(_) => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SequentialId(u64); + +/// Rendering object store. +#[derive(Debug, Default, Sole)] +pub struct Store +{ + objects: HashMap>, +} + +impl Store +{ + pub fn get_obj(&self, id: &Id) -> Option<&Object> + { + self.objects.get(id).and_then(|obj| obj.as_ref()) + } + + pub fn get_texture_obj(&self, id: &Id) -> Option<&Object> + { + let obj = self.get_obj(id)?; + + if !matches!(obj.kind(), Kind::Texture) { + return None; + } + + Some(obj) + } + + pub fn get_shader_program_obj(&self, id: &Id) -> Option<&Object> + { + let obj = self.get_obj(id)?; + + if !matches!(obj.kind(), Kind::ShaderProgram) { + return None; + } + + Some(obj) + } + + pub fn contains_maybe_pending_with_id(&self, id: &Id) -> bool + { + self.objects.contains_key(id) + } + + pub fn contains_non_pending_with_id(&self, id: &Id) -> bool + { + self.objects.get(id).and_then(|obj| obj.as_ref()).is_some() + } + + pub fn insert(&mut self, id: Id, object: Object) + { + self.objects.insert(id, Some(object)); + } + + pub fn insert_pending(&mut self, id: Id) + { + self.objects.insert(id, None); + } + + pub fn remove(&mut self, id: &Id) -> Option> + { + self.objects.remove(id) + } +} + +/// Rendering object. +#[derive(Debug, Clone)] +pub struct Object +{ + raw: RawValue, + kind: Kind, +} + +impl Object +{ + pub fn from_raw(raw: RawValue, kind: Kind) -> Self + { + Self { raw, kind } + } + + pub fn as_raw(&self) -> RawValue + { + self.raw + } + + pub fn kind(&self) -> Kind + { + self.kind + } +} + +/// Rendering object kind. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Kind +{ + Texture, + ShaderProgram, + ImplementationSpecific, +} diff --git a/engine/src/rendering/opengl.rs b/engine/src/rendering/opengl.rs new file mode 100644 index 0000000..2845464 --- /dev/null +++ b/engine/src/rendering/opengl.rs @@ -0,0 +1,1438 @@ +//! OpenGL renderer. + +use std::borrow::Cow; +use std::collections::HashMap; + +use glutin::config::Config as GlutinConfig; +use glutin::display::GetGlDisplay; +use glutin::error::Error as GlutinError; +use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext}; +use glutin::surface::{ + GlSurface as _, + Surface as GlutinSurface, + WindowSurface as GlutinWindowSurface, +}; +use opengl_bindings::blending::{ + configure as gl_blending_configure, + Configuration as GlBlendingConfig, + Equation as GlBlendingEquation, + Factor as GlBlendingFactor, +}; +use opengl_bindings::debug::{ + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + MessageSeverity, + MessageSource, + MessageType, + SetDebugMessageControlError as GlSetDebugMessageControlError, +}; +use opengl_bindings::misc::{ + clear_buffers, + define_scissor_box as gl_define_scissor_box, + enable, + get_viewport as gl_get_viewport, + set_enabled, + set_viewport as gl_set_viewport, + BufferClearMask as GlBufferClearMask, + Capability, +}; +use opengl_bindings::shader::{ + Error as GlShaderError, + Kind as ShaderKind, + Program as GlShaderProgram, + Shader as GlShader, + // UniformLocation as GlUniformLocation, +}; +use opengl_bindings::texture::{ + ColorSpace as GlTextureColorSpace, + Filtering as GlTextureFiltering, + GenerateError as GlTextureGenerateError, + PixelDataFormat as GlTexturePixelDataFormat, + Texture as GlTexture, + Wrapping as GlTextureWrapping, +}; +use opengl_bindings::vertex_array::{ + DrawError as GlDrawError, + PrimitiveKind, + VertexArray, +}; +use opengl_bindings::{ + MakeContextCurrentError as GlMakeContextCurrentError, + MaybeCurrentContextWithFns, +}; +use raw_window_handle::WindowHandle; +use safer_ffi::layout::ReprC; +use zerocopy::{Immutable, IntoBytes}; + +use crate::asset::{Assets, Handle as AssetHandle}; +use crate::data_types::dimens::Dimens; +use crate::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Query, Sole}; +use crate::image::{ColorType as ImageColorType, Image}; +use crate::matrix::Matrix; +use crate::reflection::EnumReflectionExt; +use crate::rendering::blending::{ + Equation as BlendingEquation, + Factor as BlendingFactor, +}; +use crate::rendering::object::{ + Id as ObjectId, + Kind as ObjectKind, + Object, + RawValue as ObjectRawValue, + Store as ObjectStore, +}; +use crate::rendering::opengl::glutin_compat::{ + DisplayBuilder, + Error as GlutinCompatError, +}; +use crate::rendering::opengl::graphics_mesh::GraphicsMesh; +use crate::rendering::{ + BufferClearMask, + Command, + CommandQueue, + DrawMeshOptions, + DrawPropertiesUpdateFlags, + GraphicsProperties, + SurfaceId, + SurfaceSpec, + POST_RENDER_PHASE, + RENDER_PHASE, +}; +use crate::shader::cursor::BindingValue as ShaderBindingValue; +use crate::shader::{ + Context as ShaderContext, + Error as ShaderError, + Program as ShaderProgram, + Stage as ShaderStage, +}; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Texture, + Wrapping as TextureWrapping, +}; +use crate::util::OptionExt; +use crate::vector::{Vec2, Vec3}; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady, + Window, +}; +use crate::windowing::Context as WindowingContext; + +mod glutin_compat; +mod graphics_mesh; + +#[derive(Debug, Component)] +struct WindowGlConfig +{ + gl_config: GlutinConfig, +} + +#[derive(Sole, Default)] +struct GraphicsContext +{ + gl_context: Option, + surfaces: HashMap, + shader_uniform_buffer_objs: + HashMap>>, + objects: HashMap, + next_object_key: ObjectRawValue, +} + +#[derive(Debug)] +struct GraphicsContextSurface +{ + window_surface: GlutinSurface, + size: Dimens, +} + +#[derive(Debug)] +enum GraphicsContextObject +{ + Mesh + { + mesh: GraphicsMesh, + compatible_shader_program_obj_id: ObjectId, + }, +} + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Extension {} + +impl crate::ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) + { + collector.add_system(*RENDER_PHASE, handle_commands); + + collector.add_system(*POST_RENDER_PHASE, prepare_windows); + collector.add_system(*POST_RENDER_PHASE, init_window_graphics); + + let _ = collector.add_sole(GraphicsContext::default()); + } +} + +fn prepare_windows( + window_query: Query< + (Option<&Window>, &mut WindowCreationAttributes), + ( + Without, + Without, + Without, + ), + >, + windowing_context: Single, + graphics_props: Single, + mut actions: Actions, +) +{ + let Some(display_handle) = windowing_context.display_handle() else { + return; + }; + + for (window_ent_id, (window, mut window_creation_attrs)) in + window_query.iter_with_euids() + { + tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); + + let mut glutin_config_template_builder = + glutin::config::ConfigTemplateBuilder::new(); + + if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { + glutin_config_template_builder = glutin_config_template_builder + .with_multisampling(multisampling_sample_cnt); + } + + let window_handle = match window + .as_ref() + .map(|window| unsafe { + windowing_context.get_window_as_handle(&window.wid()) + }) + .flatten() + .transpose() + { + Ok(window_handle) => window_handle, + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; + + let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new() + .with_window_attributes(window_creation_attrs.clone()) + .build( + window_handle, + &display_handle, + glutin_config_template_builder, + |mut cfgs| cfgs.next(), + ) { + Ok((new_window_creation_attrs, gl_config)) => { + (new_window_creation_attrs, gl_config) + } + Err(GlutinCompatError::WindowRequired) => { + actions.add_components(window_ent_id, (CreationReady,)); + continue; + } + Err(err) => { + tracing::error!("Failed to create platform graphics display: {err}"); + continue; + } + }; + + *window_creation_attrs = new_window_creation_attrs; + + actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); + + if window.is_none() { + actions.add_components(window_ent_id, (CreationReady,)); + } + } +} + +#[tracing::instrument(skip_all)] +fn init_window_graphics( + window_query: Query<(&Window, &WindowGlConfig), (Without,)>, + windowing_context: Single, + graphics_props: Single, + mut graphics_ctx: Single, + mut actions: Actions, +) +{ + for (window_ent_id, (window, window_gl_config)) in window_query.iter_with_euids() { + tracing::info!( + window_entity_id=%window_ent_id, + window_title=&*window.title, + "Initializing graphics for window" + ); + + let display = window_gl_config.gl_config.display(); + + let window_handle = + match unsafe { windowing_context.get_window_as_handle(&window.wid()) } + .transpose() + { + Ok(Some(window_handle)) => window_handle, + Ok(None) => { + tracing::error!( + wid = ?window.wid(), + entity_id = %window_ent_id, + "Windowing context does not contain window" + ); + continue; + } + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; + + let Some(window_inner_size) = window.inner_size().try_into_nonzero() else { + tracing::error!( + "Cannot create a surface for a window with a width/height of 0", + ); + continue; + }; + + let window_surface = match unsafe { + display.create_window_surface( + &window_gl_config.gl_config, + &glutin::surface::SurfaceAttributesBuilder::< + glutin::surface::WindowSurface, + >::new() + .build( + window_handle.as_raw(), + window_inner_size.width, + window_inner_size.height, + ), + ) + } { + Ok(window_surface) => window_surface, + Err(err) => { + tracing::error!("Failed to create window surface: {err}"); + continue; + } + }; + + let gl_context = match graphics_ctx.gl_context.get_or_try_insert_with_fn(|| { + create_gl_context( + &window_gl_config.gl_config, + &graphics_props, + window_handle, + &window_surface, + ) + }) { + Ok(gl_context) => gl_context, + Err(err) => { + tracing::error!("Failed to create GL context: {err}"); + continue; + } + }; + + if let Err(err) = gl_context.make_current(&window_surface) { + tracing::error!("Failed to make GL context current: {err}"); + continue; + }; + + if let Err(err) = gl_set_viewport( + &gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &window.inner_size().clone().into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + + set_enabled( + &gl_context, + Capability::DepthTest, + graphics_props.depth_test, + ); + + set_enabled( + &gl_context, + Capability::MultiSample, + graphics_props.multisampling_sample_cnt.is_some(), + ); + + if graphics_props.debug { + enable(&gl_context, Capability::DebugOutput); + enable(&gl_context, Capability::DebugOutputSynchronous); + + set_debug_message_callback(&gl_context, opengl_debug_message_cb); + + match set_debug_message_control( + &gl_context, + None, + None, + None, + &[], + MessageIdsAction::Disable, + ) { + Ok(()) => {} + Err(GlSetDebugMessageControlError::TooManyIds { + id_cnt: _, + max_id_cnt: _, + }) => { + unreachable!() // No ids are given + } + } + } + + let surface_id = SurfaceId::new_unique(); + + actions.add_components(window_ent_id, (SurfaceSpec { id: surface_id },)); + + graphics_ctx.surfaces.insert( + surface_id, + GraphicsContextSurface { + window_surface, + size: window.inner_size().clone(), + }, + ); + } +} + +#[tracing::instrument(skip_all)] +fn handle_commands( + mut graphics_ctx: Single, + mut object_store: Single, + mut command_queue: Single, + assets: Single, + shader_context: Single, +) +{ + let GraphicsContext { + ref gl_context, + ref mut surfaces, + ref mut shader_uniform_buffer_objs, + objects: ref mut graphics_ctx_objects, + next_object_key: ref mut next_graphics_ctx_object_key, + } = *graphics_ctx; + + let Some(gl_context) = gl_context else { + return; + }; + + let mut activated_gl_shader_program: Option<(ObjectId, GlShaderProgram)> = None; + + for command in command_queue.drain() { + let tracing_span = tracing::info_span!( + "handle_cmd", + command = %command.get_variant_reflection().name, + ); + let _tracing_span_enter = tracing_span.enter(); + + match command { + Command::RemoveSurface(surface_id) => { + let Some(surface) = surfaces.remove(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if surface.window_surface.is_current(gl_context.context()) { + if let Err(err) = gl_context.context().make_not_current_in_place() { + tracing::error!("Failed to make GL context not current: {err}"); + } + + if let Err(err) = gl_context.make_current_surfaceless() { + tracing::error!("Failed to make GL context current: {err}"); + } + } + + drop(surface); + } + Command::MakeCurrent(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if let Err(err) = gl_context.make_current(&surface.window_surface) { + tracing::error!("Failed to make graphics context current: {err}"); + continue; + } + + if let Err(err) = gl_set_viewport( + gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &surface.size.into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + } + Command::SetSurfaceSize(surface_id, new_surface_size) => { + let Some(surface) = surfaces.get_mut(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + surface.size = new_surface_size; + + if !surface.window_surface.is_current(gl_context.context()) { + continue; + } + + if let Err(err) = gl_set_viewport( + gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &surface.size.into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + } + Command::ClearBuffers(buffer_clear_mask) => { + let mut clear_mask = GlBufferClearMask::empty(); + + clear_mask.set( + GlBufferClearMask::COLOR, + buffer_clear_mask.contains(BufferClearMask::COLOR), + ); + + clear_mask.set( + GlBufferClearMask::DEPTH, + buffer_clear_mask.contains(BufferClearMask::DEPTH), + ); + + clear_mask.set( + GlBufferClearMask::STENCIL, + buffer_clear_mask.contains(BufferClearMask::STENCIL), + ); + + clear_buffers(gl_context, clear_mask); + } + Command::SwapBuffers(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if let Err(err) = + surface.window_surface.swap_buffers(gl_context.context()) + { + tracing::error!("Failed to swap buffers: {err}"); + } + } + Command::CreateShaderProgram(shader_program_obj_id, shader_program) => { + if object_store.contains_non_pending_with_id(&shader_program_obj_id) { + tracing::error!( + shader_program_object_id=?shader_program_obj_id, + shader_asset_label=?shader_program_obj_id + .into_asset_id() + .and_then(|asset_id| assets.get_label_by_id(asset_id)), + "Object store already contains a object with this ID" + ); + continue; + } + + let gl_shader_program = + match create_shader_program(gl_context, &shader_program) { + Ok(gl_shader_program) => gl_shader_program, + Err(err) => { + tracing::error!("Failed to create shader program: {err}"); + continue; + } + }; + + object_store.insert( + shader_program_obj_id, + Object::from_raw( + gl_shader_program.into_raw(), + ObjectKind::ShaderProgram, + ), + ); + } + Command::ActivateShader(shader_program_obj_id) => { + let Some(shader_program_obj) = + object_store.get_shader_program_obj(&shader_program_obj_id) + else { + tracing::error!("Shader object does not exist or has a wrong kind"); + continue; + }; + + let gl_shader_program = + GlShaderProgram::from_raw(shader_program_obj.as_raw()); + + gl_shader_program.activate(gl_context); + + activated_gl_shader_program = + Some((shader_program_obj_id, gl_shader_program)); + } + Command::SetShaderBinding(binding_location, binding_value) => { + let Some((activated_gl_shader_program_obj_id, _)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; + + if let ShaderBindingValue::Texture(texture_asset) = &binding_value { + let Some(texture_obj) = object_store + .get_texture_obj(&ObjectId::Asset(texture_asset.id())) + else { + tracing::error!( + "Texture {:?} does not exist in rendering object store", + assets.get_label(texture_asset) + ); + continue; + }; + + let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); + + gl_texture + .bind_to_texture_unit(gl_context, binding_location.binding_index); + + // gl_shader_program.set_uniform_at_location( + // curr_gl_ctx, + // GlUniformLocation::from_number( + // binding_location.binding_index as i32, + // ), + // &binding_location.binding_index, + // ); + + continue; + } + + let binding_index = binding_location.binding_index; + + let uniform_buffer_objs = shader_uniform_buffer_objs + .entry(*activated_gl_shader_program_obj_id) + .or_default(); + + let uniform_buffer = + uniform_buffer_objs.entry(binding_index).or_insert_with(|| { + let uniform_buf = + opengl_bindings::buffer::Buffer::::new(gl_context); + + uniform_buf + .init( + gl_context, + binding_location.binding_size, + opengl_bindings::buffer::Usage::Dynamic, + ) + .unwrap(); + + uniform_buf + }); + + // The index into the uniform buffer binding target is for whatever + // shader program is currently bound so the uniform buffer object has + // to be re-bound so that a uniform buffer from another shader isn't + // used + uniform_buffer.bind_to_indexed_target( + gl_context, + opengl_bindings::buffer::BindingTarget::UniformBuffer, + binding_index as u32, + ); + + let fvec3_value; + + uniform_buffer + .store_at_byte_offset( + gl_context, + binding_location.byte_offset, + match binding_value { + ShaderBindingValue::Uint(ref value) => value.as_bytes(), + ShaderBindingValue::Int(ref value) => value.as_bytes(), + ShaderBindingValue::Float(ref value) => value.as_bytes(), + ShaderBindingValue::FVec3(value) => { + fvec3_value = CF32Vec3::from(value); + + fvec3_value.as_bytes() + } + ShaderBindingValue::FMat4x4(ref value) => { + value.items().as_bytes() + } + ShaderBindingValue::Texture(_) => unreachable!(), + }, + ) + .unwrap(); + } + Command::CreateTexture(texture_asset) => { + if let Err(err) = create_texture_object( + gl_context, + &mut object_store, + &assets, + &texture_asset, + ) { + tracing::error!("Failed to create texture object: {err}"); + } + } + Command::CreateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + if object_store.contains_non_pending_with_id(&mesh_object_id) { + tracing::error!( + mesh_object_id=?mesh_object_id, + mesh_asset_label=?mesh_object_id + .into_asset_id() + .and_then(|asset_id| assets.get_label_by_id(asset_id)), + "Object store already contains a object with this ID" + ); + continue; + } + + let Some((ObjectId::Asset(curr_shader_program_asset_id), _)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; + + let curr_shader_program_metadata = shader_context + .get_program_metadata(curr_shader_program_asset_id) + .expect("Not possible"); + + let Some(vertex_desc) = &curr_shader_program_metadata.vertex_desc else { + tracing::error!( + "Current shader program does not have a vertex description" + ); + continue; + }; + + let key = *next_graphics_ctx_object_key; + + let mesh = match mesh_object_id { + ObjectId::Asset(mesh_asset_id) => match mesh.as_ref() { + Some(mesh) => mesh, + None => { + let Some(mesh) = + assets.get(&AssetHandle::from_id(mesh_asset_id)) + else { + tracing::error!( + asset_id=?mesh_asset_id, + "Mesh asset does not exist" + ); + continue; + }; + + mesh + } + }, + ObjectId::Sequential(_) => { + let Some(mesh) = mesh.as_ref() else { + tracing::error!( + object_id=?mesh_object_id, + "Object ID is sequential but no mesh data is given" + ); + continue; + }; + + mesh + } + }; + + let graphics_mesh = match GraphicsMesh::new( + gl_context, + &mesh, + mesh_usage, + &vertex_desc, + ) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!("Failed to create mesh: {err}"); + continue; + } + }; + + graphics_ctx_objects.insert( + key, + GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id: ObjectId::Asset( + *curr_shader_program_asset_id, + ), + }, + ); + + object_store.insert( + mesh_object_id, + Object::from_raw(key, ObjectKind::ImplementationSpecific), + ); + + *next_graphics_ctx_object_key += 1; + } + Command::UpdateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + let Some(mesh_graphics_ctx_obj_key) = object_store + .get_obj(&mesh_object_id) + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.get_mut(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id: _, + } = mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + if let Err(err) = graphics_mesh.update(gl_context, &mesh, mesh_usage) { + tracing::error!("Failed to update mesh: {err}"); + } + } + Command::RemoveMesh(mesh_object_id) => { + let Some(mesh_graphics_ctx_obj_key) = object_store + .remove(&mesh_object_id) + .flatten() + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.remove(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { mesh: mut graphics_mesh, .. } = + mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + graphics_mesh.destroy(gl_context); + } + Command::DrawMesh(mesh_object_id, draw_mesh_opts) => { + let Some(mesh_graphics_ctx_obj_key) = object_store + .get_obj(&mesh_object_id) + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.get(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id, + } = mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + if Some(compatible_shader_program_obj_id) + != activated_gl_shader_program.as_ref().map( + |(activated_gl_shader_program_obj_id, _)| { + activated_gl_shader_program_obj_id + }, + ) + { + tracing::error!(concat!( + "Activated shader program is not the ", + "compatible shader program of the mesh" + )); + continue; + } + + if let Err(err) = draw_mesh(gl_context, graphics_mesh, &draw_mesh_opts) { + tracing::error!("Failed to draw mesh: {err}"); + }; + } + Command::UpdateDrawProperties(draw_props, draw_props_update_flags) => { + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) + { + opengl_bindings::misc::set_polygon_mode( + gl_context, + draw_props.polygon_mode_config.face, + draw_props.polygon_mode_config.mode, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) + { + set_enabled( + gl_context, + Capability::Blend, + draw_props.blending_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) + { + gl_blending_configure( + gl_context, + GlBlendingConfig::default() + .with_source_factor(blending_factor_to_gl( + draw_props.blending_config.source_factor, + )) + .with_destination_factor(blending_factor_to_gl( + draw_props.blending_config.destination_factor, + )) + .with_equation(blending_equation_to_gl( + draw_props.blending_config.equation, + )), + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) + { + set_enabled( + gl_context, + Capability::DepthTest, + draw_props.depth_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) + { + set_enabled( + gl_context, + Capability::ScissorTest, + draw_props.scissor_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) + { + gl_define_scissor_box( + gl_context, + draw_props.scissor_box.lower_left_corner_pos.into(), + draw_props + .scissor_box + .size + .unwrap_or_else(|| { + let (_, viewport_size) = gl_get_viewport(gl_context); + + Dimens:: { + width: viewport_size + .width + .try_into() + .expect("Viewport width too large"), + height: viewport_size + .height + .try_into() + .expect("Viewport height too large"), + } + }) + .into(), + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) + { + set_enabled( + gl_context, + Capability::CullFace, + draw_props.face_culling_enabled, + ); + } + } + } + } +} + +fn create_gl_context( + gl_config: &GlutinConfig, + graphics_props: &GraphicsProperties, + window_handle: WindowHandle<'_>, + surface: &GlutinSurface, +) -> Result +{ + let display = gl_config.display(); + + let glutin_context = unsafe { + display.create_context( + gl_config, + &glutin::context::ContextAttributesBuilder::new() + .with_debug(graphics_props.debug) + .build(Some(window_handle.as_raw())), + ) + } + .map_err(CreateGlContextError::CreateGlutinContext)?; + + MaybeCurrentContextWithFns::new(glutin_context, &surface) + .map_err(CreateGlContextError::MakeContextCurrent) +} + +#[derive(Debug, thiserror::Error)] +enum CreateGlContextError +{ + #[error("Glutin context creation failed")] + CreateGlutinContext(#[source] GlutinError), + + #[error("Making GL context current failed")] + MakeContextCurrent(#[source] GlMakeContextCurrentError), +} + +#[tracing::instrument(skip_all)] +fn create_texture_object( + curr_gl_ctx: &MaybeCurrentContextWithFns, + object_store: &mut ObjectStore, + assets: &Assets, + texture_asset: &AssetHandle, +) -> Result<(), GlTextureGenerateError> +{ + let object_id = ObjectId::Asset(texture_asset.id()); + + if object_store.contains_non_pending_with_id(&object_id) { + tracing::error!( + texture_object_id=?object_id, + texture_asset_label=?assets.get_label(texture_asset), + " object store already contains object with this ID" + ); + return Ok(()); + } + + let Some(texture) = assets.get(&texture_asset) else { + tracing::error!("Texture asset is not loaded",); + return Ok(()); + }; + + let texture_image = match texture.image.color_type() { + ImageColorType::Rgb8 if (texture.image.dimensions().width * 3) % 4 != 0 => { + // The texture will be corrupted if the alignment of each horizontal line of + // the texture pixel array is not multiple of 4. + // + // Read more about this at + // wikis.khronos.org/opengl/Common_Mistakes#Texture_upload_and_pixel_reads + // + // To prevent this, the image is converted to RGBA8. RGBA8 images have a pixel + // size of 4 bytes so they cannot have any alignment problems + + tracing::warn!( + texture_asset = %assets + .get_label(&texture_asset) + .expect("Not possible"), + concat!( + "Converting texture image from RGB8 to RGBA8 to prevent alignment ", + "problems. This conversion may be slow. Consider changing the ", + "texture image's pixel format to RGBA8" + ) + ); + + &texture.image.to_rgba8() + } + _ => &texture.image, + }; + + object_store.insert( + object_id, + Object::from_raw( + create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)? + .into_raw(), + ObjectKind::Texture, + ), + ); + + Ok(()) +} + +fn draw_mesh( + current_context: &MaybeCurrentContextWithFns, + graphics_mesh: &GraphicsMesh, + opts: &DrawMeshOptions, +) -> Result<(), GlDrawError> +{ + graphics_mesh.vertex_arr.bind(current_context); + + if graphics_mesh.index_buffer.is_some() { + VertexArray::draw_elements( + current_context, + opengl_bindings::vertex_array::DrawElementsOptions { + primitive_kind: PrimitiveKind::Triangles, + element_offset: opts.element_offset, + element_cnt: opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + vertex_offset: opts.vertex_offset, + }, + )?; + } else { + VertexArray::draw_arrays( + current_context, + PrimitiveKind::Triangles, + opts.vertex_offset, + opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + )?; + } + + Ok(()) +} + +fn create_gl_texture( + current_context: &MaybeCurrentContextWithFns, + image: &Image, + texture_properties: &TextureProperties, +) -> Result +{ + let gl_texture = GlTexture::new(current_context); + + gl_texture.generate( + current_context, + &image.dimensions().into(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, + if image.color_space_is_srgb() { + GlTextureColorSpace::Srgb + } else { + GlTextureColorSpace::Linear + }, + )?; + + gl_texture.set_wrap( + current_context, + texture_wrapping_to_gl(texture_properties.wrap), + ); + + gl_texture.set_magnifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.magnifying_filter), + ); + + gl_texture.set_minifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.minifying_filter), + ); + + Ok(gl_texture) +} + +fn create_shader_program( + current_context: &MaybeCurrentContextWithFns, + shader_program: &ShaderProgram, +) -> Result +{ + let shader_program_reflection = shader_program.reflection(0).expect("Not possible"); + + let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex) + })?; + + let vertex_shader_entry_point_code = shader_program + .get_entry_point_code(vs_entry_point_index.try_into().expect( + "Vertex shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Vertex, + entrypoint: vs_entry_point_reflection + .name() + .map(|name| name.to_string().into()) + .unwrap_or("(none)".into()), + })?; + + let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment) + })?; + + let fragment_shader_entry_point_code = shader_program + .get_entry_point_code(fs_entry_point_index.try_into().expect( + "Fragment shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Fragment, + entrypoint: fs_entry_point_reflection + .name() + .map(|name| name.to_string().into()) + .unwrap_or("(none)".into()), + })?; + + let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); + + vertex_shader.set_source( + current_context, + &vertex_shader_entry_point_code.as_str().unwrap(), + )?; + + vertex_shader.compile(current_context)?; + + let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); + + fragment_shader.set_source( + current_context, + &fragment_shader_entry_point_code.as_str().unwrap(), + )?; + + fragment_shader.compile(current_context)?; + + let gl_shader_program = GlShaderProgram::new(current_context); + + gl_shader_program.attach(current_context, &vertex_shader); + gl_shader_program.attach(current_context, &fragment_shader); + + gl_shader_program.link(current_context)?; + + Ok(gl_shader_program) +} + +#[derive(Debug, thiserror::Error)] +enum CreateShaderError +{ + #[error( + "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}" + )] + GetShaderEntryPointCodeFailed + { + #[source] + err: ShaderError, + stage: ShaderStage, + entrypoint: Cow<'static, str>, + }, + + #[error("No entrypoint was found for shader stage {0:?}")] + NoShaderStageEntrypointFound(ShaderStage), + + #[error(transparent)] + ShaderError(#[from] GlShaderError), +} + +#[tracing::instrument(skip_all)] +fn opengl_debug_message_cb( + source: MessageSource, + ty: MessageType, + id: u32, + severity: MessageSeverity, + message: &str, +) +{ + use std::backtrace::{Backtrace, BacktraceStatus}; + + use tracing::{event, Level}; + + macro_rules! create_event { + ($level: expr) => { + event!($level, ?source, ?ty, id, ?severity, message); + }; + } + + if matches!(severity, MessageSeverity::Notification) { + return; + } + + match ty { + MessageType::Error => { + create_event!(Level::ERROR); + + let backtrace = Backtrace::capture(); + + if matches!(backtrace.status(), BacktraceStatus::Captured) { + tracing::error!("{backtrace}"); + // event!(Level::TRACE, "{backtrace}"); + } + } + MessageType::Other => { + create_event!(Level::INFO); + } + _ => { + create_event!(Level::WARN); + } + }; +} + +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping +{ + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } +} + +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering +{ + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} + +impl From> for opengl_bindings::data_types::Vec2 +{ + fn from(vec2: Vec2) -> Self + { + Self { x: vec2.x, y: vec2.y } + } +} + +impl From> + for opengl_bindings::data_types::Vec3 +{ + fn from(vec3: Vec3) -> Self + { + Self { x: vec3.x, y: vec3.y, z: vec3.z } + } +} + +impl From> + for opengl_bindings::data_types::Matrix +{ + fn from(matrix: Matrix) -> Self + { + Self { items: matrix.items } + } +} + +impl From> for opengl_bindings::data_types::Dimens +{ + fn from(dimens: Dimens) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} + +impl From for opengl_bindings::misc::PolygonMode +{ + fn from(mode: crate::draw_flags::PolygonMode) -> Self + { + match mode { + crate::draw_flags::PolygonMode::Point => Self::Point, + crate::draw_flags::PolygonMode::Fill => Self::Fill, + crate::draw_flags::PolygonMode::Line => Self::Line, + } + } +} + +impl From for opengl_bindings::misc::PolygonModeFace +{ + fn from(face: crate::draw_flags::PolygonModeFace) -> Self + { + match face { + crate::draw_flags::PolygonModeFace::Front => Self::Front, + crate::draw_flags::PolygonModeFace::Back => Self::Back, + crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, + } + } +} + +#[derive(Debug, IntoBytes, Immutable)] +#[repr(C)] +pub struct CF32Vec3 +{ + x: f32, + y: f32, + z: f32, +} + +impl From> for CF32Vec3 +{ + fn from(src: Vec3) -> Self + { + Self { x: src.x, y: src.y, z: src.z } + } +} + +fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor +{ + match blending_factor { + BlendingFactor::Zero => GlBlendingFactor::Zero, + BlendingFactor::One => GlBlendingFactor::One, + BlendingFactor::SrcColor => GlBlendingFactor::SrcColor, + BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor, + BlendingFactor::DstColor => GlBlendingFactor::DstColor, + BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor, + BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha, + BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha, + BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha, + BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha, + BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor, + BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor, + BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha, + BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha, + } +} + +fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation +{ + match blending_equation { + BlendingEquation::Add => GlBlendingEquation::Add, + BlendingEquation::Subtract => GlBlendingEquation::Subtract, + BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract, + BlendingEquation::Min => GlBlendingEquation::Min, + BlendingEquation::Max => GlBlendingEquation::Max, + } +} diff --git a/engine/src/rendering/opengl/glutin_compat.rs b/engine/src/rendering/opengl/glutin_compat.rs new file mode 100644 index 0000000..cfd6ea7 --- /dev/null +++ b/engine/src/rendering/opengl/glutin_compat.rs @@ -0,0 +1,268 @@ +// Original file: +// https://github.com/rust-windowing/glutin/blob/ +// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs +// +// Copyright © 2022 Kirill Chibisov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! This library provides helpers for cross-platform [`glutin`] bootstrapping +//! with [`winit`]. + +#![deny(rust_2018_idioms)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::all)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![cfg_attr(clippy, deny(warnings))] + +use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::display::{Display, DisplayApiPreference}; +use glutin::error::Error as GlutinError; +#[cfg(x11_platform)] +use glutin::platform::x11::X11GlConfigExt; +use glutin::prelude::*; +use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; + +use crate::windowing::window::CreationAttributes as WindowCreationAttributes; + +#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))] +compile_error!("Please select at least one api backend"); + +/// The helper to perform [`Display`] creation and OpenGL platform +/// bootstrapping with the help of [`winit`] with little to no platform specific +/// code. +/// +/// This is only required for the initial setup. If you want to create +/// additional windows just use the [`finalize_window`] function and the +/// configuration you've used either for the original window or picked with the +/// existing [`Display`]. +/// +/// [`winit`]: winit +/// [`Display`]: glutin::display::Display +#[derive(Default, Debug, Clone)] +pub struct DisplayBuilder +{ + preference: ApiPreference, + window_attributes: WindowCreationAttributes, +} + +impl DisplayBuilder +{ + /// Create new display builder. + pub fn new() -> Self + { + Default::default() + } + + /// The preference in picking the configuration. + #[allow(dead_code)] + pub fn with_preference(mut self, preference: ApiPreference) -> Self + { + self.preference = preference; + self + } + + /// The window attributes to use when building a window. + /// + /// By default no window is created. + pub fn with_window_attributes( + mut self, + window_creation_attrs: WindowCreationAttributes, + ) -> Self + { + self.window_attributes = window_creation_attrs; + self + } + + /// Initialize the OpenGL platform and create a compatible window to use + /// with it when the [`WindowAttributes`] was passed with + /// [`Self::with_window_attributes()`]. It's optional, since on some + /// platforms like `Android` it is not available early on, so you want to + /// find configuration and later use it with the [`finalize_window`]. + /// But if you don't care about such platform you can always pass + /// [`WindowAttributes`]. + /// + /// # Api-specific + /// + /// **WGL:** - [`WindowAttributes`] **must** be passed in + /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, + /// otherwise only builtin functions like `glClear` will be available. + pub fn build( + self, + window_handle: Option>, + display_handle: &DisplayHandle<'_>, + template_builder: ConfigTemplateBuilder, + config_picker_fn: ConfigPickerFn, + ) -> Result<(WindowCreationAttributes, Config), Error> + where + ConfigPickerFn: FnOnce(Box + '_>) -> Option, + { + // XXX with WGL backend window should be created first. + let raw_window_handle = if cfg!(wgl_backend) { + let Some(window_handle) = window_handle else { + return Err(Error::WindowRequired); + }; + + Some(window_handle.as_raw()) + } else { + None + }; + + let gl_display = + create_display(display_handle, self.preference, raw_window_handle) + .map_err(Error::CreateDisplayFailed)?; + + // XXX the native window must be passed to config picker when WGL is used + // otherwise very limited OpenGL features will be supported. + #[cfg(wgl_backend)] + let template_builder = if let Some(raw_window_handle) = raw_window_handle { + template_builder.compatible_with_native_window(raw_window_handle) + } else { + template_builder + }; + + let template = template_builder.build(); + + // SAFETY: The RawWindowHandle passed on the config template + // (when cfg(wgl_backend)) will always point to a valid object since it is + // derived from the window_handle argument which when Some is a WindowHandle and + // WindowHandles always point to a valid object + let gl_configs = unsafe { gl_display.find_configs(template) } + .map_err(Error::FindConfigsFailed)?; + + let picked_gl_config = + config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; + + #[cfg(not(wgl_backend))] + let window_attrs = + { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) }; + + #[cfg(wgl_backend)] + let window_attrs = self.window_attributes; + + Ok((window_attrs, picked_gl_config)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to create display")] + CreateDisplayFailed(#[source] GlutinError), + + #[error("Failed to find configs")] + FindConfigsFailed(#[source] GlutinError), + + #[error("No config was picked by config picker function")] + NoConfigPicked, + + #[error("Window required for building display on current platform")] + WindowRequired, +} + +fn create_display( + display_handle: &DisplayHandle<'_>, + _api_preference: ApiPreference, + _raw_window_handle: Option, +) -> Result +{ + #[cfg(egl_backend)] + let _preference = DisplayApiPreference::Egl; + + #[cfg(glx_backend)] + let _preference = DisplayApiPreference::Glx(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )); + + #[cfg(cgl_backend)] + let _preference = DisplayApiPreference::Cgl; + + #[cfg(wgl_backend)] + let _preference = DisplayApiPreference::Wgl(_raw_window_handle); + + #[cfg(all(egl_backend, glx_backend))] + let _preference = match _api_preference { + ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + }; + + #[cfg(all(wgl_backend, egl_backend))] + let _preference = match _api_preference { + ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle), + ApiPreference::FallbackEgl => { + DisplayApiPreference::WglThenEgl(_raw_window_handle) + } + }; + + let handle = display_handle.as_raw(); + unsafe { Ok(Display::new(handle, _preference)?) } +} + +/// Finalize [`Window`] creation by applying the options from the [`Config`], be +/// aware that it could remove incompatible options from the window builder like +/// `transparency`, when the provided config doesn't support it. +/// +/// [`Window`]: winit::window::Window +/// [`Config`]: glutin::config::Config +#[cfg(not(wgl_backend))] +fn finalize_window_creation_attrs( + mut attributes: WindowCreationAttributes, + gl_config: &Config, +) -> WindowCreationAttributes +{ + // Disable transparency if the end config doesn't support it. + if gl_config.supports_transparency() == Some(false) { + attributes = attributes.with_transparent(false); + } + + #[cfg(x11_platform)] + let attributes = if let Some(x11_visual) = gl_config.x11_visual() { + attributes.with_x11_visual(x11_visual.visual_id() as _) + } else { + attributes + }; + + attributes +} + +/// Simplified version of the [`DisplayApiPreference`] which is used to simplify +/// cross platform window creation. +/// +/// To learn about platform differences the [`DisplayApiPreference`] variants. +/// +/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ApiPreference +{ + /// Prefer `EGL` over system provider like `GLX` and `WGL`. + PreferEgl, + + /// Fallback to `EGL` when failed to create the system profile. + /// + /// This behavior is used by default. However consider using + /// [`Self::PreferEgl`] if you don't care about missing EGL features. + #[default] + FallbackEgl, +} diff --git a/engine/src/rendering/opengl/graphics_mesh.rs b/engine/src/rendering/opengl/graphics_mesh.rs new file mode 100644 index 0000000..4933197 --- /dev/null +++ b/engine/src/rendering/opengl/graphics_mesh.rs @@ -0,0 +1,216 @@ +use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; +use opengl_bindings::vertex_array::{ + AttributeFormat as GlVertexArrayAttributeFormat, + BindVertexBufferError as GlVertexArrayBindVertexBufferError, + DataType as GlVertexArrayDataType, + VertexArray as GlVertexArray, + VertexBufferSpec as GlVertexArrayVertexBufferSpec, +}; +use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns; + +use crate::mesh::{Mesh, VertexAttrType}; +use crate::rendering::MeshUsage; +use crate::shader::VertexDescription as ShaderVertexDescription; + +#[derive(Debug)] +pub struct GraphicsMesh +{ + /// Vertex and index buffer has to live as long as the vertex array + vertex_buffer: GlBuffer, + pub index_buffer: Option>, + pub element_cnt: u32, + pub vertex_arr: GlVertexArray, +} + +impl GraphicsMesh +{ + #[tracing::instrument(skip_all)] + pub fn new( + current_context: &GlCurrentContextWithFns, + mesh: &Mesh, + mesh_usage: MeshUsage, + vertex_desc: &ShaderVertexDescription, + ) -> Result + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + let vertex_arr = GlVertexArray::new(current_context); + let vertex_buffer = GlBuffer::new(current_context); + + vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + let vertex_buf_binding_index = 0; + + if let Err(err) = vertex_arr.bind_vertex_buffer( + current_context, + vertex_buf_binding_index, + &vertex_buffer, + GlVertexArrayVertexBufferSpec { + offset: 0, + vertex_size: mesh.vertex_buf().vertex_size(), + }, + ) { + match err { + GlVertexArrayBindVertexBufferError::OffsetValueTooLarge { + value: _, + max_value: _, + } => unreachable!(), + GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge { + value, + max_value, + } => { + panic!( + "Size of vertex ({}) is too large. Must be less than {max_value}", + value + ); + } + } + } + + for vertex_attr_props in mesh.vertex_buf().vertex_attr_props() { + let vertex_field_desc = vertex_desc + .fields + .iter() + .find(|vertex_field_desc| { + *vertex_field_desc.name == vertex_attr_props.name + }) + .unwrap(); + + let attrib_index: u32 = + vertex_field_desc.varying_input_offset.try_into().unwrap(); + + vertex_arr.enable_attrib(current_context, attrib_index); + + vertex_arr.set_attrib_format( + current_context, + attrib_index, + match &vertex_attr_props.ty { + VertexAttrType::Float32 => GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: 1, + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + }, + VertexAttrType::Float32Array { length } => { + GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: (*length).try_into().unwrap(), + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + } + } + }, + ); + + vertex_arr.set_attrib_vertex_buf_binding( + current_context, + attrib_index, + vertex_buf_binding_index, + ); + } + + if let Some(indices) = mesh.indices() { + let index_buffer = GlBuffer::new(current_context); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + vertex_arr.bind_element_buffer(current_context, &index_buffer); + + return Ok(Self { + vertex_buffer: vertex_buffer, + index_buffer: Some(index_buffer), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), + vertex_arr, + }); + } + + Ok(Self { + vertex_buffer: vertex_buffer, + index_buffer: None, + element_cnt: mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), + vertex_arr, + }) + } + + pub fn update( + &mut self, + current_context: &GlCurrentContextWithFns, + mesh: &Mesh, + mesh_usage: MeshUsage, + ) -> Result<(), Error> + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + self.vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + if let Some(indices) = mesh.indices() { + let index_buffer = self + .index_buffer + .get_or_insert_with(|| GlBuffer::new(current_context)); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + self.vertex_arr + .bind_element_buffer(current_context, &index_buffer); + + self.element_cnt = indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"); + + return Ok(()); + } + + self.element_cnt = mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"); + + Ok(()) + } + + pub fn destroy(&mut self, curr_gl_ctx: &GlCurrentContextWithFns) + { + self.vertex_arr.delete(curr_gl_ctx); + self.vertex_buffer.delete(curr_gl_ctx); + + if let Some(index_buffer) = &self.index_buffer { + index_buffer.delete(curr_gl_ctx); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to store vertices in vertex buffer")] + StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), + + #[error("Failed to store indices in index buffer")] + StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), +} + +fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage +{ + match mesh_usage { + MeshUsage::Stream => GlBufferUsage::Stream, + MeshUsage::Static => GlBufferUsage::Static, + MeshUsage::Dynamic => GlBufferUsage::Dynamic, + } +} diff --git a/engine/src/shader/default.rs b/engine/src/shader/default.rs index 8144fdf..1dc85fc 100644 --- a/engine/src/shader/default.rs +++ b/engine/src/shader/default.rs @@ -14,7 +14,7 @@ use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::model::{MaterialSearchResult, Model}; use crate::projection::{ClipVolume as ProjectionClipVolume, Projection}; -use crate::renderer::{PendingShaderBindings, SurfaceSpec}; +use crate::rendering::{PendingShaderBindings, SurfaceSpec}; use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor}; use crate::shader::{ Context as ShaderContext, -- cgit v1.2.3-18-g5258