//! OpenGL renderer. use std::borrow::Cow; use std::collections::HashMap; use ecs::actions::Actions; use ecs::query::term::Without; use ecs::sole::Single; use ecs::{Component, Query, Sole}; use glutin::config::Config as GlutinConfig; use glutin::display::GetGlDisplay; use glutin::error::Error as GlutinError; use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext}; use glutin::surface::{ GlSurface as _, Surface as GlutinSurface, WindowSurface as GlutinWindowSurface, }; use opengl_bindings::blending::{ Configuration as GlBlendingConfig, Equation as GlBlendingEquation, Factor as GlBlendingFactor, configure as gl_blending_configure, }; use opengl_bindings::debug::{ MessageIdsAction, MessageSeverity, MessageSource, MessageType, SetDebugMessageControlError as GlSetDebugMessageControlError, set_debug_message_callback, set_debug_message_control, }; use opengl_bindings::misc::{ BufferClearMask as GlBufferClearMask, Capability, clear_buffers, define_scissor_box as gl_define_scissor_box, enable, get_viewport as gl_get_viewport, set_enabled, set_viewport as gl_set_viewport, }; use opengl_bindings::shader::{ Error as GlShaderError, Kind as ShaderKind, Program as GlShaderProgram, Shader as GlShader, // UniformLocation as GlUniformLocation, }; use opengl_bindings::texture::{ ColorSpace as GlTextureColorSpace, Filtering as GlTextureFiltering, GenerateError as GlTextureGenerateError, PixelDataFormat as GlTexturePixelDataFormat, Texture as GlTexture, Wrapping as GlTextureWrapping, }; use opengl_bindings::vertex_array::{ DrawError as GlDrawError, PrimitiveKind, VertexArray, }; use opengl_bindings::{ ContextWithFns, CurrentContextWithFns, MakeContextCurrentError as GlMakeContextCurrentError, }; use raw_window_handle::WindowHandle; use safer_ffi::layout::ReprC; use zerocopy::{Immutable, IntoBytes}; use crate::asset::{Assets, Handle as AssetHandle}; use crate::data_types::dimens::Dimens; use crate::image::{ColorType as ImageColorType, Image}; use crate::matrix::Matrix; use crate::renderer::blending::{Equation as BlendingEquation, Factor as BlendingFactor}; use crate::renderer::object::{ Id as RendererObjectId, Kind as RendererObjectKind, Object as RendererObject, RawValue as RendererObjectRawValue, Store as RendererObjectStore, }; use crate::renderer::opengl::glutin_compat::{ DisplayBuilder, Error as GlutinCompatError, }; use crate::renderer::opengl::graphics_mesh::GraphicsMesh; use crate::renderer::{ BufferClearMask, Command as RendererCommand, CommandQueue as RendererCommandQueue, DrawMeshOptions, DrawPropertiesUpdateFlags, GraphicsProperties, POST_RENDER_PHASE, RENDER_PHASE, SurfaceId, SurfaceSpec, }; use crate::shader::cursor::BindingValue as ShaderBindingValue; use crate::shader::{ Context as ShaderContext, Error as ShaderError, Program as ShaderProgram, Stage as ShaderStage, }; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, Texture, Wrapping as TextureWrapping, }; use crate::util::OptionExt; use crate::vector::{Vec2, Vec3}; use crate::windowing::Context as WindowingContext; use crate::windowing::window::{ Closed as WindowClosed, CreationAttributes as WindowCreationAttributes, CreationReady, Window, }; mod glutin_compat; mod graphics_mesh; #[derive(Debug, Component)] struct WindowGlConfig { gl_config: GlutinConfig, } #[derive(Sole, Default)] struct GraphicsContext { gl_context: Option, surfaces: HashMap>, shader_uniform_buffer_objs: HashMap>>, objects: HashMap, next_object_key: RendererObjectRawValue, } #[derive(Debug)] enum GraphicsContextObject { Mesh { mesh: GraphicsMesh, compatible_shader_program_obj_id: RendererObjectId, }, } #[derive(Debug, Default)] #[non_exhaustive] pub struct Extension {} impl ecs::extension::Extension for Extension { fn collect(self, mut collector: 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), ( Without, Without, Without, ), >, windowing_context: Single, graphics_props: Single, mut actions: Actions, ) { let Some(display_handle) = windowing_context.display_handle() else { return; }; 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() .map(|window| unsafe { windowing_context.get_window_as_handle(&window.wid()) }) .flatten() .transpose() { Ok(window_handle) => window_handle, Err(err) => { tracing::error!("Failed to get window handle: {err}"); continue; } }; let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new() .with_window_attributes(window_creation_attrs.clone()) .build( window_handle, &display_handle, glutin_config_template_builder, |mut cfgs| cfgs.next(), ) { Ok((new_window_creation_attrs, gl_config)) => { (new_window_creation_attrs, gl_config) } Err(GlutinCompatError::WindowRequired) => { actions.add_components(window_ent_id, (CreationReady,)); continue; } Err(err) => { tracing::error!("Failed to create platform graphics display: {err}"); continue; } }; *window_creation_attrs = new_window_creation_attrs; actions.add_components(window_ent_id, (WindowGlConfig { gl_config },)); if window.is_none() { actions.add_components(window_ent_id, (CreationReady,)); } } } #[tracing::instrument(skip_all)] fn init_window_graphics( window_query: Query<(&Window, &WindowGlConfig), (Without,)>, windowing_context: Single, graphics_props: Single, mut graphics_ctx: Single, mut actions: Actions, ) { 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 Some(window_inner_size) = window.inner_size().try_into_nonzero() else { tracing::error!( "Cannot create a surface for a window with a width/height of 0", ); continue; }; let surface = match unsafe { display.create_window_surface( &window_gl_config.gl_config, &glutin::surface::SurfaceAttributesBuilder::< glutin::surface::WindowSurface, >::new() .build( window_handle.as_raw(), window_inner_size.width, window_inner_size.height, ), ) } { Ok(surface) => surface, Err(err) => { tracing::error!("Failed to create window surface: {err}"); continue; } }; let 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, &surface, ) }) { Ok(gl_context) => gl_context, Err(err) => { tracing::error!("Failed to create GL context: {err:#?}"); continue; } }; let Ok(curr_gl_context) = gl_context.make_current(&surface) else { tracing::error!("Failed to make GL context current"); continue; }; if let Err(err) = gl_set_viewport( &curr_gl_context, &Vec2 { x: 0, y: 0 }.into(), &window.inner_size().clone().into(), ) { tracing::error!("Failed to set viewport: {err}"); } set_enabled( &curr_gl_context, Capability::DepthTest, graphics_props.depth_test, ); set_enabled( &curr_gl_context, Capability::MultiSample, graphics_props.multisampling_sample_cnt.is_some(), ); if graphics_props.debug { enable(&curr_gl_context, Capability::DebugOutput); enable(&curr_gl_context, Capability::DebugOutputSynchronous); set_debug_message_callback(&curr_gl_context, opengl_debug_message_cb); match set_debug_message_control( &curr_gl_context, None, None, None, &[], MessageIdsAction::Disable, ) { Ok(()) => {} Err(GlSetDebugMessageControlError::TooManyIds { id_cnt: _, max_id_cnt: _, }) => { unreachable!() // No ids are given } } } let surface_id = SurfaceId::new_unique(); actions.add_components(window_ent_id, (SurfaceSpec { id: surface_id },)); graphics_ctx.surfaces.insert(surface_id, surface); } } #[tracing::instrument(skip_all)] fn handle_commands( mut graphics_ctx: Single, mut object_store: Single, mut command_queue: Single, assets: Single, shader_context: Single, ) { let GraphicsContext { ref gl_context, ref mut surfaces, ref mut shader_uniform_buffer_objs, objects: ref mut graphics_ctx_objects, next_object_key: ref mut next_graphics_ctx_object_key, } = *graphics_ctx; let Some(gl_context) = gl_context else { return; }; let mut opt_curr_gl_ctx: Option = None; let mut activated_gl_shader_program: Option<(RendererObjectId, GlShaderProgram)> = None; for command in command_queue.drain() { let tracing_span = tracing::info_span!("handle_cmd"); let _tracing_span_enter = tracing_span.enter(); match command { RendererCommand::RemoveSurface(surface_id) => { let Some(surface) = surfaces.remove(&surface_id) else { tracing::error!(surface_id=?surface_id, "Surface does not exist"); continue; }; if surface.is_current(gl_context.context()) { opt_curr_gl_ctx = None; if let Err(err) = gl_context.context().make_not_current_in_place() { tracing::error!("Failed to make GL context not current: {err}"); } } drop(surface); } RendererCommand::MakeCurrent(surface_id) => { let Some(surface) = surfaces.get(&surface_id) else { tracing::error!(surface_id=?surface_id, "Surface does not exist"); continue; }; let curr_gl_ctx = match gl_context.make_current(surface) { Ok(current_graphics_context) => current_graphics_context, Err(err) => { tracing::error!("Failed to make graphics context current: {err}"); continue; } }; opt_curr_gl_ctx = Some(curr_gl_ctx); } RendererCommand::ClearBuffers(buffer_clear_mask) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let mut clear_mask = GlBufferClearMask::empty(); clear_mask.set( GlBufferClearMask::COLOR, buffer_clear_mask.contains(BufferClearMask::COLOR), ); clear_mask.set( GlBufferClearMask::DEPTH, buffer_clear_mask.contains(BufferClearMask::DEPTH), ); clear_mask.set( GlBufferClearMask::STENCIL, buffer_clear_mask.contains(BufferClearMask::STENCIL), ); clear_buffers(&curr_gl_ctx, clear_mask); } RendererCommand::SwapBuffers(surface_id) => { let Some(surface) = surfaces.get(&surface_id) else { tracing::error!(surface_id=?surface_id, "Surface does not exist"); continue; }; if let Err(err) = surface.swap_buffers(gl_context.context()) { tracing::error!("Failed to swap buffers: {err}"); } } RendererCommand::CreateShaderProgram( shader_program_obj_id, shader_program, ) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; if object_store.contains_non_pending_with_id(&shader_program_obj_id) { tracing::error!( shader_program_object_id=?shader_program_obj_id, shader_asset_label=?shader_program_obj_id .into_asset_id() .and_then(|asset_id| assets.get_label_by_id(asset_id)), "Object store already contains a object with this ID" ); continue; } let gl_shader_program = match create_shader_program(&curr_gl_ctx, &shader_program) { Ok(gl_shader_program) => gl_shader_program, Err(err) => { tracing::error!("Failed to create shader program: {err}"); continue; } }; object_store.insert( shader_program_obj_id, RendererObject::from_raw( gl_shader_program.into_raw(), RendererObjectKind::ShaderProgram, ), ); } RendererCommand::ActivateShader(shader_program_obj_id) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let Some(shader_program_obj) = object_store.get_shader_program_obj(&shader_program_obj_id) else { tracing::error!("Shader object does not exist or has a wrong kind"); continue; }; let gl_shader_program = GlShaderProgram::from_raw(shader_program_obj.as_raw()); gl_shader_program.activate(&curr_gl_ctx); activated_gl_shader_program = Some((shader_program_obj_id, gl_shader_program)); } RendererCommand::SetShaderBinding(binding_location, binding_value) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let Some((activated_gl_shader_program_obj_id, _)) = &activated_gl_shader_program else { tracing::error!("No shader program is activated"); continue; }; if let ShaderBindingValue::Texture(texture_asset) = &binding_value { let Some(texture_obj) = object_store .get_texture_obj(&RendererObjectId::Asset(texture_asset.id())) else { tracing::error!( "Texture {:?} does not exist in renderer object store", assets.get_label(texture_asset) ); continue; }; let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); gl_texture.bind_to_texture_unit( curr_gl_ctx, binding_location.binding_index, ); // gl_shader_program.set_uniform_at_location( // curr_gl_ctx, // GlUniformLocation::from_number( // binding_location.binding_index as i32, // ), // &binding_location.binding_index, // ); continue; } let binding_index = binding_location.binding_index; let uniform_buffer_objs = shader_uniform_buffer_objs .entry(*activated_gl_shader_program_obj_id) .or_default(); let uniform_buffer = uniform_buffer_objs.entry(binding_index).or_insert_with(|| { let uniform_buf = opengl_bindings::buffer::Buffer::::new(curr_gl_ctx); uniform_buf .init( curr_gl_ctx, binding_location.binding_size, opengl_bindings::buffer::Usage::Dynamic, ) .unwrap(); uniform_buf }); // The index into the uniform buffer binding target is for whatever // shader program is currently bound so the uniform buffer object has // to be re-bound so that a uniform buffer from another shader isn't // used uniform_buffer.bind_to_indexed_target( curr_gl_ctx, opengl_bindings::buffer::BindingTarget::UniformBuffer, binding_index as u32, ); let fvec3_value; uniform_buffer .store_at_byte_offset( curr_gl_ctx, binding_location.byte_offset, match binding_value { ShaderBindingValue::Uint(ref value) => value.as_bytes(), ShaderBindingValue::Int(ref value) => value.as_bytes(), ShaderBindingValue::Float(ref value) => value.as_bytes(), ShaderBindingValue::FVec3(value) => { fvec3_value = CF32Vec3::from(value); fvec3_value.as_bytes() } ShaderBindingValue::FMat4x4(ref value) => { value.items().as_bytes() } ShaderBindingValue::Texture(_) => unreachable!(), }, ) .unwrap(); } RendererCommand::CreateTexture(texture_asset) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; if let Err(err) = create_texture_object( curr_gl_ctx, &mut object_store, &assets, &texture_asset, ) { tracing::error!("Failed to create texture object: {err}"); } } RendererCommand::CreateMesh { obj_id: mesh_object_id, mesh, usage: mesh_usage, } => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; if object_store.contains_non_pending_with_id(&mesh_object_id) { tracing::error!( mesh_object_id=?mesh_object_id, mesh_asset_label=?mesh_object_id .into_asset_id() .and_then(|asset_id| assets.get_label_by_id(asset_id)), "Object store already contains a object with this ID" ); continue; } let Some((RendererObjectId::Asset(curr_shader_program_asset_id), _)) = &activated_gl_shader_program else { tracing::error!("No shader program is activated"); continue; }; let curr_shader_program_metadata = shader_context .get_program_metadata(curr_shader_program_asset_id) .expect("Not possible"); let Some(vertex_desc) = &curr_shader_program_metadata.vertex_desc else { tracing::error!( "Current shader program does not have a vertex description" ); continue; }; let key = *next_graphics_ctx_object_key; let mesh = match mesh_object_id { RendererObjectId::Asset(mesh_asset_id) => match mesh.as_ref() { Some(mesh) => mesh, None => { let Some(mesh) = assets.get(&AssetHandle::from_id(mesh_asset_id)) else { tracing::error!( asset_id=?mesh_asset_id, "Mesh asset does not exist" ); continue; }; mesh } }, RendererObjectId::Sequential(_) => { let Some(mesh) = mesh.as_ref() else { tracing::error!( object_id=?mesh_object_id, "Object ID is sequential but no mesh data is given" ); continue; }; mesh } }; let graphics_mesh = match GraphicsMesh::new( &curr_gl_ctx, &mesh, mesh_usage, &vertex_desc, ) { Ok(graphics_mesh) => graphics_mesh, Err(err) => { tracing::error!("Failed to create mesh: {err}"); continue; } }; graphics_ctx_objects.insert( key, GraphicsContextObject::Mesh { mesh: graphics_mesh, compatible_shader_program_obj_id: RendererObjectId::Asset( *curr_shader_program_asset_id, ), }, ); object_store.insert( mesh_object_id, RendererObject::from_raw( key, RendererObjectKind::ImplementationSpecific, ), ); *next_graphics_ctx_object_key += 1; } RendererCommand::UpdateMesh { obj_id: mesh_object_id, mesh, usage: mesh_usage, } => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let Some(mesh_graphics_ctx_obj_key) = object_store .get_obj(&mesh_object_id) .map(|obj| obj.as_raw()) else { tracing::error!( object_id=?mesh_object_id, "Object store does not contain a mesh object with this ID" ); continue; }; let Some(mesh_graphics_ctx_obj) = graphics_ctx_objects.get_mut(&mesh_graphics_ctx_obj_key) else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context does not contain a mesh object with this key" ); continue; }; #[allow(irrefutable_let_patterns)] let GraphicsContextObject::Mesh { mesh: graphics_mesh, compatible_shader_program_obj_id: _, } = mesh_graphics_ctx_obj else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context object with this key is not a mesh" ); continue; }; if let Err(err) = graphics_mesh.update(curr_gl_ctx, &mesh, mesh_usage) { tracing::error!("Failed to update mesh: {err}"); } } RendererCommand::RemoveMesh(mesh_object_id) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let Some(mesh_graphics_ctx_obj_key) = object_store .remove(&mesh_object_id) .flatten() .map(|obj| obj.as_raw()) else { tracing::error!( object_id=?mesh_object_id, "Object store does not contain a mesh object with this ID" ); continue; }; let Some(mesh_graphics_ctx_obj) = graphics_ctx_objects.remove(&mesh_graphics_ctx_obj_key) else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context does not contain a mesh object with this key" ); continue; }; #[allow(irrefutable_let_patterns)] let GraphicsContextObject::Mesh { mesh: mut graphics_mesh, .. } = mesh_graphics_ctx_obj else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context object with this key is not a mesh" ); continue; }; graphics_mesh.destroy(curr_gl_ctx); } RendererCommand::DrawMesh(mesh_object_id, draw_mesh_opts) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; let Some(mesh_graphics_ctx_obj_key) = object_store .get_obj(&mesh_object_id) .map(|obj| obj.as_raw()) else { tracing::error!( object_id=?mesh_object_id, "Object store does not contain a mesh object with this ID" ); continue; }; let Some(mesh_graphics_ctx_obj) = graphics_ctx_objects.get(&mesh_graphics_ctx_obj_key) else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context does not contain a mesh object with this key" ); continue; }; #[allow(irrefutable_let_patterns)] let GraphicsContextObject::Mesh { mesh: graphics_mesh, compatible_shader_program_obj_id, } = mesh_graphics_ctx_obj else { tracing::error!( object_id=?mesh_object_id, key=mesh_graphics_ctx_obj_key, "Graphics context object with this key is not a mesh" ); continue; }; if Some(compatible_shader_program_obj_id) != activated_gl_shader_program.as_ref().map( |(activated_gl_shader_program_obj_id, _)| { activated_gl_shader_program_obj_id }, ) { tracing::error!(concat!( "Activated shader program is not the ", "compatible shader program of the mesh" )); continue; } if let Err(err) = draw_mesh(&curr_gl_ctx, graphics_mesh, &draw_mesh_opts) { tracing::error!("Failed to draw mesh: {err}"); }; } RendererCommand::UpdateDrawProperties( draw_props, draw_props_update_flags, ) => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) { opengl_bindings::misc::set_polygon_mode( &curr_gl_ctx, draw_props.polygon_mode_config.face, draw_props.polygon_mode_config.mode, ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) { set_enabled( curr_gl_ctx, Capability::Blend, draw_props.blending_enabled, ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) { gl_blending_configure( curr_gl_ctx, GlBlendingConfig::default() .with_source_factor(blending_factor_to_gl( draw_props.blending_config.source_factor, )) .with_destination_factor(blending_factor_to_gl( draw_props.blending_config.destination_factor, )) .with_equation(blending_equation_to_gl( draw_props.blending_config.equation, )), ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) { set_enabled( curr_gl_ctx, Capability::DepthTest, draw_props.depth_test_enabled, ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) { set_enabled( curr_gl_ctx, Capability::ScissorTest, draw_props.scissor_test_enabled, ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) { gl_define_scissor_box( curr_gl_ctx, draw_props.scissor_box.lower_left_corner_pos.into(), draw_props .scissor_box .size .unwrap_or_else(|| { let (_, viewport_size) = gl_get_viewport(curr_gl_ctx); Dimens:: { width: viewport_size .width .try_into() .expect("Viewport width too large"), height: viewport_size .height .try_into() .expect("Viewport height too large"), } }) .into(), ); } if draw_props_update_flags .contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) { set_enabled( curr_gl_ctx, Capability::CullFace, draw_props.face_culling_enabled, ); } } RendererCommand::SetViewport { size: viewport_size, position: viewport_position, } => { let Some(curr_gl_ctx) = &opt_curr_gl_ctx else { tracing::error!("No GL context is current"); continue; }; if let Err(err) = gl_set_viewport( curr_gl_ctx, &viewport_position.into(), &viewport_size.into(), ) { tracing::error!("Failed to set viewport: {err}"); } } } } } 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)?; ContextWithFns::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 create_texture_object( curr_gl_ctx: &CurrentContextWithFns<'_>, renderer_object_store: &mut RendererObjectStore, assets: &Assets, texture_asset: &AssetHandle, ) -> Result<(), GlTextureGenerateError> { let object_id = RendererObjectId::Asset(texture_asset.id()); if renderer_object_store.contains_non_pending_with_id(&object_id) { tracing::error!( texture_object_id=?object_id, texture_asset_label=?assets.get_label(texture_asset), "Renderer object store already contains object with this ID" ); return Ok(()); } let Some(texture) = assets.get(&texture_asset) else { tracing::error!("Texture asset is not loaded",); return Ok(()); }; let texture_image = match texture.image.color_type() { ImageColorType::Rgb8 if (texture.image.dimensions().width * 3) % 4 != 0 => { // The texture will be corrupted if the alignment of each horizontal line of // the texture pixel array is not multiple of 4. // // Read more about this at // wikis.khronos.org/opengl/Common_Mistakes#Texture_upload_and_pixel_reads // // To prevent this, the image is converted to RGBA8. RGBA8 images have a pixel // size of 4 bytes so they cannot have any alignment problems tracing::warn!( texture_asset = %assets .get_label(&texture_asset) .expect("Not possible"), concat!( "Converting texture image from RGB8 to RGBA8 to prevent alignment ", "problems. This conversion may be slow. Consider changing the ", "texture image's pixel format to RGBA8" ) ); &texture.image.to_rgba8() } _ => &texture.image, }; renderer_object_store.insert( object_id, RendererObject::from_raw( create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)? .into_raw(), RendererObjectKind::Texture, ), ); Ok(()) } fn draw_mesh( current_context: &CurrentContextWithFns<'_>, 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 create_gl_texture( current_context: &CurrentContextWithFns<'_>, image: &Image, texture_properties: &TextureProperties, ) -> Result { let gl_texture = GlTexture::new(current_context); gl_texture.generate( current_context, &image.dimensions().into(), image.as_bytes(), match image.color_type() { ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, _ => { unimplemented!(); } }, if image.color_space_is_srgb() { GlTextureColorSpace::Srgb } else { GlTextureColorSpace::Linear }, )?; gl_texture.set_wrap( current_context, texture_wrapping_to_gl(texture_properties.wrap), ); gl_texture.set_magnifying_filter( current_context, texture_filtering_to_gl(texture_properties.magnifying_filter), ); gl_texture.set_minifying_filter( current_context, texture_filtering_to_gl(texture_properties.minifying_filter), ); Ok(gl_texture) } fn create_shader_program( current_context: &CurrentContextWithFns<'_>, shader_program: &ShaderProgram, ) -> Result { 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(|name| name.to_string().into()) .unwrap_or("(none)".into()), })?; let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection .entry_points() .enumerate() .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment) .ok_or_else(|| { CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment) })?; let fragment_shader_entry_point_code = shader_program .get_entry_point_code(fs_entry_point_index.try_into().expect( "Fragment shader entry point index does not fit in 32-bit unsigned int", )) .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed { err, stage: ShaderStage::Fragment, entrypoint: fs_entry_point_reflection .name() .map(|name| name.to_string().into()) .unwrap_or("(none)".into()), })?; 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), } #[tracing::instrument(skip_all)] fn opengl_debug_message_cb( source: MessageSource, ty: MessageType, id: u32, severity: MessageSeverity, message: &str, ) { use std::backtrace::{Backtrace, BacktraceStatus}; use tracing::{Level, event}; macro_rules! create_event { ($level: expr) => { event!($level, ?source, ?ty, id, ?severity, message); }; } if matches!(severity, MessageSeverity::Notification) { return; } match ty { MessageType::Error => { create_event!(Level::ERROR); let backtrace = Backtrace::capture(); if matches!(backtrace.status(), BacktraceStatus::Captured) { tracing::error!("{backtrace}"); // event!(Level::TRACE, "{backtrace}"); } } MessageType::Other => { create_event!(Level::INFO); } _ => { create_event!(Level::WARN); } }; } #[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::Matrix { fn from(matrix: Matrix) -> Self { Self { items: matrix.items } } } impl From> for opengl_bindings::data_types::Dimens { fn from(dimens: Dimens) -> Self { Self { width: dimens.width, height: dimens.height, } } } impl From for opengl_bindings::misc::PolygonMode { fn from(mode: crate::draw_flags::PolygonMode) -> Self { match mode { crate::draw_flags::PolygonMode::Point => Self::Point, crate::draw_flags::PolygonMode::Fill => Self::Fill, crate::draw_flags::PolygonMode::Line => Self::Line, } } } impl From for opengl_bindings::misc::PolygonModeFace { fn from(face: crate::draw_flags::PolygonModeFace) -> Self { match face { crate::draw_flags::PolygonModeFace::Front => Self::Front, crate::draw_flags::PolygonModeFace::Back => Self::Back, crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, } } } #[derive(Debug, IntoBytes, Immutable)] #[repr(C)] pub struct CF32Vec3 { x: f32, y: f32, z: f32, } impl From> for CF32Vec3 { fn from(src: Vec3) -> Self { Self { x: src.x, y: src.y, z: src.z } } } fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor { match blending_factor { BlendingFactor::Zero => GlBlendingFactor::Zero, BlendingFactor::One => GlBlendingFactor::One, BlendingFactor::SrcColor => GlBlendingFactor::SrcColor, BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor, BlendingFactor::DstColor => GlBlendingFactor::DstColor, BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor, BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha, BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha, BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha, BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha, BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor, BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor, BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha, BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha, } } fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation { match blending_equation { BlendingEquation::Add => GlBlendingEquation::Add, BlendingEquation::Subtract => GlBlendingEquation::Subtract, BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract, BlendingEquation::Min => GlBlendingEquation::Min, BlendingEquation::Max => GlBlendingEquation::Max, } }