diff options
Diffstat (limited to 'engine/src/renderer')
| -rw-r--r-- | engine/src/renderer/object.rs | 112 | ||||
| -rw-r--r-- | engine/src/renderer/opengl.rs | 1578 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glutin_compat.rs | 268 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/graphics_mesh.rs | 130 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/vertex.rs | 9 |
5 files changed, 1735 insertions, 362 deletions
diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs new file mode 100644 index 0000000..d8bb2e3 --- /dev/null +++ b/engine/src/renderer/object.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; +use std::collections::hash_map::Entry as HashMapEntry; + +use ecs::Component; + +use crate::asset::Id as AssetId; + +/// Renderer object ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Id +{ + Asset(AssetId), + Other(u64), +} + +/// Renderer object store. +#[derive(Debug, Default, Component)] +pub struct Store +{ + objects: HashMap<Id, Object>, +} + +impl Store +{ + pub fn get_obj(&self, id: &Id) -> Option<&Object> + { + self.objects.get(id) + } + + 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 contains_with_id(&self, id: &Id) -> bool + { + self.objects.contains_key(id) + } + + pub fn insert(&mut self, id: Id, object: Object) + { + self.objects.insert(id, object); + } + + pub fn entry(&mut self, id: Id) -> StoreEntry<'_> + { + StoreEntry { inner: self.objects.entry(id) } + } +} + +#[derive(Debug)] +pub struct StoreEntry<'store> +{ + inner: HashMapEntry<'store, Id, Object>, +} + +impl<'store> StoreEntry<'store> +{ + pub fn or_insert(self, default_obj: Object) -> &'store mut Object + { + self.inner.or_insert(default_obj) + } + + pub fn or_insert_with( + self, + default_func: impl FnOnce() -> Object, + ) -> &'store mut Object + { + self.inner.or_insert_with(default_func) + } +} + +/// Renderer object. +#[derive(Debug, Clone)] +pub struct Object +{ + raw: u32, + kind: Kind, +} + +impl Object +{ + pub fn from_raw(raw: u32, kind: Kind) -> Self + { + Self { raw, kind } + } + + pub fn as_raw(&self) -> u32 + { + self.raw + } + + pub fn kind(&self) -> Kind + { + self.kind + } +} + +/// Renderer object kind. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Kind +{ + Texture, + Mesh, +} diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 43ec16c..4bd67a4 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -1,87 +1,152 @@ //! OpenGL renderer. +use std::any::type_name; use std::collections::HashMap; -use std::ffi::{c_void, CString}; +use std::ffi::CString; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; -use std::ops::Deref; use std::path::Path; -use std::process::abort; use ecs::actions::Actions; -use ecs::component::local::Local; -use ecs::phase::START as START_PHASE; +use ecs::entity::obtainer::Obtainer as EntityObtainer; +use ecs::event::component::{Changed, Removed}; +use ecs::pair::{ChildOf, Pair, Wildcard}; +use ecs::phase::Phase; use ecs::query::term::Without; use ecs::sole::Single; -use ecs::system::{Into as _, System}; -use ecs::{Component, Query}; - -use crate::camera::{Active as ActiveCamera, Camera}; -use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; -use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; -use crate::material::{Flags as MaterialFlags, Material}; -use crate::matrix::Matrix; -use crate::mesh::Mesh; -use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; -use crate::opengl::debug::{ - enable_debug_output, - set_debug_message_callback, - set_debug_message_control, +use ecs::system::observer::Observe; +use ecs::{Component, Query, declare_entity}; +use glutin::display::GetGlDisplay; +use glutin::prelude::GlDisplay; +use glutin::surface::{ + GlSurface, + Surface as GlutinSurface, + WindowSurface as GlutinWindowSurface, +}; +use opengl_bindings::debug::{ MessageIdsAction, MessageSeverity, MessageSource, MessageType, + SetDebugMessageControlError as GlSetDebugMessageControlError, + set_debug_message_callback, + set_debug_message_control, }; -use crate::opengl::glsl::{ - preprocess as glsl_preprocess, - PreprocessingError as GlslPreprocessingError, +use opengl_bindings::misc::{ + BufferClearMask as GlBufferClearMask, + Capability, + SetViewportError as GlSetViewportError, + clear_buffers, + enable, + set_enabled, }; -use crate::opengl::shader::{ +use opengl_bindings::shader::{ Error as GlShaderError, Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; -use crate::opengl::texture::{ - set_active_texture_unit, +use opengl_bindings::texture::{ + Filtering as GlTextureFiltering, + GenerateError as GlTextureGenerateError, + PixelDataFormat as GlTexturePixelDataFormat, Texture as GlTexture, - TextureUnit, + Wrapping as GlTextureWrapping, }; -use crate::opengl::vertex_array::{ - DataType as VertexArrayDataType, +use opengl_bindings::vertex_array::{ + DrawError as GlDrawError, PrimitiveKind, VertexArray, }; -use crate::opengl::{ - clear_buffers, - enable, - get_context_flags as get_opengl_context_flags, - BufferClearMask, - Capability, - ContextFlags, +use opengl_bindings::{ContextWithFns, CurrentContextWithFns}; +use safer_ffi::layout::ReprC; + +use crate::asset::{Assets, Id as AssetId}; +use crate::camera::Camera; +use crate::color::Color; +use crate::data_types::dimens::Dimens; +use crate::image::{ColorType as ImageColorType, Image}; +use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; +use crate::material::{Flags as MaterialFlags, Material}; +use crate::matrix::Matrix; +use crate::model::Model; +use crate::opengl::glsl::{ + PreprocessingError as GlslPreprocessingError, + preprocess as glsl_preprocess, }; use crate::projection::{ClipVolume, Projection}; -use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex}; -use crate::renderer::RENDER_PHASE; -use crate::texture::{Id as TextureId, Texture}; -use crate::transform::{Position, Scale}; -use crate::util::{defer, Defer, RefOrValue}; +use crate::renderer::object::{ + Id as RendererObjectId, + Kind as RendererObjectKind, + Object as RendererObject, + 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, + CtxUsedByWindow as RendererCtxUsedByWindow, + GraphicsProperties, + RENDER_PHASE, + SurfaceId, + SurfaceSpec, + WindowUsingRendererCtx, +}; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Wrapping as TextureWrapping, +}; +use crate::transform::WorldPosition; use crate::vector::{Vec2, Vec3}; -use crate::window::Window; +use crate::windowing::Context as WindowingContext; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady, + Window, +}; +mod glutin_compat; +mod graphics_mesh; mod vertex; -type RenderableEntity<'a> = ( - &'a Mesh, - &'a Material, - Option<&'a MaterialFlags>, - Option<&'a Position>, - Option<&'a Scale>, - Option<&'a DrawFlags>, - Option<&'a GlObjects>, +const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0; +const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1; +const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2; + +const DEFAULT_TEXTURE_OBJECT_ID: RendererObjectId = RendererObjectId::Other(0xaa); + +declare_entity!( + pub POST_RENDER_PHASE, + (Phase, Pair::builder().relation::<ChildOf>().target_id(*RENDER_PHASE).build()) ); +#[derive(Debug, Component)] +struct WindowGlConfig +{ + gl_config: glutin::config::Config, +} + +#[derive(Component)] +struct GraphicsContext +{ + gl_context: ContextWithFns, + shader_program: Option<GlShaderProgram>, + graphics_mesh_store: GraphicsMeshStore, + surfaces: HashMap<SurfaceId, GlutinSurface<GlutinWindowSurface>>, +} + +#[derive(Debug, Default)] +struct GraphicsMeshStore +{ + graphics_meshes: HashMap<AssetId, GraphicsMesh>, +} + #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} @@ -90,207 +155,953 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { - collector.add_system(*START_PHASE, initialize); + collector.add_declared_entity(&RENDER_PHASE); + collector.add_declared_entity(&POST_RENDER_PHASE); - collector.add_system( - *RENDER_PHASE, - render - .into_system() - .initialize((GlobalGlObjects::default(),)), - ); + collector.add_system(*RENDER_PHASE, super::enqueue_commands); + collector.add_system(*RENDER_PHASE, handle_commands); + + collector.add_system(*POST_RENDER_PHASE, prepare_windows); + collector.add_system(*POST_RENDER_PHASE, init_window_graphics); + + collector.add_observer(handle_model_removed); + + collector.add_observer(handle_window_changed); + collector.add_observer(handle_window_removed); } } -fn initialize(window: Single<Window>) +#[tracing::instrument(skip_all)] +fn handle_model_removed( + observe: Observe<Pair<Removed, Model>>, + renderer_ctx_query: Query<( + &mut GraphicsContext, + Pair<RendererCtxUsedByWindow, Wildcard>, + )>, + assets: Single<Assets>, +) { - window - .make_context_current() - .expect("Failed to make window context current"); + for evt_match in &observe { + let model_ent_id = evt_match.id(); + + for (renderer_ctx_ent_id, (mut graphics_ctx, renderer_ctx_used_by_window)) in + renderer_ctx_query.iter_with_euids() + { + let GraphicsContext { + ref gl_context, + ref mut graphics_mesh_store, + ref surfaces, + .. + } = *graphics_ctx; + + let model = evt_match.get_removed_comp(); + + let Some(model_spec) = assets.get(&model.spec_asset) else { + continue; + }; - gl::load_with(|symbol| match window.get_proc_address(symbol) { - Ok(addr) => addr as *const c_void, - Err(err) => { - println!( - "FATAL ERROR: Failed to get adress of OpenGL function {symbol}: {err}", + let Some(mesh_asset) = &model_spec.mesh_asset else { + continue; + }; + + if !graphics_mesh_store + .graphics_meshes + .contains_key(&mesh_asset.id()) + { + continue; + } + + let Some(window_ent) = renderer_ctx_used_by_window.get_target_ent() else { + tracing::error!( + window_entity_id = %renderer_ctx_used_by_window.id().target_entity(), + "Window entity does not exist" + ); + continue; + }; + + let Some(surface_spec) = window_ent.get::<SurfaceSpec>() else { + tracing::error!( + window_entity_id = %window_ent.uid(), + "Window entity does not have a {} component", + type_name::<SurfaceSpec>() + ); + continue; + }; + + let Some(surface) = surfaces.get(&surface_spec.id) else { + tracing::error!( + window_entity_id = %window_ent.uid(), + "Surface specified by window entity's {} component does not exist", + type_name::<SurfaceSpec>() + ); + continue; + }; + + let curr_gl_ctx = match gl_context.make_current(surface) { + Ok(curr_gl_ctx) => curr_gl_ctx, + Err(err) => { + tracing::error!("{err}"); + continue; + } + }; + + tracing::debug!( + model_entity_id=%model_ent_id, + renderer_ctx_entity_id=%renderer_ctx_ent_id, + "Cleaning up after model in renderer context" ); - abort(); + let Some(mut graphics_mesh) = + graphics_mesh_store.graphics_meshes.remove(&mesh_asset.id()) + else { + tracing::warn!( + model_entity_id=%model_ent_id, + "No mesh exists for model" + ); + continue; + }; + + graphics_mesh.destroy(&curr_gl_ctx); } - }); + } +} + +#[tracing::instrument(skip_all)] +fn handle_window_changed( + observe: Observe<Pair<Changed, Window>>, + entity_obtainer: EntityObtainer, +) +{ + for evt_match in &observe { + let window_ent = evt_match.get_ent_infallible(); + + tracing::trace!( + new_state = ?evt_match.get_changed_comp(), + "Handling window change" + ); + + let Some(surface_spec) = window_ent.get::<SurfaceSpec>() else { + continue; + }; + + let Some(renderer_ctx_ent_id) = window_ent + .get_matching_components( + Pair::builder() + .relation::<WindowUsingRendererCtx>() + .target_id(Wildcard::uid()) + .build() + .id(), + ) + .next() + .map(|comp_ref| comp_ref.id().target_entity()) + else { + continue; + }; + + let Some(renderer_ctx_ent) = entity_obtainer.get_entity(renderer_ctx_ent_id) + else { + tracing::error!("Renderer context entity does not exist"); + continue; + }; - if get_opengl_context_flags().contains(ContextFlags::DEBUG) { - initialize_debug(); + let Some(graphics_context) = renderer_ctx_ent.get::<GraphicsContext>() else { + tracing::error!( + "Renderer context entity does not have a GraphicsContext component" + ); + continue; + }; + + let Some(surface) = graphics_context.surfaces.get(&surface_spec.id) else { + tracing::error!( + window_entity_id = %window_ent.uid(), + "Surface specified by window entity's {} component does not exist", + type_name::<SurfaceSpec>() + ); + continue; + }; + + let Ok(current_graphics_context) = + graphics_context.gl_context.make_current(surface) + else { + tracing::error!("Failed to make graphics context current"); + continue; + }; + + if let Err(err) = set_viewport( + ¤t_graphics_context, + Vec2::default(), + evt_match.get_changed_comp().inner_size(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } } +} + +#[tracing::instrument(skip_all)] +fn handle_window_removed(observe: Observe<Pair<Removed, Window>>, mut actions: Actions) +{ + for evt_match in &observe { + let window_ent_id = evt_match.id(); + + let window_ent = evt_match.get_ent_infallible(); - let window_size = window.size().expect("Failed to get window size"); + tracing::debug!( + entity_id = %window_ent_id, + title = %evt_match.get_removed_comp().title, + "Handling removal of window" + ); - set_viewport(Vec2 { x: 0, y: 0 }, window_size); + actions.remove_comps::<(SurfaceSpec, WindowGlConfig)>(window_ent_id); - window.set_framebuffer_size_callback(|new_window_size| { - set_viewport(Vec2::ZERO, new_window_size); - }); + let Some(with_renderer_ctx_pair) = window_ent + .get_first_wildcard_pair_match::<WindowUsingRendererCtx, Wildcard>() + else { + tracing::warn!( + "Window entity is missing a ({}, *) pair", + type_name::<WindowUsingRendererCtx>() + ); + continue; + }; - enable(Capability::DepthTest); - enable(Capability::MultiSample); + let renderer_context_ent_id = with_renderer_ctx_pair.id().target_entity(); + + actions.remove_comps::<(GraphicsContext, RendererObjectStore)>( + renderer_context_ent_id, + ); + + actions.remove_components( + renderer_context_ent_id, + [Pair::builder() + .relation::<RendererCtxUsedByWindow>() + .target_id(window_ent_id) + .build() + .id()], + ); + + actions.remove_components(window_ent_id, [with_renderer_ctx_pair.id()]); + } } -#[allow(clippy::too_many_arguments)] -fn render( - query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, - point_light_query: Query<(&PointLight,)>, - directional_lights: Query<(&DirectionalLight,)>, - camera_query: Query<(&Camera, &Position, &ActiveCamera)>, - window: Single<Window>, - global_light: Single<GlobalLight>, - mut gl_objects: Local<GlobalGlObjects>, +#[derive(Debug, Component)] +struct SetupFailed; + +fn prepare_windows( + window_query: Query< + ( + Option<&Window>, + &mut WindowCreationAttributes, + Option<&GraphicsProperties>, + ), + ( + Without<CreationReady>, + Without<WindowGlConfig>, + Without<WindowClosed>, + Without<SetupFailed>, + ), + >, + windowing_context: Single<WindowingContext>, mut actions: Actions, ) { - let Some((camera, camera_pos, _)) = camera_query.iter().next() else { - tracing::warn!("No current camera. Nothing will be rendered"); + let Some(display_handle) = windowing_context.display_handle() else { return; }; - let point_lights = point_light_query - .iter() - .map(|(point_light,)| point_light) - .collect::<Vec<_>>(); + for (window_ent_id, (window, mut window_creation_attrs, graphics_props)) in + window_query.iter_with_euids() + { + tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); - let directional_lights = directional_lights.iter().collect::<Vec<_>>(); + let mut glutin_config_template_builder = + glutin::config::ConfigTemplateBuilder::new(); - let GlobalGlObjects { - shader_program, - textures: gl_textures, - } = &mut *gl_objects; + let graphics_props = match graphics_props.as_ref() { + Some(graphics_props) => &*graphics_props, + None => { + actions.add_components(window_ent_id, (GraphicsProperties::default(),)); - let shader_program = - shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); + &GraphicsProperties::default() + } + }; - clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); + if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { + glutin_config_template_builder = glutin_config_template_builder + .with_multisampling(multisampling_sample_cnt); + } - for ( - euid, - (mesh, material, material_flags, position, scale, draw_flags, gl_objects), - ) in query.iter_with_euids() - { - let material_flags = material_flags - .map(|material_flags| material_flags.clone()) - .unwrap_or_default(); + 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}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + }; - let gl_objs = match gl_objects.as_deref() { - Some(gl_objs) => RefOrValue::Ref(gl_objs), - None => RefOrValue::Value(Some(GlObjects::new(&mesh))), + 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}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } }; - defer!(|gl_objs| { - if let RefOrValue::Value(opt_gl_objs) = gl_objs { - actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); + *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, &GraphicsProperties), + (Without<SurfaceSpec>, Without<SetupFailed>), + >, + mut actions: Actions, + windowing_context: Single<WindowingContext>, +) +{ + for (window_ent_id, (window, window_gl_config, graphics_props)) in + window_query.iter_with_euids() + { + tracing::info!("Initializing graphics for window {window_ent_id}"); + + 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" + ); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + actions.add_components(window_ent_id, (SetupFailed,)); + continue; + } }; - }); - apply_transformation_matrices( - Transformation { - position: position.map(|pos| *pos).unwrap_or_default().position, - scale: scale.map(|scale| *scale).unwrap_or_default().scale, - }, - shader_program, - &camera, - &camera_pos, - window.size().expect("Failed to get window size"), - ); + 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; + }; - apply_light( - &material, - &material_flags, - &global_light, - shader_program, - point_lights.as_slice(), - directional_lights - .iter() - .map(|(dir_light,)| &**dir_light) - .collect::<Vec<_>>() - .as_slice(), - &camera_pos, - ); + let 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(surface) => surface, + Err(err) => { + tracing::error!("Failed to create window surface: {err}"); + continue; + } + }; - for (index, texture) in material.textures.iter().enumerate() { - let gl_texture = gl_textures - .entry(texture.id()) - .or_insert_with(|| create_gl_texture(texture)); + let context = match unsafe { + display.create_context( + &window_gl_config.gl_config, + &glutin::context::ContextAttributesBuilder::new() + .with_debug(graphics_props.debug) + .build(Some(window_handle.as_raw())), + ) + } { + Ok(context) => context, + Err(err) => { + tracing::error!("Failed to create graphics context: {err}"); + continue; + } + }; - let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); + let gl_context = match ContextWithFns::new(context, &surface) { + Ok(context) => context, + Err(err) => { + tracing::error!("Failed to create graphics context: {err}"); + continue; + } + }; - set_active_texture_unit(texture_unit); + let Ok(curr_gl_context) = gl_context.make_current(&surface) else { + tracing::error!("Failed to make graphics context current"); + continue; + }; - gl_texture.bind(); + if let Err(err) = + set_viewport(&curr_gl_context, Vec2 { x: 0, y: 0 }, window.inner_size()) + { + tracing::error!("Failed to set viewport: {err}"); } - shader_program.activate(); + set_enabled( + &curr_gl_context, + Capability::DepthTest, + graphics_props.depth_test, + ); - if let Some(draw_flags) = &draw_flags { - crate::opengl::set_polygon_mode( - draw_flags.polygon_mode_config.face, - draw_flags.polygon_mode_config.mode, - ); + set_enabled( + &curr_gl_context, + Capability::MultiSample, + graphics_props.multisampling_sample_cnt.is_some(), + ); + + if graphics_props.debug { + enable(&curr_gl_context, Capability::DebugOutput); + enable(&curr_gl_context, Capability::DebugOutputSynchronous); + + set_debug_message_callback(&curr_gl_context, opengl_debug_message_cb); + + match set_debug_message_control( + &curr_gl_context, + None, + None, + None, + &[], + MessageIdsAction::Disable, + ) { + Ok(()) => {} + Err(GlSetDebugMessageControlError::TooManyIds { + id_cnt: _, + max_id_cnt: _, + }) => { + unreachable!() // No ids are given + } + } } - draw_mesh(gl_objs.get().unwrap()); + let surface_id = SurfaceId::new_unique(); - if draw_flags.is_some() { - let default_polygon_mode_config = PolygonModeConfig::default(); + let renderer_ctx_ent_id = actions.spawn(( + GraphicsContext { + gl_context, + shader_program: None, + graphics_mesh_store: GraphicsMeshStore::default(), + surfaces: HashMap::from([(surface_id, surface)]), + }, + RendererObjectStore::default(), + RendererCommandQueue::default(), + Pair::builder() + .relation::<RendererCtxUsedByWindow>() + .target_id(window_ent_id) + .build(), + )); + + actions.add_components( + window_ent_id, + ( + SurfaceSpec { id: surface_id }, + Pair::builder() + .relation::<WindowUsingRendererCtx>() + .target_id(renderer_ctx_ent_id) + .build(), + ), + ); + } +} - crate::opengl::set_polygon_mode( - default_polygon_mode_config.face, - default_polygon_mode_config.mode, - ); +#[tracing::instrument(skip_all)] +fn handle_commands( + renderer_ctx_query: Query<( + &mut GraphicsContext, + &mut RendererObjectStore, + &mut RendererCommandQueue, + )>, + assets: Single<Assets>, +) +{ + for (mut graphics_ctx, mut renderer_object_store, mut command_queue) in + &renderer_ctx_query + { + let GraphicsContext { + ref gl_context, + ref mut shader_program, + ref mut graphics_mesh_store, + ref surfaces, + } = *graphics_ctx; + + let mut opt_curr_gl_ctx: Option<CurrentContextWithFns> = None; + + let mut opt_curr_camera: Option<(Camera, WorldPosition)> = None; + + for command in command_queue.drain() { + let tracing_span = tracing::info_span!("handle_cmd", command = ?command); + let _tracing_span_enter = tracing_span.enter(); + + match command { + RendererCommand::MakeCurrent(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + let curr_gl_ctx = match gl_context.make_current(surface) { + Ok(current_graphics_context) => current_graphics_context, + Err(err) => { + tracing::error!( + "Failed to make graphics context current: {err}" + ); + continue; + } + }; + + opt_curr_gl_ctx = Some(curr_gl_ctx); + } + RendererCommand::ClearBuffers(buffer_clear_mask) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + 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(&curr_gl_ctx, 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.swap_buffers(gl_context.context()) { + tracing::error!("Failed to swap buffers: {err}"); + } + } + RendererCommand::UseShader => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let _shader_program = shader_program.get_or_insert_with(|| { + create_default_shader_program(&curr_gl_ctx).unwrap() + }); + } + RendererCommand::ActivateShader => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program) = shader_program else { + tracing::error!("Shader does not exist"); + continue; + }; + + shader_program.activate(&curr_gl_ctx); + } + RendererCommand::UseCamera(camera, camera_world_pos) => { + opt_curr_camera = Some((camera, camera_world_pos)); + } + RendererCommand::ApplyTransform { transform, window_size } => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program) = shader_program else { + tracing::error!("Shader does not exist"); + continue; + }; + + let Some((camera, camera_world_pos)) = &opt_curr_camera else { + tracing::error!("No current camera"); + continue; + }; + + apply_transformation_matrices( + &curr_gl_ctx, + Transformation { + position: transform.position, + scale: transform.scale, + }, + shader_program, + &camera, + &camera_world_pos, + &window_size, + ); + } + RendererCommand::SetShaderDirectionalLights(directional_lights) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program) = shader_program else { + tracing::error!("Shader does not exist"); + continue; + }; + + set_shader_directional_lights( + curr_gl_ctx, + shader_program, + &directional_lights, + ); + } + RendererCommand::SetShaderPointLights(point_lights) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program) = shader_program else { + tracing::error!("Shader does not exist"); + continue; + }; + + set_shader_point_lights(curr_gl_ctx, shader_program, &point_lights); + } + RendererCommand::CreateTexture(texture) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(texture_image) = assets.get(&texture.asset_handle) else { + continue; + }; + + if let Err(err) = create_texture_object( + curr_gl_ctx, + &mut renderer_object_store, + texture.asset_handle.id(), + texture_image, + &texture.properties, + ) { + tracing::error!("Failed to create texture object: {err}"); + } + } + RendererCommand::UseMaterial { + material_asset, + material_flags, + global_light, + } => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program) = shader_program else { + tracing::error!("Shader does not exist"); + continue; + }; + + let Some((_, camera_world_pos)) = &opt_curr_camera else { + tracing::error!("No current camera"); + continue; + }; + + let material = match material_asset.as_ref() { + Some(material_asset) => { + let Some(material) = assets.get(&material_asset) else { + continue; + }; + + material + } + None => &Material::default(), + }; + + set_shader_material( + curr_gl_ctx, + material, + &material_flags, + &global_light, + shader_program, + camera_world_pos, + ); + + bind_material_textures( + curr_gl_ctx, + material, + &mut renderer_object_store, + ); + } + RendererCommand::DrawMesh { mesh_asset } => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let graphics_mesh = + match graphics_mesh_store.graphics_meshes.get(&mesh_asset.id()) { + Some(graphics_mesh) => graphics_mesh, + None => { + let Some(mesh) = assets.get(&mesh_asset) else { + tracing::trace!("Missing model asset"); + continue; + }; + + let graphics_mesh = + match GraphicsMesh::new(&curr_gl_ctx, mesh) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!( + "Failed to create {}: {err}", + type_name::<GraphicsMesh>() + ); + + continue; + } + }; + + graphics_mesh_store + .graphics_meshes + .entry(mesh_asset.id()) + .or_insert(graphics_mesh) + } + }; + + if let Err(err) = draw_mesh(&curr_gl_ctx, graphics_mesh) { + tracing::error!("Failed to draw mesh: {err}"); + }; + } + RendererCommand::SetPolygonModeConfig(polygon_mode_config) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + opengl_bindings::misc::set_polygon_mode( + &curr_gl_ctx, + polygon_mode_config.face, + polygon_mode_config.mode, + ); + } + } } } } -#[derive(Debug, Default, Component)] -struct GlobalGlObjects +fn create_default_texture(current_context: &CurrentContextWithFns<'_>) -> GlTexture { - shader_program: Option<GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, + match create_gl_texture( + current_context, + &Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8), + &TextureProperties::default(), + ) { + Ok(gl_texture) => gl_texture, + Err( + GlTextureGenerateError::SizeWidthValueTooLarge { value: _, max_value: _ } + | GlTextureGenerateError::SizeHeightValueTooLarge { value: _, max_value: _ }, + ) => unreachable!(), + } } -fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) +#[tracing::instrument(skip_all)] +fn create_texture_object( + curr_gl_ctx: &CurrentContextWithFns<'_>, + renderer_object_store: &mut RendererObjectStore, + texture_image_asset_id: AssetId, + image: &Image, + texture_properties: &TextureProperties, +) -> Result<(), GlTextureGenerateError> { - crate::opengl::set_viewport(position, size); + let object_id = RendererObjectId::Asset(texture_image_asset_id); + + if renderer_object_store.contains_with_id(&object_id) { + tracing::error!( + texture_object_id=?object_id, + "Renderer object store already contains object with this ID" + ); + return Ok(()); + } + + renderer_object_store.insert( + object_id, + RendererObject::from_raw( + create_gl_texture(curr_gl_ctx, image, texture_properties)?.into_raw(), + RendererObjectKind::Texture, + ), + ); + + Ok(()) } -fn initialize_debug() +fn bind_material_textures( + current_context: &CurrentContextWithFns<'_>, + material: &Material, + renderer_object_store: &mut RendererObjectStore, +) { - enable_debug_output(); + let material_texture_maps = [ + (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT), + (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT), + (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT), + ]; + + for (texture, texture_unit) in material_texture_maps { + let Some(texture) = texture else { + let default_texture_obj = renderer_object_store + .entry(DEFAULT_TEXTURE_OBJECT_ID) + .or_insert_with(|| { + RendererObject::from_raw( + create_default_texture(current_context).into_raw(), + RendererObjectKind::Texture, + ) + }); + + let gl_texture = GlTexture::from_raw(default_texture_obj.as_raw()); + + gl_texture.bind_to_texture_unit(current_context, texture_unit); + + continue; + }; + + let texture_object_id = RendererObjectId::Asset(texture.asset_handle.id()); + + let Some(texture_obj) = renderer_object_store.get_texture_obj(&texture_object_id) + else { + tracing::error!( + texture_object_id=?texture_object_id, + "Texture object does not exist" + ); + continue; + }; + + let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); - set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); + gl_texture.bind_to_texture_unit(current_context, texture_unit); + } } -fn draw_mesh(gl_objects: &GlObjects) +fn set_viewport( + current_context: &CurrentContextWithFns<'_>, + position: Vec2<u32>, + size: &Dimens<u32>, +) -> Result<(), GlSetViewportError> { - gl_objects.vertex_arr.bind(); + let position = + opengl_bindings::data_types::Vec2::<u32> { x: position.x, y: position.y }; - if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); + let size = opengl_bindings::data_types::Dimens::<u32> { + width: size.width, + height: size.height, + }; + + opengl_bindings::misc::set_viewport(current_context, &position, &size) +} + +fn draw_mesh( + current_context: &CurrentContextWithFns<'_>, + graphics_mesh: &GraphicsMesh, +) -> Result<(), GlDrawError> +{ + graphics_mesh.vertex_arr.bind(current_context); + + if graphics_mesh.index_buffer.is_some() { + VertexArray::draw_elements( + current_context, + PrimitiveKind::Triangles, + 0, + graphics_mesh.element_cnt, + )?; } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); + VertexArray::draw_arrays( + current_context, + PrimitiveKind::Triangles, + 0, + graphics_mesh.element_cnt, + )?; } + + Ok(()) } -fn create_gl_texture(texture: &Texture) -> GlTexture +fn create_gl_texture( + current_context: &CurrentContextWithFns<'_>, + image: &Image, + texture_properties: &TextureProperties, +) -> Result<GlTexture, GlTextureGenerateError> { - let mut gl_texture = GlTexture::new(); + let gl_texture = GlTexture::new(current_context); gl_texture.generate( - *texture.dimensions(), - texture.image().as_bytes(), - texture.pixel_data_format(), + current_context, + &image.dimensions().into(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, + )?; + + 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.apply_properties(texture.properties()); + gl_texture.set_minifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.minifying_filter), + ); - gl_texture + Ok(gl_texture) } const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); @@ -299,32 +1110,34 @@ const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl") const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl"); const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl"); -fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError> +fn create_default_shader_program( + current_context: &CurrentContextWithFns<'_>, +) -> Result<GlShaderProgram, CreateShaderError> { - let mut vertex_shader = GlShader::new(ShaderKind::Vertex); + let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); - vertex_shader.set_source(&*glsl_preprocess( - VERTEX_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; + vertex_shader.set_source( + current_context, + &*glsl_preprocess(VERTEX_GLSL_SHADER_SRC, &get_glsl_shader_content)?, + )?; - vertex_shader.compile()?; + vertex_shader.compile(current_context)?; - let mut fragment_shader = GlShader::new(ShaderKind::Fragment); + let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); - fragment_shader.set_source(&*glsl_preprocess( - FRAGMENT_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; + fragment_shader.set_source( + current_context, + &*glsl_preprocess(FRAGMENT_GLSL_SHADER_SRC, &get_glsl_shader_content)?, + )?; - fragment_shader.compile()?; + fragment_shader.compile(current_context)?; - let mut gl_shader_program = GlShaderProgram::new(); + let gl_shader_program = GlShaderProgram::new(current_context); - gl_shader_program.attach(&vertex_shader); - gl_shader_program.attach(&fragment_shader); + gl_shader_program.attach(current_context, &vertex_shader); + gl_shader_program.attach(current_context, &fragment_shader); - gl_shader_program.link()?; + gl_shader_program.link(current_context)?; Ok(gl_shader_program) } @@ -355,108 +1168,30 @@ fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> )) } -#[derive(Debug, Component)] -struct GlObjects -{ - /// Vertex and index buffer has to live as long as the vertex array - _vertex_buffer: Buffer<Vertex>, - index_buffer: Option<Buffer<u32>>, - element_cnt: u32, - - vertex_arr: VertexArray, -} - -impl GlObjects -{ - #[tracing::instrument(skip_all)] - fn new(mesh: &Mesh) -> Self - { - tracing::trace!( - "Creating vertex array, vertex buffer{}", - if mesh.indices().is_some() { - " and index buffer" - } else { - "" - } - ); - - let mut vertex_arr = VertexArray::new(); - let mut vertex_buffer = Buffer::new(); - - vertex_buffer.store_mapped(mesh.vertices(), BufferUsage::Static, |vertex| { - Vertex { - pos: vertex.pos, - texture_coords: vertex.texture_coords, - normal: vertex.normal, - } - }); - - vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0); - - let mut offset = 0u32; - - for attrib in Vertex::attrs() { - vertex_arr.enable_attrib(attrib.index); - - vertex_arr.set_attrib_format( - attrib.index, - match attrib.component_type { - AttributeComponentType::Float => VertexArrayDataType::Float, - }, - false, - offset, - ); - - vertex_arr.set_attrib_vertex_buf_binding(attrib.index, 0); - - offset += attrib.component_size * attrib.component_cnt as u32; - } - - if let Some(indices) = mesh.indices() { - let mut index_buffer = Buffer::new(); - - index_buffer.store(indices, BufferUsage::Static); - - vertex_arr.bind_element_buffer(&index_buffer); - - return 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, - }; - } - - Self { - _vertex_buffer: vertex_buffer, - index_buffer: None, - element_cnt: mesh - .vertices() - .len() - .try_into() - .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), - vertex_arr, - } - } -} - fn apply_transformation_matrices( + current_context: &CurrentContextWithFns<'_>, transformation: Transformation, gl_shader_program: &mut GlShaderProgram, camera: &Camera, - camera_pos: &Position, - window_size: Dimens<u32>, + camera_world_pos: &WorldPosition, + window_size: &Dimens<u32>, ) { - gl_shader_program - .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation)); + gl_shader_program.set_uniform( + current_context, + c"model", + &opengl_bindings::data_types::Matrix { + items: create_transformation_matrix(transformation).items, + }, + ); - let view_matrix = create_view_matrix(camera, &camera_pos.position); + let view_matrix = create_view_matrix(camera, &camera_world_pos.position); - gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix); + gl_shader_program.set_uniform( + current_context, + c"view", + &opengl_bindings::data_types::Matrix { items: view_matrix.items }, + ); #[allow(clippy::cast_precision_loss)] let proj_matrix = match &camera.projection { @@ -464,176 +1199,231 @@ fn apply_transformation_matrices( window_size.width as f32 / window_size.height as f32, ClipVolume::NegOneToOne, ), - Projection::Orthographic(orthographic_proj) => { - orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne) - } + Projection::Orthographic(orthographic_proj) => orthographic_proj + .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne), }; - gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix); + gl_shader_program.set_uniform( + current_context, + c"projection", + &opengl_bindings::data_types::Matrix { items: proj_matrix.items }, + ); } -fn apply_light<PointLightHolder>( - material: &Material, - material_flags: &MaterialFlags, - global_light: &GlobalLight, +fn set_shader_directional_lights( + curr_gl_ctx: &CurrentContextWithFns<'_>, gl_shader_program: &mut GlShaderProgram, - point_lights: &[PointLightHolder], - directional_lights: &[&DirectionalLight], - camera_pos: &Position, -) where - PointLightHolder: Deref<Target = PointLight>, + directional_lights: &[DirectionalLight], +) { debug_assert!( - point_lights.len() < 64, - "Shader cannot handle more than 64 point lights" - ); - - debug_assert!( directional_lights.len() < 64, "Shader cannot handle more than 64 directional lights" ); for (dir_light_index, dir_light) in directional_lights.iter().enumerate() { - gl_shader_program.set_uniform_vec_3fv( + let direction: opengl_bindings::data_types::Vec3<_> = dir_light.direction.into(); + + gl_shader_program.set_uniform( + curr_gl_ctx, &create_light_uniform_name( "directional_lights", dir_light_index, "direction", ), - &dir_light.direction, + &direction, ); set_light_phong_uniforms( + curr_gl_ctx, gl_shader_program, "directional_lights", dir_light_index, - *dir_light, + dir_light, ); } // There probably won't be more than 2147483648 directional lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl_shader_program - .set_uniform_1i(c"directional_light_cnt", directional_lights.len() as i32); + gl_shader_program.set_uniform( + curr_gl_ctx, + c"directional_light_cnt", + &(directional_lights.len() as i32), + ); +} + +fn set_shader_point_lights( + curr_gl_ctx: &CurrentContextWithFns<'_>, + gl_shader_program: &mut GlShaderProgram, + point_lights: &[(PointLight, WorldPosition)], +) +{ + debug_assert!( + point_lights.len() < 64, + "Shader cannot handle more than 64 point lights" + ); + + for (point_light_index, (point_light, point_light_world_pos)) in + point_lights.iter().enumerate() + { + let pos: opengl_bindings::data_types::Vec3<_> = + (point_light_world_pos.position + point_light.local_position).into(); - for (point_light_index, point_light) in point_lights.iter().enumerate() { - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( + curr_gl_ctx, &create_light_uniform_name("point_lights", point_light_index, "position"), - &point_light.position, + &pos, ); set_light_phong_uniforms( + curr_gl_ctx, gl_shader_program, "point_lights", point_light_index, - &**point_light, + &*point_light, ); set_light_attenuation_uniforms( + curr_gl_ctx, gl_shader_program, "point_lights", point_light_index, - point_light, + &*point_light, ); } // There probably won't be more than 2147483648 point lights #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] - gl_shader_program.set_uniform_1i(c"point_light_cnt", point_lights.len() as i32); + gl_shader_program.set_uniform( + curr_gl_ctx, + c"point_light_cnt", + &(point_lights.len() as i32), + ); +} - gl_shader_program.set_uniform_vec_3fv( - c"material.ambient", - &if material_flags.use_ambient_color { +fn set_shader_material( + curr_gl_ctx: &CurrentContextWithFns<'_>, + material: &Material, + material_flags: &MaterialFlags, + global_light: &GlobalLight, + gl_shader_program: &mut GlShaderProgram, + camera_world_pos: &WorldPosition, +) +{ + let ambient: opengl_bindings::data_types::Vec3<_> = + Vec3::from(if material_flags.use_ambient_color { material.ambient.clone() } else { global_light.ambient.clone() - } - .into(), - ); + }) + .into(); - gl_shader_program - .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into()); + gl_shader_program.set_uniform(curr_gl_ctx, c"material.ambient", &ambient); - #[allow(clippy::cast_possible_wrap)] - gl_shader_program - .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into()); + let diffuse: opengl_bindings::data_types::Vec3<_> = + Vec3::from(material.diffuse.clone()).into(); + + gl_shader_program.set_uniform(curr_gl_ctx, c"material.diffuse", &diffuse); - let texture_map = material - .textures - .iter() - .enumerate() - .map(|(index, texture)| (texture.id(), index)) - .collect::<HashMap<_, _>>(); + let specular: opengl_bindings::data_types::Vec3<_> = + Vec3::from(material.specular.clone()).into(); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( + gl_shader_program.set_uniform(curr_gl_ctx, c"material.specular", &specular); + + #[allow(clippy::cast_possible_wrap)] + gl_shader_program.set_uniform( + curr_gl_ctx, c"material.ambient_map", - *texture_map.get(&material.ambient_map).unwrap() as i32, + &(AMBIENT_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( + gl_shader_program.set_uniform( + curr_gl_ctx, c"material.diffuse_map", - *texture_map.get(&material.diffuse_map).unwrap() as i32, + &(DIFFUSE_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( + gl_shader_program.set_uniform( + curr_gl_ctx, c"material.specular_map", - *texture_map.get(&material.specular_map).unwrap() as i32, + &(SPECULAR_MAP_TEXTURE_UNIT as i32), + ); + + gl_shader_program.set_uniform( + curr_gl_ctx, + c"material.shininess", + &material.shininess, ); - gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess); + let view_pos: opengl_bindings::data_types::Vec3<_> = camera_world_pos.position.into(); - gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position); + gl_shader_program.set_uniform(curr_gl_ctx, c"view_pos", &view_pos); } fn set_light_attenuation_uniforms( + current_context: &CurrentContextWithFns<'_>, gl_shader_program: &mut GlShaderProgram, light_array: &str, light_index: usize, light: &PointLight, ) { - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name( light_array, light_index, "attenuation_props.constant", ), - light.attenuation_params.constant, + &light.attenuation_params.constant, ); - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"), - light.attenuation_params.linear, + &light.attenuation_params.linear, ); - gl_shader_program.set_uniform_1fv( + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name( light_array, light_index, "attenuation_props.quadratic", ), - light.attenuation_params.quadratic, + &light.attenuation_params.quadratic, ); } fn set_light_phong_uniforms( + current_context: &CurrentContextWithFns<'_>, gl_shader_program: &mut GlShaderProgram, light_array: &str, light_index: usize, light: &impl Light, ) { - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "phong.diffuse"), - &light.diffuse().clone().into(), + &opengl_bindings::data_types::Vec3 { + x: light.diffuse().red, + y: light.diffuse().green, + z: light.diffuse().blue, + }, ); - gl_shader_program.set_uniform_vec_3fv( + gl_shader_program.set_uniform( + current_context, &create_light_uniform_name(light_array, light_index, "phong.specular"), - &light.specular().clone().into(), + &opengl_bindings::data_types::Vec3 { + x: light.specular().red, + y: light.specular().green, + z: light.specular().blue, + }, ); } @@ -702,7 +1492,7 @@ fn opengl_debug_message_cb( { use std::backtrace::{Backtrace, BacktraceStatus}; - use tracing::{event, Level}; + use tracing::{Level, event}; macro_rules! create_event { ($level: expr) => { @@ -721,7 +1511,8 @@ fn opengl_debug_message_cb( let backtrace = Backtrace::capture(); if matches!(backtrace.status(), BacktraceStatus::Captured) { - event!(Level::TRACE, "{backtrace}"); + tracing::error!("{backtrace}"); + // event!(Level::TRACE, "{backtrace}"); } } MessageType::Other => { @@ -749,3 +1540,74 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4 matrix } + +#[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<Value: ReprC + Copy> From<Vec2<Value>> for opengl_bindings::data_types::Vec2<Value> +{ + fn from(vec2: Vec2<Value>) -> Self + { + Self { x: vec2.x, y: vec2.y } + } +} + +impl<Value: ReprC + Copy> From<Vec3<Value>> for opengl_bindings::data_types::Vec3<Value> +{ + fn from(vec3: Vec3<Value>) -> Self + { + Self { x: vec3.x, y: vec3.y, z: vec3.z } + } +} + +impl<Value: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value> +{ + fn from(dimens: Dimens<Value>) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} + +impl From<crate::draw_flags::PolygonMode> 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<crate::draw_flags::PolygonModeFace> 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, + } + } +} diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs new file mode 100644 index 0000000..cfd6ea7 --- /dev/null +++ b/engine/src/renderer/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<ConfigPickerFn>( + self, + window_handle: Option<WindowHandle<'_>>, + display_handle: &DisplayHandle<'_>, + template_builder: ConfigTemplateBuilder, + config_picker_fn: ConfigPickerFn, + ) -> Result<(WindowCreationAttributes, Config), Error> + where + ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>, + { + // 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<RawWindowHandle>, +) -> Result<Display, GlutinError> +{ + #[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 new file mode 100644 index 0000000..dc839a8 --- /dev/null +++ b/engine/src/renderer/opengl/graphics_mesh.rs @@ -0,0 +1,130 @@ +use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns; +use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; +use opengl_bindings::vertex_array::{ + DataType as GlVertexArrayDataType, + VertexArray as GlVertexArray, +}; + +use crate::mesh::Mesh; +use crate::renderer::opengl::vertex::{ + AttributeComponentType as VertexAttributeComponentType, + Vertex as RendererVertex, +}; + +#[derive(Debug)] +pub struct GraphicsMesh +{ + /// Vertex and index buffer has to live as long as the vertex array + vertex_buffer: GlBuffer<RendererVertex>, + pub index_buffer: Option<GlBuffer<u32>>, + pub element_cnt: u32, + pub vertex_arr: GlVertexArray, +} + +impl GraphicsMesh +{ + #[tracing::instrument(skip_all)] + pub fn new( + current_context: &GlCurrentContextWithFns<'_>, + mesh: &Mesh, + ) -> Result<Self, Error> + { + tracing::trace!( + "Creating vertex array, vertex buffer{}", + if mesh.indices().is_some() { + " and index buffer" + } else { + "" + } + ); + + let vertex_arr = GlVertexArray::new(current_context); + let vertex_buffer = GlBuffer::new(current_context); + + vertex_buffer + .store_mapped( + current_context, + mesh.vertices(), + GlBufferUsage::Static, + |vertex| RendererVertex { + pos: vertex.pos.into(), + texture_coords: vertex.texture_coords.into(), + normal: vertex.normal.into(), + }, + ) + .map_err(Error::StoreVerticesFailed)?; + + vertex_arr.bind_vertex_buffer(current_context, 0, &vertex_buffer, 0); + + let mut offset = 0u32; + + for attrib in RendererVertex::attrs() { + vertex_arr.enable_attrib(current_context, attrib.index); + + vertex_arr.set_attrib_format( + current_context, + attrib.index, + match attrib.component_type { + VertexAttributeComponentType::Float => GlVertexArrayDataType::Float, + }, + false, + offset, + ); + + vertex_arr.set_attrib_vertex_buf_binding(current_context, attrib.index, 0); + + offset += attrib.component_size * attrib.component_cnt as u32; + } + + if let Some(indices) = mesh.indices() { + let index_buffer = GlBuffer::new(current_context); + + index_buffer + .store(current_context, indices, GlBufferUsage::Static) + .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 + .vertices() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), + vertex_arr, + }) + } + + 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), +} diff --git a/engine/src/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs index 499b94b..5a1593e 100644 --- a/engine/src/renderer/opengl/vertex.rs +++ b/engine/src/renderer/opengl/vertex.rs @@ -1,12 +1,13 @@ -use crate::vector::{Vec2, Vec3}; +use safer_ffi::derive_ReprC; #[derive(Debug, Clone)] +#[derive_ReprC] #[repr(C)] pub struct Vertex { - pub pos: Vec3<f32>, - pub texture_coords: Vec2<f32>, - pub normal: Vec3<f32>, + pub pos: opengl_bindings::data_types::Vec3<f32>, + pub texture_coords: opengl_bindings::data_types::Vec2<f32>, + pub normal: opengl_bindings::data_types::Vec3<f32>, } impl Vertex |
