//! 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::phase::INIT as INIT_PHASE; 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::reflection::Stage as ShaderStage; use crate::rendering::shader::{ CompileTarget as ShaderCompileTarget, CompileTargetFormat as ShaderCompileTargetFormat, Context as ShaderContext, ContextSessionInitOpts as ShaderContextSessionInitOpts, DebugInfoLevel as ShaderDebugInfoLevel, EntrypointFlags as ShaderEntrypointFlags, Error as ShaderError, LineDirectiveMode as ShaderLineDirectiveMode, OptimizationLevel as ShaderOptimizationLevel, Program as ShaderProgram, VertexDescCreationError as ShaderVertexDescCreationError, VertexDescription as ShaderVertexDescription, }; use crate::rendering::{ AssetOrValue, BackendProperties, 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, surfaces: HashMap, backend_resources: BackendResourceStore, } #[derive(Debug, Default)] struct BackendResourceStore { inner: IntMap, next_id: BackendResourceId, } impl BackendResourceStore { fn try_create_resource( &mut self, object_store: &mut ObjectStore, object_id: ObjectId, resource_fn: impl FnOnce() -> Result, ) -> 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 = ::PRIME; fn into_int(self) -> Self::Int { self.0.into_int() } } #[derive(Debug)] struct GraphicsContextSurface { window_surface: GlutinSurface, size: PhysicalSize, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct ShaderBindingIndex(u32); impl intmap::IntKey for ShaderBindingIndex { type Int = u32; const PRIME: Self::Int = ::PRIME; fn into_int(self) -> Self::Int { self.0 } } #[derive(Debug, Clone)] enum BackendShaderBinding { Uniform(GlBuffer), 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, }, Shader { program: GlShaderProgram, info: ShaderInfo, bindings: IntMap, }, 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, Clone)] struct ShaderInfo { /// If the shader program has a entry point in the vertex stage, this field will /// contain a description of the vertex type passed to the entry point. vertex_desc: Option, } #[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(*INIT_PHASE, init); 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 init( mut shader_context: Single, mut actions: Actions, ) -> Result<(), crate::Error> { let shader_context = shader_context.get_mut()?; let shader_target_ids = shader_context.init_session( &if cfg!(debug_assertions) { ShaderContextSessionInitOpts::default() .optimization_level(ShaderOptimizationLevel::Default) .debug_info_level(ShaderDebugInfoLevel::Maximal) .line_directive_mode(ShaderLineDirectiveMode::Standard) .no_mangle(true) } else { ShaderContextSessionInitOpts::default() .optimization_level(ShaderOptimizationLevel::High) .debug_info_level(ShaderDebugInfoLevel::None) .line_directive_mode(ShaderLineDirectiveMode::None) }, &[&ShaderCompileTarget::default().format(ShaderCompileTargetFormat::Glsl)], )?; actions.create_sole(BackendProperties { shader_target_id: shader_target_ids[0], }); Ok(()) } fn prepare_windows( window_query: Query<( Option<&Window>, &mut WindowCreationAttributes, With, Without, Without, Without, )>, windowing_context: Single, graphics_props: Single, 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, Without, )>, windowing_context: Single, graphics_props: Single, mut graphics_ctx: Single, 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::>::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, mut object_store: Single, mut command_queue: Single, assets: Single, backend_properties: Single, ) -> 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 Ok(backend_properties) = backend_properties.get() else { return Ok(()); }; 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, ShaderInfo)> = 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::( object_store, object_id, || { Ok(BackendResource::Shader { program: create_shader_program( gl_context, &shader_program, backend_properties, )?, info: ShaderInfo { vertex_desc: if shader_program .metadata() .link_entrypoints .contains(ShaderEntrypointFlags::VERTEX) { Some(shader_program.create_vertex_desc( backend_properties.shader_target_id, )?) } else { None }, }, 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, info: shader_info, 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(), shader_info.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::( 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::( 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::( 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_info)) = &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_info.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, ) -> Result { let display = gl_config.display(); let glutin_context = unsafe { display.create_context( gl_config, &glutin::context::ContextAttributesBuilder::new() .with_debug(graphics_props.debug) .build(Some(window_handle.as_raw())), ) } .map_err(CreateGlContextError::CreateGlutinContext)?; MaybeCurrentContextWithFns::new(glutin_context, surface) .map_err(CreateGlContextError::MakeContextCurrent) } #[derive(Debug, thiserror::Error)] enum CreateGlContextError { #[error("Glutin context creation failed")] CreateGlutinContext(#[source] GlutinError), #[error("Making GL context current failed")] MakeContextCurrent(#[source] GlMakeContextCurrentError), } #[tracing::instrument(skip_all)] fn 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:: { 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 { 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::::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 { 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, backend_properties: &BackendProperties, ) -> Result { let shader_program_reflection = shader_program .reflection(backend_properties.shader_target_id) .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", ), backend_properties.shader_target_id, ) .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", ), backend_properties.shader_target_id, ) .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("Failed to create shader vertex description")] VertexDescCreationFailed(#[from] ShaderVertexDescCreationError), #[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 From> for opengl_bindings::data_types::Vec2 { fn from(vec2: Vec2) -> Self { Self { x: vec2.x, y: vec2.y } } } impl From> for opengl_bindings::data_types::Vec3 { fn from(vec3: Vec3) -> Self { Self { x: vec3.x, y: vec3.y, z: vec3.z } } } impl From> for opengl_bindings::data_types::Dimens { fn from(dimens: Dimens) -> Self { Self { width: dimens.width, height: dimens.height, } } } 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()), } } }