diff options
Diffstat (limited to 'engine/src/renderer')
| -rw-r--r-- | engine/src/renderer/blending.rs | 89 | ||||
| -rw-r--r-- | engine/src/renderer/main_render_pass.rs | 241 | ||||
| -rw-r--r-- | engine/src/renderer/object.rs | 137 | ||||
| -rw-r--r-- | engine/src/renderer/opengl.rs | 1884 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glsl/fragment.glsl | 73 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glsl/light.glsl | 133 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glsl/vertex.glsl | 24 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glsl/vertex_data.glsl | 11 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/glutin_compat.rs | 268 | ||||
| -rw-r--r-- | engine/src/renderer/opengl/graphics_mesh.rs | 216 |
10 files changed, 2276 insertions, 800 deletions
diff --git a/engine/src/renderer/blending.rs b/engine/src/renderer/blending.rs new file mode 100644 index 0000000..9ae2f82 --- /dev/null +++ b/engine/src/renderer/blending.rs @@ -0,0 +1,89 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Config +{ + pub source_factor: Factor, + pub destination_factor: Factor, + pub equation: Equation, +} + +impl Default for Config +{ + fn default() -> Self + { + Self { + source_factor: Factor::One, + destination_factor: Factor::Zero, + equation: Equation::default(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Factor +{ + /// Factor will be the RGBA color `(0,0,0,0)` + Zero, + + /// Factor will be the RGBA color `(1,1,1,1)` + One, + + /// Factor will be the source color + SrcColor, + + /// Factor will be the RGBA color `(1,1,1,1) - source color` + OneMinusSrcColor, + + /// Factor will be the destination color + DstColor, + + /// Factor will be the RGBA color `(1,1,1,1) - destination color` + OneMinusDstColor, + + /// Factor will be the alpha component of the source color. + SrcAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - source color alpha` + OneMinusSrcAlpha, + + /// Factor will be the alpha component of the destination color. + DstAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - destination color alpha` + OneMinusDstAlpha, + + /// Factor will be the constant color + ConstantColor, + + /// Factor will be the RGBA color `(1,1,1,1) - constant color` + OneMinusConstantColor, + + /// Factor will be the alpha component of the constant color. + ConstantAlpha, + + /// Factor will be the RGBA color `(1,1,1,1) - constant color alpha` + OneMinusConstantAlpha, +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Equation +{ + /// The destination color and source color is added to each other in the blend + /// function + #[default] + Add, + + /// The destination color is subtracted from the source color in the blend function + Subtract, + + /// The source color is subtracted from the destination color in the blend function + ReverseSubtract, + + /// The blend function will take the component-wise minimum of the destination color + /// and the source color + Min, + + /// The blend function will take the component-wise maximum of the destination color + /// and the source color + Max, +} diff --git a/engine/src/renderer/main_render_pass.rs b/engine/src/renderer/main_render_pass.rs new file mode 100644 index 0000000..7492379 --- /dev/null +++ b/engine/src/renderer/main_render_pass.rs @@ -0,0 +1,241 @@ +use ecs::Query; +use ecs::query::term::{With, Without}; +use ecs::sole::Single; + +use crate::asset::Assets; +use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; +use crate::model::{MaterialSearchResult, Model}; +use crate::renderer::object::{Id as RendererObjectId, Store as RendererObjectStore}; +use crate::renderer::{ + BufferClearMask as RendererBufferClearMask, + Command as RendererCommand, + DrawMeshOptions as RendererDrawMeshOptions, + DrawProperties as RendererDrawProperties, + DrawPropertiesUpdateFlags as RendererDrawPropertiesUpdateFlags, + MeshUsage as RendererMeshUsage, + PendingShaderBindings, + RenderPass, + RenderPasses as RendererRenderPasses, + SurfaceSpec, +}; +use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; +use crate::shader::{ + Context as ShaderContext, + ModuleSource as ShaderModuleSource, + Shader, +}; +use crate::texture::{Texture, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL}; +use crate::windowing::window::Window; + +type RenderableEntity<'a> = ( + &'a Model, + Option<&'a DrawFlags>, + Option<&'a Shader>, + Option<&'a mut PendingShaderBindings>, +); + +#[tracing::instrument(skip_all)] +pub fn add_main_render_passes( + renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, + window_surface_spec_query: Query<(&SurfaceSpec,), (With<Window>,)>, + assets: Single<Assets>, + shader_context: Single<ShaderContext>, + mut render_passes: Single<RendererRenderPasses>, + mut object_store: Single<RendererObjectStore>, +) +{ + let Some(default_shader_asset) = assets + .get_handle_to_loaded::<ShaderModuleSource>(DEFAULT_SHADER_ASSET_LABEL.clone()) + else { + tracing::error!("Default shader asset is not loaded"); + return; + }; + + for (surface_spec,) in &window_surface_spec_query { + let render_pass = render_passes.passes.push_front_mut(RenderPass { + surface_id: surface_spec.id, + commands: Vec::with_capacity(30), + draw_properties: RendererDrawProperties::default(), + }); + + let default_texture_asset = assets + .get_handle_to_loaded::<Texture>(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) + .expect("Not possible"); + + if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( + default_texture_asset.id(), + )) { + object_store + .insert_pending(RendererObjectId::Asset(default_texture_asset.id())); + + render_pass + .commands + .push(RendererCommand::CreateTexture(default_texture_asset)); + } + + render_pass.commands.push(RendererCommand::ClearBuffers( + RendererBufferClearMask::COLOR | RendererBufferClearMask::DEPTH, + )); + + for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query + { + let shader_asset = match &shader { + Some(shader) => &shader.asset_handle, + None => &default_shader_asset, + }; + + let Some(pending_shader_bindings) = pending_shader_bindings.as_mut() else { + continue; + }; + + if pending_shader_bindings.bindings.is_empty() + && pending_shader_bindings.surface_specific_bindings.is_empty() + { + continue; + } + + let Some(model_spec) = assets.get(&model.spec_asset) else { + continue; + }; + + let Some(mesh_asset) = &model_spec.mesh_asset else { + continue; + }; + + if assets.get(mesh_asset).is_none() { + continue; + } + + debug_assert!(model_spec.material_names.len() <= 1); + + let model_material_asset = match model_spec.find_first_material(&assets) { + MaterialSearchResult::Found(model_material_asset) => { + model_material_asset.clone() + // Some(model_material_asset.clone()) + } + MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => { + // MaterialSearchResult::NotFound => { + continue; + } // MaterialSearchResult::NoMaterials => None, + }; + + if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( + shader_asset.id(), + )) { + let Some(shader_program) = shader_context.get_program(&shader_asset.id()) + else { + tracing::error!( + "Shader context doesn't have a program for shader asset {:?}", + assets.get_label(&shader_asset) + ); + continue; + }; + + object_store.insert_pending(RendererObjectId::Asset(shader_asset.id())); + + render_pass + .commands + .push(RendererCommand::CreateShaderProgram( + RendererObjectId::Asset(shader_asset.id()), + shader_program.clone(), + )); + } + + render_pass.commands.push(RendererCommand::ActivateShader( + RendererObjectId::Asset(shader_asset.id()), + )); + + let Some(model_material) = assets.get(&model_material_asset) else { + // TODO: Handle this case since it may occur + unreachable!(); + }; + + for texture_asset in [ + &model_material.ambient_map, + &model_material.diffuse_map, + &model_material.specular_map, + ] + .into_iter() + .flatten() + { + if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset( + texture_asset.id(), + )) { + object_store + .insert_pending(RendererObjectId::Asset(texture_asset.id())); + + render_pass + .commands + .push(RendererCommand::CreateTexture(texture_asset.clone())); + } + } + + for (shader_binding_loc, shader_binding_val) in + &pending_shader_bindings.bindings + { + render_pass.commands.push(RendererCommand::SetShaderBinding( + shader_binding_loc.clone(), + shader_binding_val.clone(), + )); + } + + for (shader_binding_surface_id, shader_binding_loc, shader_binding_val) in + &pending_shader_bindings.surface_specific_bindings + { + if *shader_binding_surface_id != surface_spec.id { + continue; + } + + render_pass.commands.push(RendererCommand::SetShaderBinding( + shader_binding_loc.clone(), + shader_binding_val.clone(), + )); + } + + if let Some(draw_flags) = draw_flags.as_deref() + && draw_flags.polygon_mode_config != PolygonModeConfig::default() + { + render_pass + .commands + .push(RendererCommand::UpdateDrawProperties( + RendererDrawProperties { + polygon_mode_config: draw_flags.polygon_mode_config.clone(), + ..Default::default() + }, + RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, + )); + } + + if !object_store + .contains_maybe_pending_with_id(&RendererObjectId::Asset(mesh_asset.id())) + { + object_store.insert_pending(RendererObjectId::Asset(mesh_asset.id())); + + render_pass.commands.push(RendererCommand::CreateMesh { + obj_id: RendererObjectId::Asset(mesh_asset.id()), + mesh: None, + usage: RendererMeshUsage::Static, + }); + } + + render_pass.commands.push(RendererCommand::DrawMesh( + RendererObjectId::Asset(mesh_asset.id()), + RendererDrawMeshOptions::default(), + )); + + if let Some(draw_flags) = draw_flags.as_deref() + && draw_flags.polygon_mode_config != PolygonModeConfig::default() + { + render_pass + .commands + .push(RendererCommand::UpdateDrawProperties( + RendererDrawProperties { + polygon_mode_config: PolygonModeConfig::default(), + ..Default::default() + }, + RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, + )); + } + } + } +} diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs new file mode 100644 index 0000000..bdff885 --- /dev/null +++ b/engine/src/renderer/object.rs @@ -0,0 +1,137 @@ +use std::collections::HashMap; +use std::sync::atomic::{AtomicU64, Ordering}; + +use ecs::Sole; + +use crate::asset::Id as AssetId; + +pub type RawValue = u32; + +/// Renderer object ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Id +{ + Asset(AssetId), + Sequential(SequentialId), +} + +impl Id +{ + pub fn new_sequential() -> Self + { + static NEXT_SEQUENTIAL_ID: AtomicU64 = AtomicU64::new(0); + + Self::Sequential(SequentialId( + NEXT_SEQUENTIAL_ID.fetch_add(1, Ordering::Relaxed), + )) + } + + pub fn into_asset_id(self) -> Option<AssetId> + { + match self { + Self::Asset(asset_id) => Some(asset_id), + Self::Sequential(_) => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SequentialId(u64); + +/// Renderer object store. +#[derive(Debug, Default, Sole)] +pub struct Store +{ + objects: HashMap<Id, Option<Object>>, +} + +impl Store +{ + pub fn get_obj(&self, id: &Id) -> Option<&Object> + { + self.objects.get(id).and_then(|obj| obj.as_ref()) + } + + pub fn get_texture_obj(&self, id: &Id) -> Option<&Object> + { + let obj = self.get_obj(id)?; + + if !matches!(obj.kind(), Kind::Texture) { + return None; + } + + Some(obj) + } + + pub fn get_shader_program_obj(&self, id: &Id) -> Option<&Object> + { + let obj = self.get_obj(id)?; + + if !matches!(obj.kind(), Kind::ShaderProgram) { + return None; + } + + Some(obj) + } + + pub fn contains_maybe_pending_with_id(&self, id: &Id) -> bool + { + self.objects.contains_key(id) + } + + pub fn contains_non_pending_with_id(&self, id: &Id) -> bool + { + self.objects.get(id).and_then(|obj| obj.as_ref()).is_some() + } + + pub fn insert(&mut self, id: Id, object: Object) + { + self.objects.insert(id, Some(object)); + } + + pub fn insert_pending(&mut self, id: Id) + { + self.objects.insert(id, None); + } + + pub fn remove(&mut self, id: &Id) -> Option<Option<Object>> + { + self.objects.remove(id) + } +} + +/// Renderer object. +#[derive(Debug, Clone)] +pub struct Object +{ + raw: RawValue, + kind: Kind, +} + +impl Object +{ + pub fn from_raw(raw: RawValue, kind: Kind) -> Self + { + Self { raw, kind } + } + + pub fn as_raw(&self) -> RawValue + { + self.raw + } + + pub fn kind(&self) -> Kind + { + self.kind + } +} + +/// Renderer object kind. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Kind +{ + Texture, + ShaderProgram, + ImplementationSpecific, +} diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 5665860..378a89d 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -1,685 +1,1338 @@ //! OpenGL renderer. +use std::borrow::Cow; use std::collections::HashMap; -use std::ffi::{c_void, 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::{PRESENT as PRESENT_PHASE, START as START_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::{Component, Query, Sole}; +use glutin::config::Config as GlutinConfig; +use glutin::display::GetGlDisplay; +use glutin::error::Error as GlutinError; +use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext}; +use glutin::surface::{ + GlSurface as _, + Surface as GlutinSurface, + WindowSurface as GlutinWindowSurface, +}; +use opengl_bindings::blending::{ + Configuration as GlBlendingConfig, + Equation as GlBlendingEquation, + Factor as GlBlendingFactor, + configure as gl_blending_configure, +}; +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, + clear_buffers, + define_scissor_box as gl_define_scissor_box, + enable, + get_viewport as gl_get_viewport, + set_enabled, + set_viewport as gl_set_viewport, }; -use crate::opengl::shader::{ +use opengl_bindings::shader::{ Error as GlShaderError, Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, + // UniformLocation as GlUniformLocation, }; -use crate::opengl::texture::{ - set_active_texture_unit, +use opengl_bindings::texture::{ + ColorSpace as GlTextureColorSpace, + 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, +use opengl_bindings::{ + ContextWithFns, + CurrentContextWithFns, + MakeContextCurrentError as GlMakeContextCurrentError, +}; +use raw_window_handle::WindowHandle; +use safer_ffi::layout::ReprC; +use zerocopy::{Immutable, IntoBytes}; + +use crate::asset::{Assets, Handle as AssetHandle}; +use crate::data_types::dimens::Dimens; +use crate::image::{ColorType as ImageColorType, Image}; +use crate::matrix::Matrix; +use crate::reflection::EnumReflectionExt; +use crate::renderer::blending::{Equation as BlendingEquation, Factor as BlendingFactor}; +use crate::renderer::object::{ + Id as RendererObjectId, + Kind as RendererObjectKind, + Object as RendererObject, + RawValue as RendererObjectRawValue, + Store as RendererObjectStore, +}; +use crate::renderer::opengl::glutin_compat::{ + DisplayBuilder, + Error as GlutinCompatError, +}; +use crate::renderer::opengl::graphics_mesh::GraphicsMesh; +use crate::renderer::{ BufferClearMask, - Capability, - ContextFlags, + Command as RendererCommand, + CommandQueue as RendererCommandQueue, + DrawMeshOptions, + DrawPropertiesUpdateFlags, + GraphicsProperties, + POST_RENDER_PHASE, + RENDER_PHASE, + SurfaceId, + SurfaceSpec, +}; +use crate::shader::cursor::BindingValue as ShaderBindingValue; +use crate::shader::{ + Context as ShaderContext, + Error as ShaderError, + Program as ShaderProgram, + Stage as ShaderStage, }; -use crate::projection::{ClipVolume, Projection}; -use crate::texture::{Id as TextureId, Texture}; -use crate::transform::{Position, Scale}; -use crate::util::{defer, Defer, RefOrValue}; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Texture, + Wrapping as TextureWrapping, +}; +use crate::util::OptionExt; use crate::vector::{Vec2, Vec3}; -use crate::vertex::{AttributeComponentType, Vertex}; -use crate::window::Window; - -type RenderableEntity<'a> = ( - &'a Mesh, - &'a Material, - &'a Option<MaterialFlags>, - &'a Option<Position>, - &'a Option<Scale>, - &'a Option<DrawFlags>, - &'a Option<GlObjects>, -); +use crate::windowing::Context as WindowingContext; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady, + Window, +}; -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct Extension {} +mod glutin_compat; +mod graphics_mesh; -impl ecs::extension::Extension for Extension +#[derive(Debug, Component)] +struct WindowGlConfig { - fn collect(self, mut collector: ecs::extension::Collector<'_>) - { - collector.add_system(*START_PHASE, initialize); - - collector.add_system( - *PRESENT_PHASE, - render - .into_system() - .initialize((GlobalGlObjects::default(),)), - ); - } + gl_config: GlutinConfig, } -fn initialize(window: Single<Window>) +#[derive(Sole, Default)] +struct GraphicsContext { - window - .make_context_current() - .expect("Failed to make window context current"); - - 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}", - ); + gl_context: Option<ContextWithFns>, + surfaces: HashMap<SurfaceId, GraphicsContextSurface>, + shader_uniform_buffer_objs: + HashMap<RendererObjectId, HashMap<u32, opengl_bindings::buffer::Buffer<u8>>>, + objects: HashMap<RendererObjectRawValue, GraphicsContextObject>, + next_object_key: RendererObjectRawValue, +} - abort(); - } - }); +#[derive(Debug)] +struct GraphicsContextSurface +{ + window_surface: GlutinSurface<GlutinWindowSurface>, + size: Dimens<u32>, +} - if get_opengl_context_flags().contains(ContextFlags::DEBUG) { - initialize_debug(); - } +#[derive(Debug)] +enum GraphicsContextObject +{ + Mesh + { + mesh: GraphicsMesh, + compatible_shader_program_obj_id: RendererObjectId, + }, +} - let window_size = window.size().expect("Failed to get window size"); +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Extension {} - set_viewport(Vec2 { x: 0, y: 0 }, window_size); +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + collector.add_system(*RENDER_PHASE, handle_commands); - window.set_framebuffer_size_callback(|new_window_size| { - set_viewport(Vec2::ZERO, new_window_size); - }); + collector.add_system(*POST_RENDER_PHASE, prepare_windows); + collector.add_system(*POST_RENDER_PHASE, init_window_graphics); - enable(Capability::DepthTest); - enable(Capability::MultiSample); + let _ = collector.add_sole(GraphicsContext::default()); + } } -#[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>, +fn prepare_windows( + window_query: Query< + (Option<&Window>, &mut WindowCreationAttributes), + ( + Without<CreationReady>, + Without<WindowGlConfig>, + Without<WindowClosed>, + ), + >, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, 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<_>>(); - - let directional_lights = directional_lights.iter().collect::<Vec<_>>(); - - let GlobalGlObjects { - shader_program, - textures: gl_textures, - } = &mut *gl_objects; - - let shader_program = - shader_program.get_or_insert_with(|| create_default_shader_program().unwrap()); - - clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); - - for ( - euid, - (mesh, material, material_flags, position, scale, draw_flags, gl_objects), - ) in query.iter_with_euids() + for (window_ent_id, (window, mut window_creation_attrs)) in + window_query.iter_with_euids() { - let material_flags = material_flags - .map(|material_flags| material_flags.clone()) - .unwrap_or_default(); - - let gl_objs = match gl_objects.as_deref() { - Some(gl_objs) => RefOrValue::Ref(gl_objs), - None => RefOrValue::Value(Some(GlObjects::new(&mesh))), - }; - - defer!(|gl_objs| { - if let RefOrValue::Value(opt_gl_objs) = gl_objs { - actions.add_components(euid, (opt_gl_objs.take().unwrap(),)); - }; - }); - - 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"), - ); + tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); - 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, - ); - - for (index, texture) in material.textures.iter().enumerate() { - let gl_texture = gl_textures - .entry(texture.id()) - .or_insert_with(|| create_gl_texture(texture)); - - let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); + let mut glutin_config_template_builder = + glutin::config::ConfigTemplateBuilder::new(); - set_active_texture_unit(texture_unit); - - gl_texture.bind(); + if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { + glutin_config_template_builder = glutin_config_template_builder + .with_multisampling(multisampling_sample_cnt); } - shader_program.activate(); + let window_handle = match window + .as_ref() + .map(|window| unsafe { + windowing_context.get_window_as_handle(&window.wid()) + }) + .flatten() + .transpose() + { + Ok(window_handle) => window_handle, + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; - if let Some(draw_flags) = &draw_flags { - crate::opengl::set_polygon_mode( - draw_flags.polygon_mode_config.face, - draw_flags.polygon_mode_config.mode, - ); - } + let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new() + .with_window_attributes(window_creation_attrs.clone()) + .build( + window_handle, + &display_handle, + glutin_config_template_builder, + |mut cfgs| cfgs.next(), + ) { + Ok((new_window_creation_attrs, gl_config)) => { + (new_window_creation_attrs, gl_config) + } + Err(GlutinCompatError::WindowRequired) => { + actions.add_components(window_ent_id, (CreationReady,)); + continue; + } + Err(err) => { + tracing::error!("Failed to create platform graphics display: {err}"); + continue; + } + }; - draw_mesh(gl_objs.get().unwrap()); + *window_creation_attrs = new_window_creation_attrs; - if draw_flags.is_some() { - let default_polygon_mode_config = PolygonModeConfig::default(); + actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); - crate::opengl::set_polygon_mode( - default_polygon_mode_config.face, - default_polygon_mode_config.mode, - ); + if window.is_none() { + actions.add_components(window_ent_id, (CreationReady,)); } } } -#[derive(Debug, Default, Component)] -struct GlobalGlObjects -{ - shader_program: Option<GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, -} - -fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) -{ - crate::opengl::set_viewport(position, size); -} - -fn initialize_debug() +#[tracing::instrument(skip_all)] +fn init_window_graphics( + window_query: Query<(&Window, &WindowGlConfig), (Without<SurfaceSpec>,)>, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, + mut graphics_ctx: Single<GraphicsContext>, + mut actions: Actions, +) { - enable_debug_output(); - - set_debug_message_callback(opengl_debug_message_cb); - set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable); -} + for (window_ent_id, (window, window_gl_config)) in window_query.iter_with_euids() { + tracing::info!( + window_entity_id=%window_ent_id, + window_title=&*window.title, + "Initializing graphics for window" + ); -fn draw_mesh(gl_objects: &GlObjects) -{ - gl_objects.vertex_arr.bind(); + let display = window_gl_config.gl_config.display(); + + let window_handle = + match unsafe { windowing_context.get_window_as_handle(&window.wid()) } + .transpose() + { + Ok(Some(window_handle)) => window_handle, + Ok(None) => { + tracing::error!( + wid = ?window.wid(), + entity_id = %window_ent_id, + "Windowing context does not contain window" + ); + continue; + } + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; - if gl_objects.index_buffer.is_some() { - VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); - } else { - VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt); - } -} + 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; + }; -fn create_gl_texture(texture: &Texture) -> GlTexture -{ - let mut gl_texture = GlTexture::new(); + let window_surface = match unsafe { + display.create_window_surface( + &window_gl_config.gl_config, + &glutin::surface::SurfaceAttributesBuilder::< + glutin::surface::WindowSurface, + >::new() + .build( + window_handle.as_raw(), + window_inner_size.width, + window_inner_size.height, + ), + ) + } { + Ok(window_surface) => window_surface, + Err(err) => { + tracing::error!("Failed to create window surface: {err}"); + continue; + } + }; - gl_texture.generate( - *texture.dimensions(), - texture.image().as_bytes(), - texture.pixel_data_format(), - ); + let gl_context = match graphics_ctx.gl_context.get_or_try_insert_with_fn(|| { + create_gl_context( + &window_gl_config.gl_config, + &graphics_props, + window_handle, + &window_surface, + ) + }) { + Ok(gl_context) => gl_context, + Err(err) => { + tracing::error!("Failed to create GL context: {err:#?}"); + continue; + } + }; - gl_texture.apply_properties(texture.properties()); + let Ok(curr_gl_context) = gl_context.make_current(&window_surface) else { + tracing::error!("Failed to make GL context current"); + continue; + }; - gl_texture -} + if let Err(err) = gl_set_viewport( + &curr_gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &window.inner_size().clone().into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } -const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl"); -const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl"); + set_enabled( + &curr_gl_context, + Capability::DepthTest, + graphics_props.depth_test, + ); -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"); + set_enabled( + &curr_gl_context, + Capability::MultiSample, + graphics_props.multisampling_sample_cnt.is_some(), + ); -fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError> -{ - let mut vertex_shader = GlShader::new(ShaderKind::Vertex); + 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 + } + } + } - vertex_shader.set_source(&*glsl_preprocess( - VERTEX_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; + let surface_id = SurfaceId::new_unique(); - vertex_shader.compile()?; + actions.add_components(window_ent_id, (SurfaceSpec { id: surface_id },)); - let mut fragment_shader = GlShader::new(ShaderKind::Fragment); + graphics_ctx.surfaces.insert( + surface_id, + GraphicsContextSurface { + window_surface, + size: window.inner_size().clone(), + }, + ); + } +} - fragment_shader.set_source(&*glsl_preprocess( - FRAGMENT_GLSL_SHADER_SRC, - &get_glsl_shader_content, - )?)?; +#[tracing::instrument(skip_all)] +fn handle_commands( + mut graphics_ctx: Single<GraphicsContext>, + mut object_store: Single<RendererObjectStore>, + mut command_queue: Single<RendererCommandQueue>, + assets: Single<Assets>, + shader_context: Single<ShaderContext>, +) +{ + let GraphicsContext { + ref gl_context, + ref mut surfaces, + ref mut shader_uniform_buffer_objs, + objects: ref mut graphics_ctx_objects, + next_object_key: ref mut next_graphics_ctx_object_key, + } = *graphics_ctx; + + let Some(gl_context) = gl_context else { + return; + }; - fragment_shader.compile()?; + let mut opt_curr_gl_ctx: Option<CurrentContextWithFns> = None; - let mut gl_shader_program = GlShaderProgram::new(); + let mut activated_gl_shader_program: Option<(RendererObjectId, GlShaderProgram)> = + None; - gl_shader_program.attach(&vertex_shader); - gl_shader_program.attach(&fragment_shader); + for command in command_queue.drain() { + let tracing_span = tracing::info_span!( + "handle_cmd", + command = %command.get_variant_reflection().name, + ); + let _tracing_span_enter = tracing_span.enter(); - gl_shader_program.link()?; + match command { + RendererCommand::RemoveSurface(surface_id) => { + let Some(surface) = surfaces.remove(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; - Ok(gl_shader_program) -} + if surface.window_surface.is_current(gl_context.context()) { + opt_curr_gl_ctx = None; -#[derive(Debug, thiserror::Error)] -enum CreateShaderError -{ - #[error(transparent)] - ShaderError(#[from] GlShaderError), + if let Err(err) = gl_context.context().make_not_current_in_place() { + tracing::error!("Failed to make GL context not current: {err}"); + } + } - #[error(transparent)] - PreprocessingError(#[from] GlslPreprocessingError), + drop(surface); + } + RendererCommand::MakeCurrent(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + let curr_gl_ctx = match gl_context.make_current(&surface.window_surface) { + Ok(current_graphics_context) => current_graphics_context, + Err(err) => { + tracing::error!("Failed to make graphics context current: {err}"); + continue; + } + }; + + if let Err(err) = gl_set_viewport( + &curr_gl_ctx, + &Vec2 { x: 0, y: 0 }.into(), + &surface.size.into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + + opt_curr_gl_ctx = Some(curr_gl_ctx); + } + RendererCommand::SetSurfaceSize(surface_id, new_surface_size) => { + let Some(surface) = surfaces.get_mut(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + surface.size = new_surface_size; + + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + continue; + }; + + if !surface.window_surface.is_current(gl_context.context()) { + continue; + } + + if let Err(err) = gl_set_viewport( + &curr_gl_ctx, + &Vec2 { x: 0, y: 0 }.into(), + &surface.size.into(), + ) { + tracing::error!("Failed to set viewport: {err}"); + } + } + 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.window_surface.swap_buffers(gl_context.context()) + { + tracing::error!("Failed to swap buffers: {err}"); + } + } + RendererCommand::CreateShaderProgram( + shader_program_obj_id, + shader_program, + ) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + if object_store.contains_non_pending_with_id(&shader_program_obj_id) { + tracing::error!( + shader_program_object_id=?shader_program_obj_id, + shader_asset_label=?shader_program_obj_id + .into_asset_id() + .and_then(|asset_id| assets.get_label_by_id(asset_id)), + "Object store already contains a object with this ID" + ); + continue; + } + + let gl_shader_program = + match create_shader_program(&curr_gl_ctx, &shader_program) { + Ok(gl_shader_program) => gl_shader_program, + Err(err) => { + tracing::error!("Failed to create shader program: {err}"); + continue; + } + }; + + object_store.insert( + shader_program_obj_id, + RendererObject::from_raw( + gl_shader_program.into_raw(), + RendererObjectKind::ShaderProgram, + ), + ); + } + RendererCommand::ActivateShader(shader_program_obj_id) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(shader_program_obj) = + object_store.get_shader_program_obj(&shader_program_obj_id) + else { + tracing::error!("Shader object does not exist or has a wrong kind"); + continue; + }; + + let gl_shader_program = + GlShaderProgram::from_raw(shader_program_obj.as_raw()); + + gl_shader_program.activate(&curr_gl_ctx); + + activated_gl_shader_program = + Some((shader_program_obj_id, gl_shader_program)); + } + RendererCommand::SetShaderBinding(binding_location, binding_value) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some((activated_gl_shader_program_obj_id, _)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; + + if let ShaderBindingValue::Texture(texture_asset) = &binding_value { + let Some(texture_obj) = object_store + .get_texture_obj(&RendererObjectId::Asset(texture_asset.id())) + else { + tracing::error!( + "Texture {:?} does not exist in renderer object store", + assets.get_label(texture_asset) + ); + continue; + }; + + let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); + + gl_texture.bind_to_texture_unit( + curr_gl_ctx, + binding_location.binding_index, + ); + + // gl_shader_program.set_uniform_at_location( + // curr_gl_ctx, + // GlUniformLocation::from_number( + // binding_location.binding_index as i32, + // ), + // &binding_location.binding_index, + // ); + + continue; + } + + let binding_index = binding_location.binding_index; + + let uniform_buffer_objs = shader_uniform_buffer_objs + .entry(*activated_gl_shader_program_obj_id) + .or_default(); + + let uniform_buffer = + uniform_buffer_objs.entry(binding_index).or_insert_with(|| { + let uniform_buf = + opengl_bindings::buffer::Buffer::<u8>::new(curr_gl_ctx); + + uniform_buf + .init( + curr_gl_ctx, + binding_location.binding_size, + opengl_bindings::buffer::Usage::Dynamic, + ) + .unwrap(); + + uniform_buf + }); + + // The index into the uniform buffer binding target is for whatever + // shader program is currently bound so the uniform buffer object has + // to be re-bound so that a uniform buffer from another shader isn't + // used + uniform_buffer.bind_to_indexed_target( + curr_gl_ctx, + opengl_bindings::buffer::BindingTarget::UniformBuffer, + binding_index as u32, + ); + + let fvec3_value; + + uniform_buffer + .store_at_byte_offset( + curr_gl_ctx, + binding_location.byte_offset, + match binding_value { + ShaderBindingValue::Uint(ref value) => value.as_bytes(), + ShaderBindingValue::Int(ref value) => value.as_bytes(), + ShaderBindingValue::Float(ref value) => value.as_bytes(), + ShaderBindingValue::FVec3(value) => { + fvec3_value = CF32Vec3::from(value); + + fvec3_value.as_bytes() + } + ShaderBindingValue::FMat4x4(ref value) => { + value.items().as_bytes() + } + ShaderBindingValue::Texture(_) => unreachable!(), + }, + ) + .unwrap(); + } + RendererCommand::CreateTexture(texture_asset) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + if let Err(err) = create_texture_object( + curr_gl_ctx, + &mut object_store, + &assets, + &texture_asset, + ) { + tracing::error!("Failed to create texture object: {err}"); + } + } + RendererCommand::CreateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + if object_store.contains_non_pending_with_id(&mesh_object_id) { + tracing::error!( + mesh_object_id=?mesh_object_id, + mesh_asset_label=?mesh_object_id + .into_asset_id() + .and_then(|asset_id| assets.get_label_by_id(asset_id)), + "Object store already contains a object with this ID" + ); + continue; + } + + let Some((RendererObjectId::Asset(curr_shader_program_asset_id), _)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; + + let curr_shader_program_metadata = shader_context + .get_program_metadata(curr_shader_program_asset_id) + .expect("Not possible"); + + let Some(vertex_desc) = &curr_shader_program_metadata.vertex_desc else { + tracing::error!( + "Current shader program does not have a vertex description" + ); + continue; + }; + + let key = *next_graphics_ctx_object_key; + + let mesh = match mesh_object_id { + RendererObjectId::Asset(mesh_asset_id) => match mesh.as_ref() { + Some(mesh) => mesh, + None => { + let Some(mesh) = + assets.get(&AssetHandle::from_id(mesh_asset_id)) + else { + tracing::error!( + asset_id=?mesh_asset_id, + "Mesh asset does not exist" + ); + continue; + }; + + mesh + } + }, + RendererObjectId::Sequential(_) => { + let Some(mesh) = mesh.as_ref() else { + tracing::error!( + object_id=?mesh_object_id, + "Object ID is sequential but no mesh data is given" + ); + continue; + }; + + mesh + } + }; + + let graphics_mesh = match GraphicsMesh::new( + &curr_gl_ctx, + &mesh, + mesh_usage, + &vertex_desc, + ) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!("Failed to create mesh: {err}"); + continue; + } + }; + + graphics_ctx_objects.insert( + key, + GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id: RendererObjectId::Asset( + *curr_shader_program_asset_id, + ), + }, + ); + + object_store.insert( + mesh_object_id, + RendererObject::from_raw( + key, + RendererObjectKind::ImplementationSpecific, + ), + ); + + *next_graphics_ctx_object_key += 1; + } + RendererCommand::UpdateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(mesh_graphics_ctx_obj_key) = object_store + .get_obj(&mesh_object_id) + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.get_mut(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id: _, + } = mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + if let Err(err) = graphics_mesh.update(curr_gl_ctx, &mesh, mesh_usage) { + tracing::error!("Failed to update mesh: {err}"); + } + } + RendererCommand::RemoveMesh(mesh_object_id) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(mesh_graphics_ctx_obj_key) = object_store + .remove(&mesh_object_id) + .flatten() + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.remove(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { mesh: mut graphics_mesh, .. } = + mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + graphics_mesh.destroy(curr_gl_ctx); + } + RendererCommand::DrawMesh(mesh_object_id, draw_mesh_opts) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + let Some(mesh_graphics_ctx_obj_key) = object_store + .get_obj(&mesh_object_id) + .map(|obj| obj.as_raw()) + else { + tracing::error!( + object_id=?mesh_object_id, + "Object store does not contain a mesh object with this ID" + ); + continue; + }; + + let Some(mesh_graphics_ctx_obj) = + graphics_ctx_objects.get(&mesh_graphics_ctx_obj_key) + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context does not contain a mesh object with this key" + ); + continue; + }; + + #[allow(irrefutable_let_patterns)] + let GraphicsContextObject::Mesh { + mesh: graphics_mesh, + compatible_shader_program_obj_id, + } = mesh_graphics_ctx_obj + else { + tracing::error!( + object_id=?mesh_object_id, + key=mesh_graphics_ctx_obj_key, + "Graphics context object with this key is not a mesh" + ); + continue; + }; + + if Some(compatible_shader_program_obj_id) + != activated_gl_shader_program.as_ref().map( + |(activated_gl_shader_program_obj_id, _)| { + activated_gl_shader_program_obj_id + }, + ) + { + tracing::error!(concat!( + "Activated shader program is not the ", + "compatible shader program of the mesh" + )); + continue; + } + + if let Err(err) = draw_mesh(&curr_gl_ctx, graphics_mesh, &draw_mesh_opts) + { + tracing::error!("Failed to draw mesh: {err}"); + }; + } + RendererCommand::UpdateDrawProperties( + draw_props, + draw_props_update_flags, + ) => { + let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { + tracing::error!("No GL context is current"); + continue; + }; + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) + { + opengl_bindings::misc::set_polygon_mode( + &curr_gl_ctx, + draw_props.polygon_mode_config.face, + draw_props.polygon_mode_config.mode, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) + { + set_enabled( + curr_gl_ctx, + Capability::Blend, + draw_props.blending_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) + { + gl_blending_configure( + curr_gl_ctx, + GlBlendingConfig::default() + .with_source_factor(blending_factor_to_gl( + draw_props.blending_config.source_factor, + )) + .with_destination_factor(blending_factor_to_gl( + draw_props.blending_config.destination_factor, + )) + .with_equation(blending_equation_to_gl( + draw_props.blending_config.equation, + )), + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) + { + set_enabled( + curr_gl_ctx, + Capability::DepthTest, + draw_props.depth_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) + { + set_enabled( + curr_gl_ctx, + Capability::ScissorTest, + draw_props.scissor_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) + { + gl_define_scissor_box( + curr_gl_ctx, + draw_props.scissor_box.lower_left_corner_pos.into(), + draw_props + .scissor_box + .size + .unwrap_or_else(|| { + let (_, viewport_size) = gl_get_viewport(curr_gl_ctx); + + Dimens::<u16> { + width: viewport_size + .width + .try_into() + .expect("Viewport width too large"), + height: viewport_size + .height + .try_into() + .expect("Viewport height too large"), + } + }) + .into(), + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) + { + set_enabled( + curr_gl_ctx, + Capability::CullFace, + draw_props.face_culling_enabled, + ); + } + } + } + } } -fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error> +fn create_gl_context( + gl_config: &GlutinConfig, + graphics_props: &GraphicsProperties, + window_handle: WindowHandle<'_>, + surface: &GlutinSurface<GlutinWindowSurface>, +) -> Result<ContextWithFns, CreateGlContextError> { - 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()); + let display = gl_config.display(); + + let glutin_context = unsafe { + display.create_context( + gl_config, + &glutin::context::ContextAttributesBuilder::new() + .with_debug(graphics_props.debug) + .build(Some(window_handle.as_raw())), + ) } + .map_err(CreateGlContextError::CreateGlutinContext)?; - Err(IoError::new( - IoErrorKind::NotFound, - format!("Content for shader file {} not found", path.display()), - )) + ContextWithFns::new(glutin_context, &surface) + .map_err(CreateGlContextError::MakeContextCurrent) } -#[derive(Debug, Component)] -struct GlObjects +#[derive(Debug, thiserror::Error)] +enum CreateGlContextError { - /// 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, + #[error("Glutin context creation failed")] + CreateGlutinContext(#[source] GlutinError), - vertex_arr: VertexArray, + #[error("Making GL context current failed")] + MakeContextCurrent(#[source] GlMakeContextCurrentError), } -impl GlObjects +#[tracing::instrument(skip_all)] +fn create_texture_object( + curr_gl_ctx: &CurrentContextWithFns<'_>, + renderer_object_store: &mut RendererObjectStore, + assets: &Assets, + texture_asset: &AssetHandle<Texture>, +) -> Result<(), GlTextureGenerateError> { - #[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(mesh.vertices(), BufferUsage::Static); + let object_id = RendererObjectId::Asset(texture_asset.id()); - vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0); - - let mut offset = 0u32; + if renderer_object_store.contains_non_pending_with_id(&object_id) { + tracing::error!( + texture_object_id=?object_id, + texture_asset_label=?assets.get_label(texture_asset), + "Renderer object store already contains object with this ID" + ); + return Ok(()); + } - for attrib in Vertex::attrs() { - vertex_arr.enable_attrib(attrib.index); + let Some(texture) = assets.get(&texture_asset) else { + tracing::error!("Texture asset is not loaded",); + return Ok(()); + }; - vertex_arr.set_attrib_format( - attrib.index, - match attrib.component_type { - AttributeComponentType::Float => VertexArrayDataType::Float, - }, - false, - offset, + let texture_image = match texture.image.color_type() { + ImageColorType::Rgb8 if (texture.image.dimensions().width * 3) % 4 != 0 => { + // The texture will be corrupted if the alignment of each horizontal line of + // the texture pixel array is not multiple of 4. + // + // Read more about this at + // wikis.khronos.org/opengl/Common_Mistakes#Texture_upload_and_pixel_reads + // + // To prevent this, the image is converted to RGBA8. RGBA8 images have a pixel + // size of 4 bytes so they cannot have any alignment problems + + tracing::warn!( + texture_asset = %assets + .get_label(&texture_asset) + .expect("Not possible"), + concat!( + "Converting texture image from RGB8 to RGBA8 to prevent alignment ", + "problems. This conversion may be slow. Consider changing the ", + "texture image's pixel format to RGBA8" + ) ); - 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, + &texture.image.to_rgba8() } - } -} - -fn apply_transformation_matrices( - transformation: Transformation, - gl_shader_program: &mut GlShaderProgram, - camera: &Camera, - camera_pos: &Position, - window_size: Dimens<u32>, -) -{ - gl_shader_program - .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation)); - - let view_matrix = create_view_matrix(camera, &camera_pos.position); - - gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix); + _ => &texture.image, + }; - #[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, + renderer_object_store.insert( + object_id, + RendererObject::from_raw( + create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)? + .into_raw(), + RendererObjectKind::Texture, ), - Projection::Orthographic(orthographic_proj) => { - orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne) - } - }; + ); - gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix); + Ok(()) } -fn apply_light<PointLightHolder>( - material: &Material, - material_flags: &MaterialFlags, - global_light: &GlobalLight, - gl_shader_program: &mut GlShaderProgram, - point_lights: &[PointLightHolder], - directional_lights: &[&DirectionalLight], - camera_pos: &Position, -) where - PointLightHolder: Deref<Target = PointLight>, +fn draw_mesh( + current_context: &CurrentContextWithFns<'_>, + graphics_mesh: &GraphicsMesh, + opts: &DrawMeshOptions, +) -> Result<(), GlDrawError> { - 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( - &create_light_uniform_name( - "directional_lights", - dir_light_index, - "direction", - ), - &dir_light.direction, - ); - - set_light_phong_uniforms( - gl_shader_program, - "directional_lights", - dir_light_index, - *dir_light, - ); + graphics_mesh.vertex_arr.bind(current_context); + + if graphics_mesh.index_buffer.is_some() { + VertexArray::draw_elements( + current_context, + opengl_bindings::vertex_array::DrawElementsOptions { + primitive_kind: PrimitiveKind::Triangles, + element_offset: opts.element_offset, + element_cnt: opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + vertex_offset: opts.vertex_offset, + }, + )?; + } else { + VertexArray::draw_arrays( + current_context, + PrimitiveKind::Triangles, + opts.vertex_offset, + opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + )?; } - // 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); - - for (point_light_index, point_light) in point_lights.iter().enumerate() { - gl_shader_program.set_uniform_vec_3fv( - &create_light_uniform_name("point_lights", point_light_index, "position"), - &point_light.position, - ); - - set_light_phong_uniforms( - gl_shader_program, - "point_lights", - point_light_index, - &**point_light, - ); - - set_light_attenuation_uniforms( - gl_shader_program, - "point_lights", - point_light_index, - point_light, - ); - } + Ok(()) +} - // 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); +fn create_gl_texture( + current_context: &CurrentContextWithFns<'_>, + image: &Image, + texture_properties: &TextureProperties, +) -> Result<GlTexture, GlTextureGenerateError> +{ + let gl_texture = GlTexture::new(current_context); - gl_shader_program.set_uniform_vec_3fv( - c"material.ambient", - &if material_flags.use_ambient_color { - material.ambient.clone() + gl_texture.generate( + current_context, + &image.dimensions().into(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, + if image.color_space_is_srgb() { + GlTextureColorSpace::Srgb } else { - global_light.ambient.clone() - } - .into(), - ); - - gl_shader_program - .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into()); - - #[allow(clippy::cast_possible_wrap)] - gl_shader_program - .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into()); + GlTextureColorSpace::Linear + }, + )?; - let texture_map = material - .textures - .iter() - .enumerate() - .map(|(index, texture)| (texture.id(), index)) - .collect::<HashMap<_, _>>(); - - #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( - c"material.ambient_map", - *texture_map.get(&material.ambient_map).unwrap() as i32, + gl_texture.set_wrap( + current_context, + texture_wrapping_to_gl(texture_properties.wrap), ); - #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( - c"material.diffuse_map", - *texture_map.get(&material.diffuse_map).unwrap() as i32, + gl_texture.set_magnifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.magnifying_filter), ); - #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform_1i( - c"material.specular_map", - *texture_map.get(&material.specular_map).unwrap() as i32, + gl_texture.set_minifying_filter( + current_context, + texture_filtering_to_gl(texture_properties.minifying_filter), ); - gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess); - - gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position); + Ok(gl_texture) } -fn set_light_attenuation_uniforms( - gl_shader_program: &mut GlShaderProgram, - light_array: &str, - light_index: usize, - light: &PointLight, -) +fn create_shader_program( + current_context: &CurrentContextWithFns<'_>, + shader_program: &ShaderProgram, +) -> Result<GlShaderProgram, CreateShaderError> { - gl_shader_program.set_uniform_1fv( - &create_light_uniform_name( - light_array, - light_index, - "attenuation_props.constant", - ), - light.attenuation_params.constant, - ); + let shader_program_reflection = shader_program.reflection(0).expect("Not possible"); - gl_shader_program.set_uniform_1fv( - &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"), - light.attenuation_params.linear, - ); + let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex) + })?; + + let vertex_shader_entry_point_code = shader_program + .get_entry_point_code(vs_entry_point_index.try_into().expect( + "Vertex shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Vertex, + entrypoint: vs_entry_point_reflection + .name() + .map(|name| name.to_string().into()) + .unwrap_or("(none)".into()), + })?; + + let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment) + })?; - gl_shader_program.set_uniform_1fv( - &create_light_uniform_name( - light_array, - light_index, - "attenuation_props.quadratic", - ), - light.attenuation_params.quadratic, - ); -} + let fragment_shader_entry_point_code = shader_program + .get_entry_point_code(fs_entry_point_index.try_into().expect( + "Fragment shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Fragment, + entrypoint: fs_entry_point_reflection + .name() + .map(|name| name.to_string().into()) + .unwrap_or("(none)".into()), + })?; -fn set_light_phong_uniforms( - gl_shader_program: &mut GlShaderProgram, - light_array: &str, - light_index: usize, - light: &impl Light, -) -{ - gl_shader_program.set_uniform_vec_3fv( - &create_light_uniform_name(light_array, light_index, "phong.diffuse"), - &light.diffuse().clone().into(), - ); + let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); - gl_shader_program.set_uniform_vec_3fv( - &create_light_uniform_name(light_array, light_index, "phong.specular"), - &light.specular().clone().into(), - ); -} + vertex_shader.set_source( + current_context, + &vertex_shader_entry_point_code.as_str().unwrap(), + )?; -trait Light -{ - fn diffuse(&self) -> &Color<f32>; - fn specular(&self) -> &Color<f32>; -} + vertex_shader.compile(current_context)?; -impl Light for PointLight -{ - fn diffuse(&self) -> &Color<f32> - { - &self.diffuse - } + let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); - fn specular(&self) -> &Color<f32> - { - &self.specular - } -} + fragment_shader.set_source( + current_context, + &fragment_shader_entry_point_code.as_str().unwrap(), + )?; -impl Light for DirectionalLight -{ - fn diffuse(&self) -> &Color<f32> - { - &self.diffuse - } + fragment_shader.compile(current_context)?; - fn specular(&self) -> &Color<f32> - { - &self.specular - } -} + let gl_shader_program = GlShaderProgram::new(current_context); -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(), - ) - } + 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) } -fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4> +#[derive(Debug, thiserror::Error)] +enum CreateShaderError { - let mut view = Matrix::new(); + #[error( + "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}" + )] + GetShaderEntryPointCodeFailed + { + #[source] + err: ShaderError, + stage: ShaderStage, + entrypoint: Cow<'static, str>, + }, - view.look_at(&camera_pos, &camera.target, &camera.global_up); + #[error("No entrypoint was found for shader stage {0:?}")] + NoShaderStageEntrypointFound(ShaderStage), - view + #[error(transparent)] + ShaderError(#[from] GlShaderError), } #[tracing::instrument(skip_all)] @@ -693,7 +1346,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) => { @@ -712,7 +1365,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 => { @@ -724,19 +1378,131 @@ fn opengl_debug_message_cb( }; } -#[derive(Debug)] -struct Transformation +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping { - position: Vec3<f32>, - scale: Vec3<f32>, + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } } -fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4, 4> +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering { - let mut matrix = Matrix::new_identity(); + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} - matrix.translate(&transformation.position); - matrix.scale(&transformation.scale); +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 } + } +} - matrix +impl<Value: ReprC + IntoBytes + 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: ReprC + Copy> From<Matrix<Value, 4, 4>> + for opengl_bindings::data_types::Matrix<Value, 4, 4> +{ + fn from(matrix: Matrix<Value, 4, 4>) -> Self + { + Self { items: matrix.items } + } +} + +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, + } + } +} + +#[derive(Debug, IntoBytes, Immutable)] +#[repr(C)] +pub struct CF32Vec3 +{ + x: f32, + y: f32, + z: f32, +} + +impl From<Vec3<f32>> for CF32Vec3 +{ + fn from(src: Vec3<f32>) -> Self + { + Self { x: src.x, y: src.y, z: src.z } + } +} + +fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor +{ + match blending_factor { + BlendingFactor::Zero => GlBlendingFactor::Zero, + BlendingFactor::One => GlBlendingFactor::One, + BlendingFactor::SrcColor => GlBlendingFactor::SrcColor, + BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor, + BlendingFactor::DstColor => GlBlendingFactor::DstColor, + BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor, + BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha, + BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha, + BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha, + BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha, + BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor, + BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor, + BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha, + BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha, + } +} + +fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation +{ + match blending_equation { + BlendingEquation::Add => GlBlendingEquation::Add, + BlendingEquation::Subtract => GlBlendingEquation::Subtract, + BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract, + BlendingEquation::Min => GlBlendingEquation::Min, + BlendingEquation::Max => GlBlendingEquation::Max, + } } diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl deleted file mode 100644 index 5bf5ff2..0000000 --- a/engine/src/renderer/opengl/glsl/fragment.glsl +++ /dev/null @@ -1,73 +0,0 @@ -#version 330 core - -#preinclude "light.glsl" -#preinclude "vertex_data.glsl" - -#define MAX_LIGHT_CNT 64 - -out vec4 FragColor; - -in VertexData vertex_data; - -uniform vec3 view_pos; -uniform sampler2D input_texture; -uniform Material material; - -uniform PointLight point_lights[MAX_LIGHT_CNT]; -uniform int point_light_cnt; - -uniform DirectionalLight directional_lights[MAX_LIGHT_CNT]; -uniform int directional_light_cnt; - -void main() -{ - vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords); - - vec3 directional_light_sum = vec3(0.0, 0.0, 0.0); - - for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) { - CalculatedLight calculated_dir_light; - - calc_light( - // Negated since we want the light to point from the light direction - normalize(-directional_lights[dl_index].direction), - directional_lights[dl_index].phong, - vertex_data, - view_pos, - material, - calculated_dir_light - ); - - directional_light_sum += - calculated_dir_light.diffuse + calculated_dir_light.specular; - } - - vec3 point_light_sum = vec3(0.0, 0.0, 0.0); - - for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) { - vec3 light_direction = - normalize(point_lights[pl_index].position - vertex_data.world_space_pos); - - CalculatedLight calculated_point_light; - - calc_light( - light_direction, - point_lights[pl_index].phong, - vertex_data, - view_pos, - material, - calculated_point_light - ); - - float attenuation = - calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos); - - calculated_point_light.diffuse *= attenuation; - calculated_point_light.specular *= attenuation; - - point_light_sum += - calculated_point_light.diffuse + calculated_point_light.specular; - } - - FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0); -} diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl deleted file mode 100644 index f12b5fe..0000000 --- a/engine/src/renderer/opengl/glsl/light.glsl +++ /dev/null @@ -1,133 +0,0 @@ -#version 330 core - -#ifndef LIGHT_GLSL -#define LIGHT_GLSL - -#preinclude "vertex_data.glsl" - -struct Material -{ - vec3 ambient; - vec3 diffuse; - vec3 specular; - sampler2D ambient_map; - sampler2D diffuse_map; - sampler2D specular_map; - float shininess; -}; - -struct LightPhong -{ - vec3 diffuse; - vec3 specular; -}; - -struct AttenuationProperties -{ - float constant; - float linear; - float quadratic; -}; - -struct PointLight -{ - LightPhong phong; - vec3 position; - AttenuationProperties attenuation_props; -}; - -struct DirectionalLight -{ - LightPhong phong; - vec3 direction; -}; - -struct CalculatedLight -{ - vec3 diffuse; - vec3 specular; -}; - -vec3 calc_ambient_light(in Material material, in vec2 texture_coords) -{ - return vec3(texture(material.ambient_map, texture_coords)) * material.ambient; -} - -vec3 calc_diffuse_light( - in Material material, - in LightPhong light_phong, - in vec3 light_dir, - in vec3 norm, - in vec2 texture_coords -) -{ - float diff = max(dot(norm, light_dir), 0.0); - - return light_phong.diffuse * ( - diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse) - ); -} - -vec3 calc_specular_light( - in Material material, - in LightPhong light_phong, - in vec3 light_dir, - in vec3 norm, - in vec3 view_pos, - in vec3 frag_pos, - in vec2 texture_coords -) -{ - vec3 view_direction = normalize(view_pos - frag_pos); - - vec3 halfway_direction = normalize(light_dir + view_direction); - - float spec = - pow(max(dot(norm, halfway_direction), 0.0), material.shininess); - - return light_phong.specular * ( - spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular) - ); -} - -float calc_attenuation(in PointLight point_light, in vec3 position) -{ - float light_distance = length(point_light.position - position); - - return 1.0 / (point_light.attenuation_props.constant - + point_light.attenuation_props.linear - * light_distance + point_light.attenuation_props.quadratic - * pow(light_distance, 2)); -} - -void calc_light( - in vec3 light_direction, - in LightPhong light_phong, - in VertexData vertex_data, - in vec3 view_pos, - in Material material, - out CalculatedLight calculated_light -) -{ - vec3 norm = normalize(vertex_data.world_space_normal); - - calculated_light.diffuse = calc_diffuse_light( - material, - light_phong, - light_direction, - norm, - vertex_data.texture_coords - ); - - calculated_light.specular = calc_specular_light( - material, - light_phong, - light_direction, - norm, - view_pos, - vertex_data.world_space_pos, - vertex_data.texture_coords - ); -} - -#endif diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl deleted file mode 100644 index b57caa6..0000000 --- a/engine/src/renderer/opengl/glsl/vertex.glsl +++ /dev/null @@ -1,24 +0,0 @@ -#version 330 core - -#preinclude "vertex_data.glsl" - -layout (location = 0) in vec3 pos; -layout (location = 1) in vec2 texture_coords; -layout (location = 2) in vec3 normal; - -out VertexData vertex_data; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -void main() -{ - gl_Position = projection * view * model * vec4(pos, 1.0); - - vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0)); - vertex_data.texture_coords = texture_coords; - - // TODO: Do this using CPU for performance increase - vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal; -} diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl deleted file mode 100644 index 486d445..0000000 --- a/engine/src/renderer/opengl/glsl/vertex_data.glsl +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef VERTEX_DATA_GLSL -#define VERTEX_DATA_GLSL - -struct VertexData -{ - vec2 texture_coords; - vec3 world_space_pos; - vec3 world_space_normal; -}; - -#endif 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..c5ff0c6 --- /dev/null +++ b/engine/src/renderer/opengl/graphics_mesh.rs @@ -0,0 +1,216 @@ +use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns; +use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; +use opengl_bindings::vertex_array::{ + AttributeFormat as GlVertexArrayAttributeFormat, + BindVertexBufferError as GlVertexArrayBindVertexBufferError, + DataType as GlVertexArrayDataType, + VertexArray as GlVertexArray, + VertexBufferSpec as GlVertexArrayVertexBufferSpec, +}; + +use crate::mesh::{Mesh, VertexAttrType}; +use crate::renderer::MeshUsage; +use crate::shader::VertexDescription as ShaderVertexDescription; + +#[derive(Debug)] +pub struct GraphicsMesh +{ + /// Vertex and index buffer has to live as long as the vertex array + vertex_buffer: GlBuffer<u8>, + 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, + mesh_usage: MeshUsage, + vertex_desc: &ShaderVertexDescription, + ) -> Result<Self, Error> + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + let vertex_arr = GlVertexArray::new(current_context); + let vertex_buffer = GlBuffer::new(current_context); + + vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + let vertex_buf_binding_index = 0; + + if let Err(err) = vertex_arr.bind_vertex_buffer( + current_context, + vertex_buf_binding_index, + &vertex_buffer, + GlVertexArrayVertexBufferSpec { + offset: 0, + vertex_size: mesh.vertex_buf().vertex_size(), + }, + ) { + match err { + GlVertexArrayBindVertexBufferError::OffsetValueTooLarge { + value: _, + max_value: _, + } => unreachable!(), + GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge { + value, + max_value, + } => { + panic!( + "Size of vertex ({}) is too large. Must be less than {max_value}", + value + ); + } + } + } + + for vertex_attr_props in mesh.vertex_buf().vertex_attr_props() { + let vertex_field_desc = vertex_desc + .fields + .iter() + .find(|vertex_field_desc| { + *vertex_field_desc.name == vertex_attr_props.name + }) + .unwrap(); + + let attrib_index: u32 = + vertex_field_desc.varying_input_offset.try_into().unwrap(); + + vertex_arr.enable_attrib(current_context, attrib_index); + + vertex_arr.set_attrib_format( + current_context, + attrib_index, + match &vertex_attr_props.ty { + VertexAttrType::Float32 => GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: 1, + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + }, + VertexAttrType::Float32Array { length } => { + GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: (*length).try_into().unwrap(), + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + } + } + }, + ); + + vertex_arr.set_attrib_vertex_buf_binding( + current_context, + attrib_index, + vertex_buf_binding_index, + ); + } + + if let Some(indices) = mesh.indices() { + let index_buffer = GlBuffer::new(current_context); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + vertex_arr.bind_element_buffer(current_context, &index_buffer); + + return Ok(Self { + vertex_buffer: vertex_buffer, + index_buffer: Some(index_buffer), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), + vertex_arr, + }); + } + + Ok(Self { + vertex_buffer: vertex_buffer, + index_buffer: None, + element_cnt: mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), + vertex_arr, + }) + } + + pub fn update( + &mut self, + current_context: &GlCurrentContextWithFns<'_>, + mesh: &Mesh, + mesh_usage: MeshUsage, + ) -> Result<(), Error> + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + self.vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + if let Some(indices) = mesh.indices() { + let index_buffer = self + .index_buffer + .get_or_insert_with(|| GlBuffer::new(current_context)); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + self.vertex_arr + .bind_element_buffer(current_context, &index_buffer); + + self.element_cnt = indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"); + + return Ok(()); + } + + self.element_cnt = mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"); + + Ok(()) + } + + pub fn destroy(&mut self, curr_gl_ctx: &GlCurrentContextWithFns<'_>) + { + self.vertex_arr.delete(curr_gl_ctx); + self.vertex_buffer.delete(curr_gl_ctx); + + if let Some(index_buffer) = &self.index_buffer { + index_buffer.delete(curr_gl_ctx); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to store vertices in vertex buffer")] + StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), + + #[error("Failed to store indices in index buffer")] + StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), +} + +fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage +{ + match mesh_usage { + MeshUsage::Stream => GlBufferUsage::Stream, + MeshUsage::Static => GlBufferUsage::Static, + MeshUsage::Dynamic => GlBufferUsage::Dynamic, + } +} |
