diff options
Diffstat (limited to 'engine/src/rendering/backend/opengl.rs')
| -rw-r--r-- | engine/src/rendering/backend/opengl.rs | 2054 |
1 files changed, 2054 insertions, 0 deletions
diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs new file mode 100644 index 0000000..0a04d6e --- /dev/null +++ b/engine/src/rendering/backend/opengl.rs @@ -0,0 +1,2054 @@ +//! OpenGL rendering backend. + +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::Infallible; +use std::hint::cold_path; +use std::num::NonZero; + +use ecs::query::term::With; +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 intmap::IntMap; +use opengl_bindings::blending::{ + configure as gl_blending_configure, + Configuration as GlBlendingConfig, + Equation as GlBlendingEquation, + Factor as GlBlendingFactor, +}; +use opengl_bindings::buffer::{ + BindingTarget as GlBufferBindingTarget, + Buffer as GlBuffer, +}; +use opengl_bindings::debug::{ + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + MessageSeverity, + MessageSource, + MessageType, + SetDebugMessageControlError as GlSetDebugMessageControlError, +}; +use opengl_bindings::framebuffer::{ + bind as gl_bind_framebuffer, + Attachment as GlFramebufferAttachment, + ColorAttachment as GlFramebufferColorAttachment, + Framebuffer as GlFramebuffer, + Target as GlFramebufferTarget, +}; +use opengl_bindings::misc::{ + clear_buffers, + define_scissor_box as gl_define_scissor_box, + enable, + get_viewport as gl_get_viewport, + set_depth_function as gl_set_depth_function, + set_enabled, + set_face_culling_mode as gl_set_face_culling_mode, + set_viewport as gl_set_viewport, + BufferClearMask as GlBufferClearMask, + Capability, + DepthFunction as GlDepthFunction, + FaceCullingMode as GlFaceCullingMode, + PolygonMode as GlPolygonMode, + PolygonModeFace as GlPolygonModeFace, +}; +use opengl_bindings::shader::{ + Error as GlShaderError, + Kind as ShaderKind, + Program as GlShaderProgram, + Shader as GlShader, +}; +use opengl_bindings::texture::{ + CubeMapFace as GlCubeMapTextureFace, + Error as GlTextureError, + Filtering as GlTextureFiltering, + PixelDataFormat as GlTexturePixelDataFormat, + Texture as GlTexture, + Wrapping as GlTextureWrapping, +}; +use opengl_bindings::vertex_array::{ + DrawError as GlDrawError, + PrimitiveKind, + VertexArray, +}; +use opengl_bindings::{ + MakeContextCurrentError as GlMakeContextCurrentError, + MaybeCurrentContextWithFns, +}; +use raw_window_handle::WindowHandle; +use safer_ffi::layout::ReprC; +use zerocopy::IntoBytes; + +use crate::asset::Assets; +use crate::data_types::color::Color; +use crate::data_types::dimens::Dimens; +use crate::draw_flags::{PolygonMode, PolygonModeFace}; +use crate::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Query, Sole}; +use crate::reflection::EnumReflectionExt; +use crate::rendering::backend::opengl::glutin_compat::{ + DisplayBuilder, + Error as GlutinCompatError, +}; +use crate::rendering::backend::opengl::graphics_mesh::GraphicsMesh; +use crate::rendering::blending::{ + Equation as BlendingEquation, + Factor as BlendingFactor, +}; +use crate::rendering::object::{ + Id as ObjectId, + Kind as ObjectKind, + Object, + RawValue as ObjectRawValue, + Store as ObjectStore, +}; +use crate::rendering::shader::cursor::{ + Binding as ShaderBinding, + BindingLocation as ShaderBindingLocation, + BindingValue as ShaderBindingValue, +}; +use crate::rendering::shader::{ + Error as ShaderError, + Program as ShaderProgram, + ProgramMetadata as ShaderProgramMetadata, + Stage as ShaderStage, +}; +use crate::rendering::{ + AssetOrValue, + BufferClearMask, + Command, + CommandQueue, + DepthFunction, + DrawMeshOptions, + DrawProperties, + DrawPropertiesUpdateFlags, + FaceCullingMode, + FramebufferProperties, + FramebufferPropertiesUpdateFlags, + GraphicsProperties, + Surface, + SurfaceId, + TargetWindow, + TextureCreation, + TexturePixelDataFormat, + TextureUpdate, + POST_RENDER_PHASE, + RENDER_PHASE, +}; +use crate::texture::{ + Border as TextureBorder, + CubeMapFace as CubeMapTextureFace, + Filtering as TextureFiltering, + Properties as TextureProperties, + Wrapping as TextureWrapping, +}; +use crate::util::OptionExt; +use crate::vector::{Vec2, Vec3}; +use crate::windowing::dpi::PhysicalSize; +use crate::windowing::window::{ + Closed as WindowClosed, + CreationAttributes as WindowCreationAttributes, + CreationReady, + Window, +}; +use crate::windowing::Context as WindowingContext; + +mod glutin_compat; +mod graphics_mesh; + +#[derive(Debug, Component)] +struct WindowGlConfig +{ + gl_config: GlutinConfig, +} + +#[derive(Sole, Default)] +struct GraphicsContext +{ + gl_context: Option<MaybeCurrentContextWithFns>, + surfaces: HashMap<SurfaceId, GraphicsContextSurface>, + backend_resources: BackendResourceStore, +} + +#[derive(Debug, Default)] +struct BackendResourceStore +{ + inner: IntMap<BackendResourceId, BackendResource>, + next_id: BackendResourceId, +} + +impl BackendResourceStore +{ + fn try_create_resource<Err>( + &mut self, + object_store: &mut ObjectStore, + object_id: ObjectId, + resource_fn: impl FnOnce() -> Result<BackendResource, Err>, + ) -> Result<(), Err> + { + if object_store.contains_non_pending_with_id(&object_id) { + cold_path(); + tracing::error!( + ?object_id, + "Object store already contains a object with this ID" + ); + return Ok(()); + } + + let resource = resource_fn()?; + + let res_id = self.next_id; + + self.next_id.0 += 1; + + let object_kind = resource.object_kind(); + + self.inner.insert(res_id, resource); + + object_store.insert(object_id, Object::from_raw(res_id.0, object_kind)); + + Ok(()) + } + + fn get_resource( + &self, + object_store: &ObjectStore, + object_id: ObjectId, + object_kind: ObjectKind, + ) -> Option<&BackendResource> + { + let Some(object) = object_store.get_obj(&object_id) else { + cold_path(); + tracing::error!( + ?object_id, + %object_kind, + "Object does not exist in the rendering object store" + ); + return None; + }; + + if object.kind() != object_kind { + cold_path(); + tracing::error!( + ?object_id, + expected_object_kind = %object_kind, + found_object_kind = %object.kind(), + "Unexpected object kind of object in object store" + ); + return None; + } + + let resource_id = BackendResourceId(object.as_raw()); + + let Some(resource) = self.inner.get(resource_id) else { + cold_path(); + tracing::error!( + ?object_id, + %object_kind, + ?resource_id, + "Backend resource does not exist" + ); + return None; + }; + + debug_assert_eq!(resource.object_kind(), object_kind); + + Some(resource) + } + + fn get_resource_mut( + &mut self, + object_store: &ObjectStore, + object_id: ObjectId, + object_kind: ObjectKind, + ) -> Option<&mut BackendResource> + { + let Some(object) = object_store.get_obj(&object_id) else { + cold_path(); + tracing::error!( + ?object_id, + %object_kind, + "Object does not exist in the rendering object store" + ); + return None; + }; + + if object.kind() != object_kind { + cold_path(); + tracing::error!( + ?object_id, + expected_object_kind = %object_kind, + found_object_kind = %object.kind(), + "Unexpected object kind of object in object store" + ); + return None; + } + + let resource_id = BackendResourceId(object.as_raw()); + + let Some(resource) = self.inner.get_mut(resource_id) else { + cold_path(); + tracing::error!( + ?object_id, + %object_kind, + ?resource_id, + "Backend resource does not exist" + ); + return None; + }; + + debug_assert_eq!(resource.object_kind(), object_kind); + + Some(resource) + } + + fn remove_resource( + &mut self, + object_store: &mut ObjectStore, + object_id: ObjectId, + object_kind: ObjectKind, + curr_gl_ctx: &MaybeCurrentContextWithFns, + ) + { + if let Some(object) = object_store.get_obj(&object_id) { + if object.kind() != object_kind { + cold_path(); + tracing::error!( + ?object_id, + expected_object_kind = %object_kind, + found_object_kind = %object.kind(), + "Unexpected object kind" + ); + return; + } + } + + let Some(object) = object_store.remove(&object_id).flatten() else { + tracing::error!( + ?object_id, + %object_kind, + "Object does not exist in the rendering object store" + ); + return; + }; + + let resource_id = BackendResourceId(object.as_raw()); + + let Some(resource) = self.inner.remove(resource_id) else { + cold_path(); + tracing::error!( + ?object_id, + %object_kind, + ?resource_id, + "Backend resource does not exist" + ); + return; + }; + + resource.destroy(curr_gl_ctx); + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct BackendResourceId(ObjectRawValue); + +impl intmap::IntKey for BackendResourceId +{ + type Int = ObjectRawValue; + + const PRIME: Self::Int = <ObjectRawValue as intmap::IntKey>::PRIME; + + fn into_int(self) -> Self::Int + { + self.0.into_int() + } +} + +#[derive(Debug)] +struct GraphicsContextSurface +{ + window_surface: GlutinSurface<GlutinWindowSurface>, + size: PhysicalSize<u32>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct ShaderBindingIndex(u32); + +impl intmap::IntKey for ShaderBindingIndex +{ + type Int = u32; + + const PRIME: Self::Int = <u32 as intmap::IntKey>::PRIME; + + fn into_int(self) -> Self::Int + { + self.0 + } +} + +#[derive(Debug, Clone)] +enum BackendShaderBinding +{ + Uniform(GlBuffer<u8>), + Texture(GlTexture), +} + +impl BackendShaderBinding +{ + fn bind( + &self, + gl_context: &MaybeCurrentContextWithFns, + binding_index: ShaderBindingIndex, + ) + { + match self { + BackendShaderBinding::Uniform(uniform_buf) => { + uniform_buf.bind_to_indexed_target( + gl_context, + GlBufferBindingTarget::UniformBuffer, + binding_index.0, + ); + } + BackendShaderBinding::Texture(texture) => { + texture.bind_to_texture_unit(gl_context, binding_index.0); + } + } + } +} + +#[derive(Debug)] +enum BackendResource +{ + Mesh + { + mesh: GraphicsMesh, + vertex_attrs_updated_for_shader: Option<ObjectId>, + }, + Shader + { + program: GlShaderProgram, + program_metadata: ShaderProgramMetadata, + bindings: IntMap<ShaderBindingIndex, BackendShaderBinding>, + }, + Texture + { + texture: GlTexture, + pixel_data_format: TexturePixelDataFormat, + kind: TextureKind, + }, + Framebuffer + { + framebuffer: GlFramebuffer + }, +} + +impl BackendResource +{ + fn object_kind(&self) -> ObjectKind + { + match self { + Self::Mesh { .. } => ObjectKind::Mesh, + Self::Shader { .. } => ObjectKind::ShaderProgram, + Self::Texture { .. } => ObjectKind::Texture, + Self::Framebuffer { .. } => ObjectKind::Framebuffer, + } + } + + fn destroy(&self, curr_gl_ctx: &MaybeCurrentContextWithFns) + { + match self { + Self::Mesh { mesh, .. } => { + mesh.destroy(curr_gl_ctx); + } + Self::Shader { program, .. } => { + program.clone().delete(curr_gl_ctx); + } + Self::Texture { texture, .. } => { + texture.clone().delete(curr_gl_ctx); + } + Self::Framebuffer { framebuffer } => { + framebuffer.clone().delete(curr_gl_ctx); + } + } + } +} + +#[derive(Debug)] +enum TextureKind +{ + Texture2D, + CubeMap, +} + +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Extension {} + +impl crate::ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) + { + collector.add_system(*RENDER_PHASE, handle_commands); + + collector.add_system(*POST_RENDER_PHASE, prepare_windows); + collector.add_system(*POST_RENDER_PHASE, init_window_graphics); + + let _ = collector.add_sole(GraphicsContext::default()); + } +} + +fn prepare_windows( + window_query: Query<( + Option<&Window>, + &mut WindowCreationAttributes, + With<TargetWindow>, + Without<CreationReady>, + Without<WindowGlConfig>, + Without<WindowClosed>, + )>, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, + mut actions: Actions, +) -> Result<(), crate::Error> +{ + let windowing_context = windowing_context.get()?; + let graphics_props = graphics_props.get()?; + + let Some(display_handle) = windowing_context.display_handle() else { + return Ok(()); + }; + + for (window_ent_id, (window, mut window_creation_attrs)) in + window_query.iter_with_euids() + { + tracing::debug!("Preparing window entity {window_ent_id} for use in rendering"); + + let mut glutin_config_template_builder = + glutin::config::ConfigTemplateBuilder::new(); + + if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt { + glutin_config_template_builder = glutin_config_template_builder + .with_multisampling(multisampling_sample_cnt); + } + + let window_handle = match window + .as_ref() + .and_then(|window| unsafe { + windowing_context.get_window_as_handle(&window.wid()) + }) + .transpose() + { + Ok(window_handle) => window_handle, + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; + + let (new_window_creation_attrs, gl_config) = match DisplayBuilder::default() + .with_window_attributes(window_creation_attrs.clone()) + .build( + window_handle, + &display_handle, + glutin_config_template_builder, + |mut cfgs| cfgs.next(), + ) { + Ok((new_window_creation_attrs, gl_config)) => { + (new_window_creation_attrs, gl_config) + } + Err(GlutinCompatError::WindowRequired) => { + actions.add_components(window_ent_id, (CreationReady,)); + continue; + } + Err(err) => { + tracing::error!("Failed to create platform graphics display: {err}"); + continue; + } + }; + + *window_creation_attrs = new_window_creation_attrs; + + actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); + + if window.is_none() { + actions.add_components(window_ent_id, (CreationReady,)); + } + } + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn init_window_graphics( + window_query: Query<( + &Window, + &WindowGlConfig, + With<TargetWindow>, + Without<Surface>, + )>, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, + mut graphics_ctx: Single<GraphicsContext>, + mut actions: Actions, +) -> Result<(), crate::Error> +{ + let Ok(graphics_ctx) = graphics_ctx.get_mut() else { + unreachable!(); + }; + + let windowing_context = windowing_context.get()?; + let graphics_props = graphics_props.get()?; + + for (window_ent_id, (window, window_gl_config)) in window_query.iter_with_euids() { + tracing::info!( + window_entity_id=%window_ent_id, + window_title=&*window.title, + "Initializing graphics for window" + ); + + let display = window_gl_config.gl_config.display(); + + let window_handle = + match unsafe { windowing_context.get_window_as_handle(&window.wid()) } + .transpose() + { + Ok(Some(window_handle)) => window_handle, + Ok(None) => { + tracing::error!( + wid = ?window.wid(), + entity_id = %window_ent_id, + "Windowing context does not contain window" + ); + continue; + } + Err(err) => { + tracing::error!("Failed to get window handle: {err}"); + continue; + } + }; + + let Ok(window_inner_size) = + PhysicalSize::<NonZero<u32>>::try_convert_from(window.inner_size) + else { + tracing::error!( + "Cannot create a surface for a window with a width/height of 0", + ); + continue; + }; + + let window_surface = match unsafe { + display.create_window_surface( + &window_gl_config.gl_config, + &glutin::surface::SurfaceAttributesBuilder::< + glutin::surface::WindowSurface, + >::new() + .build( + window_handle.as_raw(), + window_inner_size.width, + window_inner_size.height, + ), + ) + } { + Ok(window_surface) => window_surface, + Err(err) => { + tracing::error!("Failed to create window surface: {err}"); + continue; + } + }; + + let gl_context = match graphics_ctx.gl_context.get_or_try_insert_with_fn(|| { + create_gl_context( + &window_gl_config.gl_config, + graphics_props, + window_handle, + &window_surface, + ) + }) { + Ok(gl_context) => gl_context, + Err(err) => { + tracing::error!("Failed to create GL context: {err}"); + continue; + } + }; + + if let Err(err) = gl_context.make_current(&window_surface) { + tracing::error!("Failed to make GL context current: {err}"); + continue; + } + + if let Err(err) = gl_set_viewport( + gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &opengl_bindings::data_types::Dimens { + width: window.inner_size.width, + height: window.inner_size.height, + }, + ) { + tracing::error!("Failed to set viewport: {err}"); + } + + set_enabled(gl_context, Capability::DepthTest, graphics_props.depth_test); + + set_enabled( + gl_context, + Capability::MultiSample, + graphics_props.multisampling_sample_cnt.is_some(), + ); + + if graphics_props.debug { + enable(gl_context, Capability::DebugOutput); + enable(gl_context, Capability::DebugOutputSynchronous); + + set_debug_message_callback(gl_context, opengl_debug_message_cb); + + match set_debug_message_control( + gl_context, + None, + None, + None, + &[], + MessageIdsAction::Disable, + ) { + Ok(()) => {} + Err(GlSetDebugMessageControlError::TooManyIds { + id_cnt: _, + max_id_cnt: _, + }) => { + unreachable!() // No ids are given + } + } + } + + let surface_id = SurfaceId::new_unique(); + + actions.add_components( + window_ent_id, + (Surface { + id: surface_id, + size: window.inner_size, + },), + ); + + graphics_ctx.surfaces.insert( + surface_id, + GraphicsContextSurface { + window_surface, + size: window.inner_size, + }, + ); + } + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn handle_commands( + mut graphics_ctx: Single<GraphicsContext>, + mut object_store: Single<ObjectStore>, + mut command_queue: Single<CommandQueue>, + assets: Single<Assets>, +) -> Result<(), crate::Error> +{ + let Ok(graphics_ctx) = graphics_ctx.get_mut() else { + unreachable!(); + }; + + let object_store = object_store.get_mut()?; + let command_queue = command_queue.get_mut()?; + let assets = assets.get()?; + + let GraphicsContext { + ref gl_context, + ref mut surfaces, + ref mut backend_resources, + } = *graphics_ctx; + + let Some(gl_context) = gl_context else { + return Ok(()); + }; + + let mut activated_gl_shader_program: Option<( + ObjectId, + GlShaderProgram, + ShaderProgramMetadata, + )> = None; + + for command in command_queue.drain() { + let tracing_span = tracing::info_span!( + "handle_cmd", + command = %command.get_variant_reflection().name, + ); + let _tracing_span_enter = tracing_span.enter(); + + match command { + Command::RemoveSurface(surface_id) => { + let Some(surface) = surfaces.remove(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if surface.window_surface.is_current(gl_context.context()) { + if let Err(err) = gl_context.context().make_not_current_in_place() { + tracing::error!("Failed to make GL context not current: {err}"); + } + + if let Err(err) = gl_context.make_current_surfaceless() { + tracing::error!("Failed to make GL context current: {err}"); + } + } + + drop(surface); + } + Command::MakeCurrent(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if let Err(err) = gl_context.make_current(&surface.window_surface) { + tracing::error!("Failed to make graphics context current: {err}"); + continue; + } + + if let Err(err) = gl_set_viewport( + gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &opengl_bindings::data_types::Dimens { + width: surface.size.width, + height: surface.size.height, + }, + ) { + tracing::error!("Failed to set viewport: {err}"); + } + } + Command::SetSurfaceSize(surface_id, new_surface_size) => { + let Some(surface) = surfaces.get_mut(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + surface.size = new_surface_size; + + if !surface.window_surface.is_current(gl_context.context()) { + continue; + } + + if let Err(err) = gl_set_viewport( + gl_context, + &Vec2 { x: 0, y: 0 }.into(), + &opengl_bindings::data_types::Dimens { + width: surface.size.width, + height: surface.size.height, + }, + ) { + tracing::error!("Failed to set viewport: {err}"); + } + } + Command::ClearBuffers(buffer_clear_mask) => { + let mut clear_mask = GlBufferClearMask::empty(); + + clear_mask.set( + GlBufferClearMask::COLOR, + buffer_clear_mask.contains(BufferClearMask::COLOR), + ); + + clear_mask.set( + GlBufferClearMask::DEPTH, + buffer_clear_mask.contains(BufferClearMask::DEPTH), + ); + + clear_mask.set( + GlBufferClearMask::STENCIL, + buffer_clear_mask.contains(BufferClearMask::STENCIL), + ); + + clear_buffers(gl_context, clear_mask); + } + Command::SwapBuffers(surface_id) => { + let Some(surface) = surfaces.get(&surface_id) else { + tracing::error!(surface_id=?surface_id, "Surface does not exist"); + continue; + }; + + if let Err(err) = + surface.window_surface.swap_buffers(gl_context.context()) + { + tracing::error!("Failed to swap buffers: {err}"); + } + } + Command::CreateShaderProgram(object_id, shader_program) => { + if let Err(err) = backend_resources + .try_create_resource::<CreateShaderError>( + object_store, + object_id, + || { + Ok(BackendResource::Shader { + program: create_shader_program( + gl_context, + &shader_program, + )?, + program_metadata: shader_program.metadata().clone(), + bindings: IntMap::with_capacity(4), + }) + }, + ) + { + tracing::error!("Failed to create shader program: {err}"); + } + } + Command::ActivateShader(shader_object_id) => { + let Some(resource) = backend_resources.get_resource( + object_store, + shader_object_id, + ObjectKind::ShaderProgram, + ) else { + continue; + }; + + let BackendResource::Shader { program, program_metadata, bindings } = + resource + else { + unreachable!(); + }; + + program.activate(gl_context); + + for (binding_index, binding) in bindings { + binding.bind(gl_context, binding_index); + } + + activated_gl_shader_program = + Some((shader_object_id, program.clone(), program_metadata.clone())); + } + Command::SetShaderBinding( + shader_object_id, + ShaderBinding { + location: binding_location, + value: binding_value, + }, + ) => { + let binding = set_shader_binding( + backend_resources, + object_store, + gl_context, + shader_object_id, + &binding_location, + binding_value, + ); + + if let Some(binding) = binding { + if Some(shader_object_id) + == activated_gl_shader_program + .as_ref() + .map(|(obj_id, ..)| *obj_id) + { + binding.bind( + gl_context, + ShaderBindingIndex(binding_location.binding_index), + ); + } + } + } + Command::CreateTexture { + obj_id, + pixel_data_format, + creation, + properties, + } => { + let kind = match &creation { + TextureCreation::Texture2D { .. } => TextureKind::Texture2D, + TextureCreation::CubeMap { .. } => TextureKind::CubeMap, + }; + + if let Err(err) = backend_resources + .try_create_resource::<opengl_bindings::texture::Error>( + object_store, + obj_id, + || { + let gl_texture = create_gl_texture( + gl_context, + creation, + pixel_data_format, + &properties, + )?; + + Ok(BackendResource::Texture { + texture: gl_texture, + pixel_data_format, + kind, + }) + }, + ) + { + tracing::error!("Failed to create texture: {err}"); + } + } + Command::UpdateTexture { + obj_id, + pixels, + pixel_data_format, + update, + } => { + let Some(texture_resource) = backend_resources.get_resource( + object_store, + obj_id, + ObjectKind::Texture, + ) else { + continue; + }; + + let BackendResource::Texture { + texture: gl_texture, + pixel_data_format: tex_pixel_data_format, + kind: tex_metadata, + } = texture_resource + else { + unreachable!(); + }; + + if !matches!( + (&update, tex_metadata), + (TextureUpdate::Texture2D { .. }, TextureKind::Texture2D) + | (TextureUpdate::CubeMap { .. }, TextureKind::CubeMap) + ) { + tracing::error!( + texture_object_id = ?obj_id, + "Texture is of incorrect kind" + ); + continue; + } + + if &pixel_data_format != tex_pixel_data_format { + cold_path(); + tracing::error!( + texture_object_id = ?obj_id, + expected_pixel_data_format = ?*tex_pixel_data_format, + found_pixel_data_format = ?pixel_data_format, + "Texture has incorrect pixel data format" + ); + continue; + } + + if let Err(err) = update_texture_object( + gl_context, + gl_texture, + pixels, + pixel_data_format, + update, + ) { + tracing::error!("Failed to update texture object: {err}"); + } + } + Command::RemoveTexture(object_id) => { + backend_resources.remove_resource( + object_store, + object_id, + ObjectKind::Texture, + gl_context, + ); + } + Command::CreateFramebuffer(object_id) => { + backend_resources.try_create_resource::<Infallible>( + object_store, + object_id, + || { + Ok(BackendResource::Framebuffer { + framebuffer: GlFramebuffer::new(gl_context), + }) + }, + ); + } + Command::RemoveFramebuffer(object_id) => { + backend_resources.remove_resource( + object_store, + object_id, + ObjectKind::Framebuffer, + gl_context, + ); + } + Command::UpdateFramebufferProperties { + object_id, + properties, + update_flags, + } => { + update_framebuffer_properties( + backend_resources, + object_store, + gl_context, + object_id, + properties, + update_flags, + ); + } + Command::CreateMesh { obj_id, mesh, usage: mesh_usage } => { + let mesh = match &mesh { + AssetOrValue::Asset(mesh_asset) => { + let Some(mesh) = assets.get(mesh_asset) else { + tracing::error!( + asset_id=?mesh_asset.id(), + "Mesh asset does not exist" + ); + continue; + }; + + mesh + } + AssetOrValue::Value(mesh) => mesh, + }; + + if let Err(err) = backend_resources + .try_create_resource::<graphics_mesh::Error>( + object_store, + obj_id, + || { + Ok(BackendResource::Mesh { + mesh: GraphicsMesh::new(gl_context, mesh, mesh_usage)?, + vertex_attrs_updated_for_shader: None, + }) + }, + ) + { + tracing::error!("Failed to create mesh: {err}"); + } + } + Command::UpdateMesh { obj_id, mesh, usage: mesh_usage } => { + let Some(resource) = backend_resources.get_resource_mut( + object_store, + obj_id, + ObjectKind::Mesh, + ) else { + continue; + }; + + let BackendResource::Mesh { mesh: graphics_mesh, .. } = resource else { + unreachable!(); + }; + + if let Err(err) = graphics_mesh.update(gl_context, &mesh, mesh_usage) { + tracing::error!("Failed to update mesh: {err}"); + } + } + Command::RemoveMesh(object_id) => { + backend_resources.remove_resource( + object_store, + object_id, + ObjectKind::Mesh, + gl_context, + ); + } + Command::DrawMesh(object_id, draw_mesh_opts) => { + let Some(resource) = backend_resources.get_resource_mut( + object_store, + object_id, + ObjectKind::Mesh, + ) else { + continue; + }; + + let BackendResource::Mesh { + mesh: graphics_mesh, + vertex_attrs_updated_for_shader, + } = resource + else { + unreachable!(); + }; + + let Some((shader_program_obj_id, _, shader_program_metadata)) = + &activated_gl_shader_program + else { + tracing::error!("No shader program is activated"); + continue; + }; + + if vertex_attrs_updated_for_shader.is_none_or( + |vertex_attrs_updated_for_shader| { + vertex_attrs_updated_for_shader != *shader_program_obj_id + }, + ) { + let Some(shader_program_vertex_desc) = + &shader_program_metadata.vertex_desc + else { + tracing::error!( + "Activated shader program does not have a vertex description" + ); + continue; + }; + + if let Err(err) = graphics_mesh.update_vertex_attrs_for_shader( + gl_context, + shader_program_vertex_desc, + ) { + tracing::error!("Cannot draw mesh: {err}"); + continue; + } + + *vertex_attrs_updated_for_shader = Some(*shader_program_obj_id); + } + + if let Err(err) = draw_mesh(gl_context, graphics_mesh, &draw_mesh_opts) { + tracing::error!("Failed to draw mesh: {err}"); + } + } + Command::UpdateDrawProperties(properties, update_flags) => { + update_draw_properties( + backend_resources, + object_store, + gl_context, + properties, + update_flags, + ); + } + } + } + + Ok(()) +} + +fn create_gl_context( + gl_config: &GlutinConfig, + graphics_props: &GraphicsProperties, + window_handle: WindowHandle<'_>, + surface: &GlutinSurface<GlutinWindowSurface>, +) -> Result<MaybeCurrentContextWithFns, CreateGlContextError> +{ + let display = gl_config.display(); + + let glutin_context = unsafe { + display.create_context( + gl_config, + &glutin::context::ContextAttributesBuilder::new() + .with_debug(graphics_props.debug) + .build(Some(window_handle.as_raw())), + ) + } + .map_err(CreateGlContextError::CreateGlutinContext)?; + + MaybeCurrentContextWithFns::new(glutin_context, surface) + .map_err(CreateGlContextError::MakeContextCurrent) +} + +#[derive(Debug, thiserror::Error)] +enum CreateGlContextError +{ + #[error("Glutin context creation failed")] + CreateGlutinContext(#[source] GlutinError), + + #[error("Making GL context current failed")] + MakeContextCurrent(#[source] GlMakeContextCurrentError), +} + +#[tracing::instrument(skip_all)] +fn update_texture_object( + curr_gl_ctx: &MaybeCurrentContextWithFns, + gl_texture: &GlTexture, + pixels: Box<[u8]>, + pixel_data_format: TexturePixelDataFormat, + update: TextureUpdate, +) -> Result<(), GlTextureError> +{ + match update { + TextureUpdate::Texture2D { size, offset } => { + gl_texture.store_image_2d( + curr_gl_ctx, + 0, + offset.into(), + size.into(), + tex_pixel_data_format_into_gl(pixel_data_format), + &pixels, + )?; + } + TextureUpdate::CubeMap { size, offset, face } => { + gl_texture.store_image_3d( + curr_gl_ctx, + 0, + Vec3 { + x: offset.x, + y: offset.y, + z: cube_map_texture_face_to_gl(face) as u32, + } + .into(), + [size.width, size.height, 1], + tex_pixel_data_format_into_gl(pixel_data_format), + &pixels, + )?; + } + } + + gl_texture.generate_mipmap(curr_gl_ctx); + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn update_draw_properties( + backend_resources: &BackendResourceStore, + object_store: &ObjectStore, + gl_context: &MaybeCurrentContextWithFns, + properties: DrawProperties, + update_flags: DrawPropertiesUpdateFlags, +) +{ + if update_flags.contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) { + opengl_bindings::misc::set_polygon_mode( + gl_context, + polygon_mode_face_to_gl(properties.polygon_mode_config.face), + polygon_mode_to_gl(properties.polygon_mode_config.mode), + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) { + set_enabled(gl_context, Capability::Blend, properties.blending_enabled); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) { + gl_blending_configure( + gl_context, + GlBlendingConfig::default() + .with_source_factor(blending_factor_to_gl( + properties.blending_config.source_factor, + )) + .with_destination_factor(blending_factor_to_gl( + properties.blending_config.destination_factor, + )) + .with_equation(blending_equation_to_gl( + properties.blending_config.equation, + )), + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) { + set_enabled( + gl_context, + Capability::DepthTest, + properties.depth_test_enabled, + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::DEPTH_FUNCTION) { + gl_set_depth_function( + gl_context, + match properties.depth_function { + DepthFunction::Never => GlDepthFunction::Never, + DepthFunction::Less => GlDepthFunction::Less, + DepthFunction::Equal => GlDepthFunction::Equal, + DepthFunction::LessOrEqual => GlDepthFunction::LessOrEqual, + DepthFunction::Greater => GlDepthFunction::Greater, + DepthFunction::NotEqual => GlDepthFunction::NotEqual, + DepthFunction::GreaterOrEqual => GlDepthFunction::GreaterOrEqual, + DepthFunction::Always => GlDepthFunction::Always, + }, + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) { + set_enabled( + gl_context, + Capability::ScissorTest, + properties.scissor_test_enabled, + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) { + gl_define_scissor_box( + gl_context, + properties.scissor_box.lower_left_corner_pos.into(), + properties + .scissor_box + .size + .unwrap_or_else(|| { + let (_, viewport_size) = gl_get_viewport(gl_context); + + 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 update_flags.contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) { + set_enabled( + gl_context, + Capability::CullFace, + properties.face_culling_enabled, + ); + } + + if update_flags.contains(DrawPropertiesUpdateFlags::FACE_CULLING_MODE) { + gl_set_face_culling_mode( + gl_context, + match properties.face_culling_mode { + FaceCullingMode::Front => GlFaceCullingMode::Front, + FaceCullingMode::Back => GlFaceCullingMode::Back, + FaceCullingMode::FrontAndBack => GlFaceCullingMode::FrontAndBack, + }, + ); + } + + 'if_draw_fb: { + if update_flags.contains(DrawPropertiesUpdateFlags::DRAW_FRAMEBUFFER) { + let Some(framebuffer_obj_id) = properties.draw_framebuffer else { + gl_bind_framebuffer(gl_context, GlFramebufferTarget::Draw, None); + break 'if_draw_fb; + }; + + let Some(framebuffer_resource) = backend_resources.get_resource( + object_store, + framebuffer_obj_id, + ObjectKind::Framebuffer, + ) else { + break 'if_draw_fb; + }; + + let BackendResource::Framebuffer { framebuffer } = framebuffer_resource + else { + unreachable!(); + }; + + gl_bind_framebuffer(gl_context, GlFramebufferTarget::Draw, Some(framebuffer)); + } + } + + 'if_read_fb: { + if update_flags.contains(DrawPropertiesUpdateFlags::READ_FRAMEBUFFER) { + let Some(framebuffer_obj_id) = properties.read_framebuffer else { + gl_bind_framebuffer(gl_context, GlFramebufferTarget::Read, None); + break 'if_read_fb; + }; + + let Some(framebuffer_resource) = backend_resources.get_resource( + object_store, + framebuffer_obj_id, + ObjectKind::Framebuffer, + ) else { + break 'if_read_fb; + }; + + let BackendResource::Framebuffer { framebuffer } = framebuffer_resource + else { + unreachable!(); + }; + + gl_bind_framebuffer(gl_context, GlFramebufferTarget::Read, Some(framebuffer)); + } + } +} + +#[tracing::instrument(skip_all)] +fn update_framebuffer_properties( + backend_resources: &BackendResourceStore, + object_store: &ObjectStore, + current_context: &MaybeCurrentContextWithFns, + framebuffer_object_id: ObjectId, + properties: FramebufferProperties, + update_flags: FramebufferPropertiesUpdateFlags, +) +{ + let Some(resource) = backend_resources.get_resource( + object_store, + framebuffer_object_id, + ObjectKind::Framebuffer, + ) else { + return; + }; + + let BackendResource::Framebuffer { framebuffer } = resource else { + unreachable!(); + }; + + 'if_dt: { + if update_flags.contains(FramebufferPropertiesUpdateFlags::DEPTH_TEXTURE) { + let Some(framebuffer_depth_texture) = &properties.depth_texture else { + framebuffer + .detach_texture(current_context, GlFramebufferAttachment::Depth); + + break 'if_dt; + }; + + let Some(depth_texture_resource) = backend_resources.get_resource( + object_store, + framebuffer_depth_texture.object_id, + ObjectKind::Texture, + ) else { + break 'if_dt; + }; + + let BackendResource::Texture { texture: depth_texture, .. } = + depth_texture_resource + else { + unreachable!(); + }; + + framebuffer.attach_texture( + current_context, + GlFramebufferAttachment::Depth, + depth_texture.clone(), + framebuffer_depth_texture.mipmap_level, + ); + } + } + + if update_flags.contains(FramebufferPropertiesUpdateFlags::COLOR_DRAW_BUFFER) { + framebuffer.set_draw_buffer( + current_context, + properties.color_draw_buffer.map(|color_texture_index| { + GlFramebufferColorAttachment(color_texture_index.0) + }), + ); + } +} + +fn set_shader_binding( + backend_resources: &mut BackendResourceStore, + object_store: &ObjectStore, + gl_context: &MaybeCurrentContextWithFns, + shader_object_id: ObjectId, + binding_location: &ShaderBindingLocation, + binding_value: ShaderBindingValue, +) -> Option<BackendShaderBinding> +{ + if let ShaderBindingValue::Texture(texture_object_id, _) = &binding_value { + let gl_texture = match backend_resources.get_resource( + object_store, + *texture_object_id, + ObjectKind::Texture, + ) { + Some(BackendResource::Texture { texture, .. }) => texture.clone(), + Some(_) => unreachable!(), + None => return None, + }; + + let shader_bindings = match backend_resources.get_resource_mut( + object_store, + shader_object_id, + ObjectKind::ShaderProgram, + ) { + Some(BackendResource::Shader { bindings, .. }) => bindings, + Some(_) => unreachable!(), + None => return None, + }; + + // TODO: Textures should probably also be handled somehow here + if let Some(BackendShaderBinding::Uniform(prev_binding_uniform_buf)) = + shader_bindings.remove(ShaderBindingIndex(binding_location.binding_index)) + { + prev_binding_uniform_buf.delete(gl_context); + } + + return Some( + shader_bindings + .entry(ShaderBindingIndex(binding_location.binding_index)) + .set_or_insert_with(|| BackendShaderBinding::Texture(gl_texture.clone())) + .clone(), + ); + + // if Some(shader_object_id) + // == activated_gl_shader_program + // .as_ref() + // .map(|(obj_id, ..)| *obj_id) + // { + // BackendShaderBinding::Texture(gl_texture.clone()).bind( + // gl_context, + // ShaderBindingIndex(binding_location.binding_index), + // ); + // } + } + + let shader_bindings = match backend_resources.get_resource_mut( + object_store, + shader_object_id, + ObjectKind::ShaderProgram, + ) { + Some(BackendResource::Shader { bindings, .. }) => bindings, + Some(_) => unreachable!(), + None => return None, + }; + + let binding = if let Some(binding @ BackendShaderBinding::Uniform(_)) = + shader_bindings + .get(ShaderBindingIndex(binding_location.binding_index)) + .cloned() + { + binding + } else { + let uniform_buf = GlBuffer::<u8>::new(gl_context); + + uniform_buf + .init( + gl_context, + binding_location.binding_size, + opengl_bindings::buffer::Usage::Dynamic, + ) + .unwrap(); + + shader_bindings + .entry(ShaderBindingIndex(binding_location.binding_index)) + .set_or_insert_with(|| BackendShaderBinding::Uniform(uniform_buf)) + .clone() + }; + + let BackendShaderBinding::Uniform(binding_uniform_buf) = &binding else { + unreachable!(); + }; + + let fvec3_value; + let mut color_value = [0.0; 4]; + + binding_uniform_buf + .store_at_byte_offset( + gl_context, + binding_location.byte_offset, + match binding_value { + ShaderBindingValue::Uint(ref value) => value.as_bytes(), + ShaderBindingValue::Int(ref value) => value.as_bytes(), + ShaderBindingValue::Float(ref value) => value.as_bytes(), + ShaderBindingValue::FVec3(value) => { + fvec3_value = [value.x, value.y, value.z]; + fvec3_value.as_bytes() + } + ShaderBindingValue::Color(value) => match value { + Color::Rgb(value) => { + color_value[..3].copy_from_slice(&[value.r, value.g, value.b]); + + color_value[..3].as_bytes() + } + Color::Rgba(value) => { + color_value[..4] + .copy_from_slice(&[value.r, value.g, value.b, value.a]); + + color_value[..4].as_bytes() + } + }, + ShaderBindingValue::FMat4x4(ref value) => value.items().as_bytes(), + ShaderBindingValue::Texture(_, _) => unreachable!(), + }, + ) + .unwrap(); + + Some(binding) + + // if Some(shader_object_id) + // == activated_gl_shader_program + // .as_ref() + // .map(|(obj_id, ..)| *obj_id) + // { + // BackendShaderBinding::Uniform(binding_uniform_buf.clone()).bind( + // gl_context, + // ShaderBindingIndex(binding_location.binding_index), + // ); + // } +} + +fn draw_mesh( + current_context: &MaybeCurrentContextWithFns, + graphics_mesh: &GraphicsMesh, + opts: &DrawMeshOptions, +) -> Result<(), GlDrawError> +{ + graphics_mesh.vertex_arr.bind(current_context); + + if graphics_mesh.index_buffer.is_some() { + VertexArray::draw_elements( + current_context, + opengl_bindings::vertex_array::DrawElementsOptions { + primitive_kind: PrimitiveKind::Triangles, + element_offset: opts.element_offset, + element_cnt: opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + vertex_offset: opts.vertex_offset, + }, + )?; + } else { + VertexArray::draw_arrays( + current_context, + PrimitiveKind::Triangles, + opts.vertex_offset, + opts.element_cnt.unwrap_or(graphics_mesh.element_cnt), + )?; + } + + Ok(()) +} + +fn tex_pixel_data_format_into_gl( + tex_pixel_data_format: TexturePixelDataFormat, +) -> GlTexturePixelDataFormat +{ + match tex_pixel_data_format { + TexturePixelDataFormat::Rgb(data_type) => { + GlTexturePixelDataFormat::Rgb(match data_type { + crate::rendering::RgbTextureDataType::UnsignedByte => { + opengl_bindings::texture::RgbDataType::UnsignedByte + } + }) + } + TexturePixelDataFormat::Srgb(data_type) => { + GlTexturePixelDataFormat::Srgb(match data_type { + crate::rendering::SrgbTextureDataType::UnsignedByte => { + opengl_bindings::texture::SrgbDataType::UnsignedByte + } + }) + } + TexturePixelDataFormat::Rgba(data_type) => { + GlTexturePixelDataFormat::Rgba(match data_type { + crate::rendering::RgbaTextureDataType::UnsignedByte => { + opengl_bindings::texture::RgbaDataType::UnsignedByte + } + }) + } + TexturePixelDataFormat::Srgba(data_type) => { + GlTexturePixelDataFormat::Srgba(match data_type { + crate::rendering::SrgbaTextureDataType::UnsignedByte => { + opengl_bindings::texture::SrgbaDataType::UnsignedByte + } + }) + } + TexturePixelDataFormat::Depth(data_type) => { + GlTexturePixelDataFormat::DepthComponent(match data_type { + crate::rendering::DepthTextureDataType::Float32 => { + opengl_bindings::texture::DepthComponentDataType::Float32 + } + }) + } + } +} + +fn create_gl_texture( + curr_gl_context: &MaybeCurrentContextWithFns, + texture_creation: TextureCreation, + texture_pixel_data_format: TexturePixelDataFormat, + texture_properties: &TextureProperties, +) -> Result<GlTexture, GlTextureError> +{ + let gl_texture = match texture_creation { + TextureCreation::Texture2D { size, image } => { + GlTexture::builder().size(size.into()).create_2d( + curr_gl_context, + image.as_deref(), + tex_pixel_data_format_into_gl(texture_pixel_data_format), + ) + } + TextureCreation::CubeMap { size, images } => { + GlTexture::builder().size(size.into()).create_cube_map( + curr_gl_context, + images.as_ref().map(|images| { + images.each_ref().map(|(face, image)| { + (cube_map_texture_face_to_gl(*face), &**image) + }) + }), + tex_pixel_data_format_into_gl(texture_pixel_data_format), + ) + } + }?; + + gl_texture.set_wrap( + curr_gl_context, + texture_wrapping_to_gl(texture_properties.wrap), + ); + + gl_texture.set_magnifying_filter( + curr_gl_context, + texture_filtering_to_gl(texture_properties.magnifying_filter), + ); + + gl_texture.set_minifying_filter( + curr_gl_context, + texture_filtering_to_gl(texture_properties.minifying_filter), + ); + + if let Some(border) = texture_properties.border.as_ref() { + gl_texture.set_float_border_values( + curr_gl_context, + match border { + TextureBorder::RgbaF32(color) => [color.r, color.g, color.b, color.a], + TextureBorder::Depth(depth) => [*depth, 0.0, 0.0, 0.0], + }, + ); + } + + Ok(gl_texture) +} + +fn cube_map_texture_face_to_gl(face: CubeMapTextureFace) -> GlCubeMapTextureFace +{ + match face { + CubeMapTextureFace::PositiveX => GlCubeMapTextureFace::PositiveX, + CubeMapTextureFace::NegativeX => GlCubeMapTextureFace::NegativeX, + CubeMapTextureFace::PositiveY => GlCubeMapTextureFace::PositiveY, + CubeMapTextureFace::NegativeY => GlCubeMapTextureFace::NegativeY, + CubeMapTextureFace::PositiveZ => GlCubeMapTextureFace::PositiveZ, + CubeMapTextureFace::NegativeZ => GlCubeMapTextureFace::NegativeZ, + } +} + +fn create_shader_program( + current_context: &MaybeCurrentContextWithFns, + shader_program: &ShaderProgram, +) -> Result<GlShaderProgram, CreateShaderError> +{ + let shader_program_reflection = shader_program.reflection(0).expect("Not possible"); + + let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex) + })?; + + let vertex_shader_entry_point_code = shader_program + .get_entry_point_code(vs_entry_point_index.try_into().expect( + "Vertex shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Vertex, + entrypoint: vs_entry_point_reflection + .name() + .map_or("(none)".into(), |name| name.to_string().into()), + })?; + + let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection + .entry_points() + .enumerate() + .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment) + .ok_or_else(|| { + CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment) + })?; + + let fragment_shader_entry_point_code = shader_program + .get_entry_point_code(fs_entry_point_index.try_into().expect( + "Fragment shader entry point index does not fit in 32-bit unsigned int", + )) + .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { + err, + stage: ShaderStage::Fragment, + entrypoint: fs_entry_point_reflection + .name() + .map_or("(none)".into(), |name| name.to_string().into()), + })?; + + let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex); + + vertex_shader.set_source( + current_context, + vertex_shader_entry_point_code.as_str().unwrap(), + )?; + + vertex_shader.compile(current_context)?; + + let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment); + + fragment_shader.set_source( + current_context, + fragment_shader_entry_point_code.as_str().unwrap(), + )?; + + fragment_shader.compile(current_context)?; + + let gl_shader_program = GlShaderProgram::new(current_context); + + gl_shader_program.attach(current_context, &vertex_shader); + gl_shader_program.attach(current_context, &fragment_shader); + + gl_shader_program.link(current_context)?; + + Ok(gl_shader_program) +} + +#[derive(Debug, thiserror::Error)] +enum CreateShaderError +{ + #[error( + "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}" + )] + GetShaderEntryPointCodeFailed + { + #[source] + err: ShaderError, + stage: ShaderStage, + entrypoint: Cow<'static, str>, + }, + + #[error("No entrypoint was found for shader stage {0:?}")] + NoShaderStageEntrypointFound(ShaderStage), + + #[error(transparent)] + ShaderError(#[from] GlShaderError), +} + +// This function not having tracing instrumentation is intentional. It would not provide +// any value +fn opengl_debug_message_cb( + source: MessageSource, + ty: MessageType, + id: u32, + severity: MessageSeverity, + message: &str, +) +{ + macro_rules! emit_log_with_message { + ($level: expr) => { + tracing::event!($level, ?source, ?ty, id, ?severity, "{message}"); + }; + } + + if matches!(severity, MessageSeverity::Notification) { + return; + } + + match ty { + MessageType::Error => { + emit_log_with_message!(tracing::Level::ERROR); + + let backtrace = std::backtrace::Backtrace::capture(); + + if backtrace.status() == std::backtrace::BacktraceStatus::Captured { + tracing::error!("{backtrace}"); + } + } + MessageType::Other => { + emit_log_with_message!(tracing::Level::INFO); + } + _ => { + emit_log_with_message!(tracing::Level::WARN); + } + } +} + +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping +{ + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } +} + +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering +{ + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} + +impl<Value: ReprC + Copy> From<Vec2<Value>> for opengl_bindings::data_types::Vec2<Value> +{ + fn from(vec2: Vec2<Value>) -> Self + { + Self { x: vec2.x, y: vec2.y } + } +} + +impl<Value: ReprC + 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: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value> +{ + fn from(dimens: Dimens<Value>) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} + +fn polygon_mode_to_gl(mode: PolygonMode) -> GlPolygonMode +{ + match mode { + PolygonMode::Point => GlPolygonMode::Point, + PolygonMode::Fill => GlPolygonMode::Fill, + PolygonMode::Line => GlPolygonMode::Line, + } +} + +fn polygon_mode_face_to_gl(face: PolygonModeFace) -> GlPolygonModeFace +{ + match face { + PolygonModeFace::Front => GlPolygonModeFace::Front, + PolygonModeFace::Back => GlPolygonModeFace::Back, + PolygonModeFace::FrontAndBack => GlPolygonModeFace::FrontAndBack, + } +} + +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, + } +} + +trait IntMapEntryExt<'map, Value> +{ + fn set_or_insert_with(self, func: impl FnOnce() -> Value) -> &'map mut Value; +} + +impl<'map, Key, Value> IntMapEntryExt<'map, Value> for intmap::Entry<'map, Key, Value> +where + Key: intmap::IntKey, + Value: 'map, +{ + fn set_or_insert_with(self, func: impl FnOnce() -> Value) -> &'map mut Value + { + match self { + Self::Occupied(occupied) => { + let value = occupied.into_mut(); + + *value = func(); + + value + } + Self::Vacant(vacant) => vacant.insert(func()), + } + } +} |
