//! OpenGL renderer. use std::any::type_name; use std::collections::HashMap; use std::ffi::CString; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::path::Path; use ecs::actions::Actions; use ecs::component::Handle as ComponentHandle; 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::observer::Observe; use ecs::{declare_entity, Component, Query}; use glutin::display::GetGlDisplay; use glutin::prelude::GlDisplay; use glutin::surface::GlSurface; 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, enable, set_enabled, BufferClearMask, Capability, SetViewportError as GlSetViewportError, }; use opengl_bindings::shader::{ Error as GlShaderError, Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, }; use opengl_bindings::texture::{ 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::{ContextWithFns, CurrentContextWithFns}; use safer_ffi::layout::ReprC; use crate::asset::{Assets, Id as AssetId}; 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::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::{ preprocess as glsl_preprocess, PreprocessingError as GlslPreprocessingError, }; use crate::projection::{ClipVolume, Projection}; use crate::renderer::opengl::glutin_compat::{ DisplayBuilder, Error as GlutinCompatError, }; use crate::renderer::opengl::graphics_mesh::GraphicsMesh; use crate::renderer::{GraphicsProperties, RENDER_PHASE}; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, Wrapping as TextureWrapping, }; use crate::transform::{Scale, WorldPosition}; use crate::util::MapVec; 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; mod vertex; const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0; const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1; const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2; type RenderableEntity<'a> = ( &'a Model, Option<&'a MaterialFlags>, Option<&'a WorldPosition>, Option<&'a Scale>, Option<&'a DrawFlags>, &'a [Pair], ); declare_entity!( pub POST_RENDER_PHASE, (Phase, Pair::builder().relation::().target_id(*RENDER_PHASE).build()) ); #[derive(Debug, Component)] struct WithGraphicsContext; #[derive(Debug, Component)] struct WindowGlConfig { gl_config: glutin::config::Config, } #[derive(Debug, Component)] struct WindowGraphicsSurface { surface: glutin::surface::Surface, } #[derive(Component)] struct GraphicsContext { context: ContextWithFns, shader_program: Option, textures_objs: HashMap, default_1x1_texture_obj: Option, graphics_mesh_store: GraphicsMeshStore, } #[derive(Debug, Default)] struct GraphicsMeshStore { graphics_meshes: MapVec, next_id: GraphicsMeshId, } impl GraphicsMeshStore { fn insert(&mut self, graphics_mesh: GraphicsMesh) -> GraphicsMeshId { let id = self.next_id; self.graphics_meshes.insert(id, graphics_mesh); self.next_id.inner += 1; id } } #[derive(Debug, Component)] struct DataInGraphicsContext { graphics_mesh_id: GraphicsMeshId, } #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { collector.add_declared_entity(&RENDER_PHASE); collector.add_declared_entity(&POST_RENDER_PHASE); collector.add_system(*RENDER_PHASE, render); 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); } } #[tracing::instrument(skip_all)] fn handle_model_removed(observe: Observe>, mut actions: Actions) { for evt_match in &observe { let ent_id = evt_match.id(); tracing::debug!(entity_id=%ent_id, "Cleaning up after model"); let ent = evt_match.get_ent_infallible(); for data_in_graphics_ctx_pair in ent.get_wildcard_pair_matches::() { actions.remove_components(ent_id, [data_in_graphics_ctx_pair.id()]); let Some(graphics_context_ent) = data_in_graphics_ctx_pair.get_target_ent() else { tracing::trace!( concat!( "Graphics context referenced by pair ({}, {}) does not exist. ", "Skipping cleanup of this model" ), type_name::(), data_in_graphics_ctx_pair.id().target_entity() ); continue; }; let Some(data_in_graphics_ctx) = data_in_graphics_ctx_pair.get_data_as_relation() else { unreachable!(); }; let Some(mut graphics_context) = graphics_context_ent.get_mut::() else { tracing::trace!( "Graphics context entity {} does not have a {} component", graphics_context_ent.uid(), type_name::() ); continue; }; graphics_context .graphics_mesh_store .graphics_meshes .remove(data_in_graphics_ctx.graphics_mesh_id); } } } #[tracing::instrument(skip_all)] fn handle_window_changed( observe: Observe>, 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(window_graphics_surface) = window_ent.get::() else { continue; }; let Some(graphics_context_ent_id) = window_ent .get_matching_components( Pair::builder() .relation::() .target_id(Wildcard::uid()) .build() .id(), ) .next() .map(|comp_ref| comp_ref.id().target_entity()) else { continue; }; let Some(graphics_context_ent) = entity_obtainer.get_entity(graphics_context_ent_id) else { tracing::error!("Graphics context entity does not exist"); continue; }; let Some(graphics_context) = graphics_context_ent.get::() else { tracing::error!( "Graphics context entity does not have a GraphicsContext component" ); continue; }; let Ok(current_graphics_context) = graphics_context .context .make_current(&window_graphics_surface.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>, mut actions: Actions) { for evt_match in &observe { let window_ent_id = evt_match.id(); let window_ent = evt_match.get_ent_infallible(); tracing::debug!( entity_id = %window_ent_id, title = %evt_match.get_removed_comp().title, "Handling removal of window" ); actions.remove_comps::<(WindowGraphicsSurface, WindowGlConfig)>(window_ent_id); let Some(with_graphics_ctx_pair_handle) = window_ent.get_first_wildcard_pair_match::() else { tracing::warn!("Window entity is missing a (WithGraphicsContext, *) pair"); continue; }; let graphics_context_ent_id = with_graphics_ctx_pair_handle.id().target_entity(); actions.remove_comps::<(GraphicsContext,)>(graphics_context_ent_id); actions.remove_components(window_ent_id, [with_graphics_ctx_pair_handle.id()]); } } #[derive(Debug, Component)] struct SetupFailed; // fn on_window_creation_attrs_added( // observe: Observe>, // windowing: Single, // window_store: Single, // mut actions: Actions, // ) // { // for evt_match in &observe { // let Some(ent) = evt_match.get_entity() else { // unreachable!(); // }; // // if ent.has_component(WindowGlConfig::id()) || // ent.has_component(WindowClosed::id()) || ent.has_component() {} } // } fn prepare_windows( window_query: Query< ( Option<&Window>, &mut WindowCreationAttributes, Option<&GraphicsProperties>, ), ( Without, Without, Without, Without, ), >, windowing_context: Single, mut actions: Actions, ) { let Some(display_handle) = windowing_context.display_handle() else { return; }; 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 mut glutin_config_template_builder = glutin::config::ConfigTemplateBuilder::new(); let graphics_props = match graphics_props.as_ref() { Some(graphics_props) => &*graphics_props, None => { actions.add_components(window_ent_id, (GraphicsProperties::default(),)); &GraphicsProperties::default() } }; 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}"); actions.add_components(window_ent_id, (SetupFailed,)); 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}"); actions.add_components(window_ent_id, (SetupFailed,)); continue; } }; *window_creation_attrs = new_window_creation_attrs; // let gl_config_template = glutin_config_template_builder.build(); // // let display = match glutin_winit_compat::create_display( // unsafe { engine_display.as_display_handle() }, // glutin_winit_compat::ApiPreference::default(), // None, // ) { // Ok(gl_display) => gl_display, // Err(err) => { // tracing::error!("Failed to create graphics platform display: {err}"); // continue; // } // }; // // let mut gl_configs = match unsafe { display.find_configs(gl_config_template) } // { Ok(gl_configs) => gl_configs, // Err(err) => { // tracing::error!("Failed to find GL configs: {err:?}"); // continue; // } // }; // // let Some(first_gl_config) = gl_configs.next() else { // tracing::error!("No matching GL configuration exists"); // continue; // }; // // *window_creation_attrs = finalize_window_creation_attrs( // window_creation_attrs.clone(), // &first_gl_config, // ); 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, Without), >, mut actions: Actions, windowing_context: Single, ) { 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; } }; 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 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; } }; 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 context = match ContextWithFns::new(context, &surface) { Ok(context) => context, Err(err) => { tracing::error!("Failed to create graphics context: {err}"); continue; } }; let Ok(current_graphics_context) = context.make_current(&surface) else { tracing::error!("Failed to make graphics context current"); continue; }; if let Err(err) = set_viewport( ¤t_graphics_context, Vec2 { x: 0, y: 0 }, window.inner_size(), ) { tracing::error!("Failed to set viewport: {err}"); } set_enabled( ¤t_graphics_context, Capability::DepthTest, graphics_props.depth_test, ); set_enabled( ¤t_graphics_context, Capability::MultiSample, graphics_props.multisampling_sample_cnt.is_some(), ); if graphics_props.debug { enable(¤t_graphics_context, Capability::DebugOutput); enable( ¤t_graphics_context, Capability::DebugOutputSynchronous, ); set_debug_message_callback( ¤t_graphics_context, opengl_debug_message_cb, ); match set_debug_message_control( ¤t_graphics_context, None, None, None, &[], MessageIdsAction::Disable, ) { Ok(()) => {} Err(GlSetDebugMessageControlError::TooManyIds { id_cnt: _, max_id_cnt: _, }) => { unreachable!() // No ids are given } } } let graphics_context_ent_id = actions.spawn((GraphicsContext { context, shader_program: None, textures_objs: HashMap::new(), default_1x1_texture_obj: None, graphics_mesh_store: GraphicsMeshStore::default(), },)); actions.add_components( window_ent_id, ( WindowGraphicsSurface { surface }, Pair::builder() .relation::() .target_id(graphics_context_ent_id) .build(), ), ); } } #[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] fn render( query: Query, (Without,)>, point_light_query: Query<(&PointLight, &WorldPosition)>, directional_lights: Query<(&DirectionalLight,)>, camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, window_query: Query<( &Window, &WindowGraphicsSurface, &GraphicsProperties, Pair, )>, global_light: Single, assets: Single, mut actions: Actions, ) { for ( window_ent_id, (window, window_graphics_surface, window_graphics_props, graphics_context_pair), ) in window_query.iter_with_euids() { let Some(graphics_context_ent) = graphics_context_pair.get_target_ent() else { tracing::error!("Window's associated graphics context entity does not exist"); actions.remove_components(window_ent_id, [graphics_context_pair.id()]); continue; }; let Some(mut graphics_context) = graphics_context_ent.get_mut::() else { tracing::error!( "Graphics context entity does not have a GraphicsContext component" ); return; }; let GraphicsContext { ref context, ref mut shader_program, ref mut textures_objs, ref mut default_1x1_texture_obj, ref mut graphics_mesh_store, } = *graphics_context; let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { tracing::warn!("No current camera. Nothing will be rendered"); return; }; let Ok(current_graphics_context) = context.make_current(&window_graphics_surface.surface) else { tracing::error!("Failed to make graphics context current"); continue; }; let directional_lights = directional_lights.iter().collect::>(); let shader_program = shader_program.get_or_insert_with(|| { create_default_shader_program(¤t_graphics_context).unwrap() }); let mut clear_mask = BufferClearMask::COLOR; clear_mask.set(BufferClearMask::DEPTH, window_graphics_props.depth_test); clear_buffers(¤t_graphics_context, clear_mask); for ( euid, ( model, material_flags, position, scale, draw_flags, data_in_graphics_ctx_pairs, ), ) in query.iter_with_euids() { let Some(model_data) = assets.get(&model.asset_handle) else { tracing::trace!("Missing model asset"); continue; }; let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); let graphics_mesh_id = match data_in_graphics_ctx_pairs .get_with_target_id(graphics_context_ent.uid()) { Some(data_in_graphics_ctx_pair) => { let Some(data_in_graphics_ctx) = data_in_graphics_ctx_pair.get_data::() else { tracing::warn!( concat!( "Pair with relation {} ({}) has no data or data with a ", "wrong type. This pair will be removed" ), type_name::(), data_in_graphics_ctx_pair.id() ); actions.remove_components(euid, [data_in_graphics_ctx_pair.id()]); continue; }; data_in_graphics_ctx.graphics_mesh_id } None => { let graphics_mesh = match GraphicsMesh::new( ¤t_graphics_context, &model_data.mesh, ) { Ok(graphics_mesh) => graphics_mesh, Err(err) => { tracing::error!( "Failed to create {}: {err}", type_name::() ); // This system should not try again actions.add_components(euid, (NoDraw,)); continue; } }; let graphics_mesh_id = graphics_mesh_store.insert(graphics_mesh); actions.add_components( euid, (Pair::builder() .relation_as_data(DataInGraphicsContext { graphics_mesh_id }) .target_id(graphics_context_ent.uid()) .build(),), ); graphics_mesh_id } }; let Some(graphics_mesh) = graphics_mesh_store.graphics_meshes.get(&graphics_mesh_id) else { tracing::error!("Graphics mesh with ID: {graphics_mesh_id:?} not found"); continue; }; apply_transformation_matrices( ¤t_graphics_context, Transformation { position: position.map(|pos| *pos).unwrap_or_default().position, scale: scale.map(|scale| *scale).unwrap_or_default().scale, }, shader_program, &camera, &camera_world_pos, window.inner_size(), ); if model_data.materials.len() > 1 { tracing::warn!(concat!( "Multiple model materials are not supported ", "so only the first material will be used" )); } let material = match model_data.materials.values().next() { Some(material) => material, None => { tracing::warn!("Model has no materials. Using default material"); &Material::default() } }; apply_light( ¤t_graphics_context, &material, &material_flags, &global_light, shader_program, (point_light_query.iter(), point_light_query.iter().count()), directional_lights .iter() .map(|(dir_light,)| &**dir_light) .collect::>() .as_slice(), &camera_world_pos, ); match create_bind_material_textures( ¤t_graphics_context, &material, &assets, textures_objs, default_1x1_texture_obj, ) { Ok(()) => {} Err(CreateBindMaterialTexturesError::MissingTextureAsset) => { continue; } Err( err @ CreateBindMaterialTexturesError::CreateTextureFailed { .. }, ) => { tracing::error!( "Creating &/ binding material textures failed: {err}" ); // This system should not try again actions.add_components(euid, (NoDraw,)); continue; } } shader_program.activate(¤t_graphics_context); if let Some(draw_flags) = &draw_flags { opengl_bindings::misc::set_polygon_mode( ¤t_graphics_context, draw_flags.polygon_mode_config.face, draw_flags.polygon_mode_config.mode, ); } if let Err(err) = draw_mesh(¤t_graphics_context, &graphics_mesh) { tracing::error!( entity_id = %euid, graphics_context_entity_id = %graphics_context_ent.uid(), "Failed to draw mesh: {err}", ); // This system should not try again actions.add_components(euid, (NoDraw,)); continue; }; if draw_flags.is_some() { let default_polygon_mode_config = PolygonModeConfig::default(); opengl_bindings::misc::set_polygon_mode( ¤t_graphics_context, default_polygon_mode_config.face, default_polygon_mode_config.mode, ); } } if let Err(err) = window_graphics_surface .surface .swap_buffers(context.context()) { tracing::error!("Failed to swap buffers: {err}"); } } } fn create_default_texture(current_context: &CurrentContextWithFns<'_>) -> 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 create_bind_material_textures( current_context: &CurrentContextWithFns<'_>, material: &Material, assets: &Assets, texture_objs: &mut HashMap, default_1x1_texture_obj: &mut Option, ) -> Result<(), CreateBindMaterialTexturesError> { 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 gl_texture = default_1x1_texture_obj .get_or_insert_with(|| create_default_texture(current_context)); gl_texture.bind_to_texture_unit(current_context, texture_unit); continue; }; let texture_image_asset_id = texture.asset_handle.id(); let gl_texture = match texture_objs.get(&texture_image_asset_id) { Some(gl_texture) => gl_texture, None => { let Some(image) = assets.get::(&texture.asset_handle) else { tracing::trace!(handle=?texture.asset_handle, "Missing texture asset"); return Err(CreateBindMaterialTexturesError::MissingTextureAsset); }; texture_objs.entry(texture_image_asset_id).or_insert( create_gl_texture(current_context, image, &texture.properties) .map_err(|err| { CreateBindMaterialTexturesError::CreateTextureFailed { err, image_asset_id: texture_image_asset_id, } })?, ) } }; gl_texture.bind_to_texture_unit(current_context, texture_unit); } Ok(()) } #[derive(Debug, thiserror::Error)] enum CreateBindMaterialTexturesError { #[error("Missing texture asset")] MissingTextureAsset, #[error("Failed to create texture from image asset with ID {image_asset_id:?}")] CreateTextureFailed { #[source] err: GlTextureGenerateError, image_asset_id: AssetId, }, } fn set_viewport( current_context: &CurrentContextWithFns<'_>, position: Vec2, size: &Dimens, ) -> Result<(), GlSetViewportError> { let position = opengl_bindings::data_types::Vec2:: { x: position.x, y: position.y }; let size = opengl_bindings::data_types::Dimens:: { 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( current_context, PrimitiveKind::Triangles, 0, graphics_mesh.element_cnt, )?; } Ok(()) } fn create_gl_texture( current_context: &CurrentContextWithFns<'_>, 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!(); } }, )?; 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) } const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); 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( current_context: &CurrentContextWithFns<'_>, ) -> Result { let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); vertex_shader.set_source( current_context, &*glsl_preprocess(VERTEX_GLSL_SHADER_SRC, &get_glsl_shader_content)?, )?; vertex_shader.compile(current_context)?; let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); fragment_shader.set_source( current_context, &*glsl_preprocess(FRAGMENT_GLSL_SHADER_SRC, &get_glsl_shader_content)?, )?; 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(transparent)] ShaderError(#[from] GlShaderError), #[error(transparent)] PreprocessingError(#[from] GlslPreprocessingError), } fn get_glsl_shader_content(path: &Path) -> Result, std::io::Error> { if path == Path::new("vertex_data.glsl") { return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec()); } if path == Path::new("light.glsl") { return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec()); } Err(IoError::new( IoErrorKind::NotFound, format!("Content for shader file {} not found", path.display()), )) } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct GraphicsMeshId { inner: usize, } fn apply_transformation_matrices( current_context: &CurrentContextWithFns<'_>, transformation: Transformation, gl_shader_program: &mut GlShaderProgram, camera: &Camera, camera_world_pos: &WorldPosition, window_size: &Dimens, ) { 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_world_pos.position); 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 { Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( window_size.width as f32 / window_size.height as f32, ClipVolume::NegOneToOne, ), Projection::Orthographic(orthographic_proj) => orthographic_proj .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne), }; gl_shader_program.set_uniform( current_context, c"projection", &opengl_bindings::data_types::Matrix { items: proj_matrix.items }, ); } fn apply_light<'point_light>( current_context: &CurrentContextWithFns<'_>, material: &Material, material_flags: &MaterialFlags, global_light: &GlobalLight, gl_shader_program: &mut GlShaderProgram, (point_light_iter, point_light_cnt): ( impl Iterator< Item = ( ComponentHandle<'point_light, PointLight>, ComponentHandle<'point_light, WorldPosition>, ), >, usize, ), directional_lights: &[&DirectionalLight], camera_world_pos: &WorldPosition, ) { debug_assert!( point_light_cnt < 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() { let direction: opengl_bindings::data_types::Vec3<_> = dir_light.direction.into(); gl_shader_program.set_uniform( current_context, &create_light_uniform_name( "directional_lights", dir_light_index, "direction", ), &direction, ); set_light_phong_uniforms( current_context, gl_shader_program, "directional_lights", dir_light_index, *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( current_context, c"directional_light_cnt", &(directional_lights.len() as i32), ); for (point_light_index, (point_light, point_light_world_pos)) in point_light_iter.enumerate() { let pos: opengl_bindings::data_types::Vec3<_> = (point_light_world_pos.position + point_light.local_position).into(); gl_shader_program.set_uniform( current_context, &create_light_uniform_name("point_lights", point_light_index, "position"), &pos, ); set_light_phong_uniforms( current_context, gl_shader_program, "point_lights", point_light_index, &*point_light, ); set_light_attenuation_uniforms( current_context, gl_shader_program, "point_lights", point_light_index, &*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( current_context, c"point_light_cnt", &(point_light_cnt as i32), ); let ambient: opengl_bindings::data_types::Vec3<_> = Vec3::from(if material_flags.use_ambient_color { material.ambient.clone() } else { global_light.ambient.clone() }) .into(); gl_shader_program.set_uniform(current_context, c"material.ambient", &ambient); let diffuse: opengl_bindings::data_types::Vec3<_> = Vec3::from(material.diffuse.clone()).into(); gl_shader_program.set_uniform(current_context, c"material.diffuse", &diffuse); let specular: opengl_bindings::data_types::Vec3<_> = Vec3::from(material.specular.clone()).into(); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform(current_context, c"material.specular", &specular); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( current_context, c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( current_context, c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32), ); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( current_context, c"material.specular_map", &(SPECULAR_MAP_TEXTURE_UNIT as i32), ); gl_shader_program.set_uniform( current_context, c"material.shininess", &material.shininess, ); let view_pos: opengl_bindings::data_types::Vec3<_> = camera_world_pos.position.into(); gl_shader_program.set_uniform(current_context, 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( current_context, &create_light_uniform_name( light_array, light_index, "attenuation_props.constant", ), &light.attenuation_params.constant, ); gl_shader_program.set_uniform( current_context, &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"), &light.attenuation_params.linear, ); gl_shader_program.set_uniform( current_context, &create_light_uniform_name( light_array, light_index, "attenuation_props.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( current_context, &create_light_uniform_name(light_array, light_index, "phong.diffuse"), &opengl_bindings::data_types::Vec3 { x: light.diffuse().red, y: light.diffuse().green, z: light.diffuse().blue, }, ); gl_shader_program.set_uniform( current_context, &create_light_uniform_name(light_array, light_index, "phong.specular"), &opengl_bindings::data_types::Vec3 { x: light.specular().red, y: light.specular().green, z: light.specular().blue, }, ); } trait Light { fn diffuse(&self) -> &Color; fn specular(&self) -> &Color; } impl Light for PointLight { fn diffuse(&self) -> &Color { &self.diffuse } fn specular(&self) -> &Color { &self.specular } } impl Light for DirectionalLight { fn diffuse(&self) -> &Color { &self.diffuse } fn specular(&self) -> &Color { &self.specular } } fn create_light_uniform_name( light_array: &str, light_index: usize, light_field: &str, ) -> CString { unsafe { CString::from_vec_with_nul_unchecked( format!("{light_array}[{light_index}].{light_field}\0").into(), ) } } fn create_view_matrix(camera: &Camera, camera_pos: &Vec3) -> Matrix { let mut view = Matrix::new(); view.look_at(&camera_pos, &camera.target, &camera.global_up); view } #[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); } }; } #[derive(Debug)] struct Transformation { position: Vec3, scale: Vec3, } fn create_transformation_matrix(transformation: Transformation) -> Matrix { let mut matrix = Matrix::new_identity(); matrix.translate(&transformation.position); matrix.scale(&transformation.scale); 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 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::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, } } }