diff options
Diffstat (limited to 'engine/src/rendering/backend/opengl.rs')
| -rw-r--r-- | engine/src/rendering/backend/opengl.rs | 1438 |
1 files changed, 1438 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..5ef6b38 --- /dev/null +++ b/engine/src/rendering/backend/opengl.rs @@ -0,0 +1,1438 @@ +//! OpenGL rendering backend. + +use std::borrow::Cow; +use std::collections::HashMap; + +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::{ + configure as gl_blending_configure, + Configuration as GlBlendingConfig, + Equation as GlBlendingEquation, + Factor as GlBlendingFactor, +}; +use opengl_bindings::debug::{ + set_debug_message_callback, + set_debug_message_control, + MessageIdsAction, + MessageSeverity, + MessageSource, + MessageType, + SetDebugMessageControlError as GlSetDebugMessageControlError, +}; +use opengl_bindings::misc::{ + clear_buffers, + define_scissor_box as gl_define_scissor_box, + enable, + get_viewport as gl_get_viewport, + set_enabled, + set_viewport as gl_set_viewport, + BufferClearMask as GlBufferClearMask, + Capability, +}; +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::{ + MakeContextCurrentError as GlMakeContextCurrentError, + MaybeCurrentContextWithFns, +}; +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::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Query, Sole}; +use crate::image::{ColorType as ImageColorType, Image}; +use crate::matrix::Matrix; +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::{ + BufferClearMask, + Command, + CommandQueue, + DrawMeshOptions, + DrawPropertiesUpdateFlags, + GraphicsProperties, + SurfaceId, + SurfaceSpec, + POST_RENDER_PHASE, + RENDER_PHASE, +}; +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::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>, + shader_uniform_buffer_objs: + HashMap<ObjectId, HashMap<u32, opengl_bindings::buffer::Buffer<u8>>>, + objects: HashMap<ObjectRawValue, GraphicsContextObject>, + next_object_key: ObjectRawValue, +} + +#[derive(Debug)] +struct GraphicsContextSurface +{ + window_surface: GlutinSurface<GlutinWindowSurface>, + size: Dimens<u32>, +} + +#[derive(Debug)] +enum GraphicsContextObject +{ + Mesh + { + mesh: GraphicsMesh, + compatible_shader_program_obj_id: ObjectId, + }, +} + +#[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), + ( + Without<CreationReady>, + Without<WindowGlConfig>, + Without<WindowClosed>, + ), + >, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, + 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<SurfaceSpec>,)>, + windowing_context: Single<WindowingContext>, + graphics_props: Single<GraphicsProperties>, + mut graphics_ctx: Single<GraphicsContext>, + 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 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(), + &window.inner_size().clone().into(), + ) { + 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, (SurfaceSpec { id: surface_id },)); + + graphics_ctx.surfaces.insert( + surface_id, + GraphicsContextSurface { + window_surface, + size: window.inner_size().clone(), + }, + ); + } +} + +#[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>, + shader_context: Single<ShaderContext>, +) +{ + let GraphicsContext { + ref gl_context, + ref mut surfaces, + ref mut shader_uniform_buffer_objs, + objects: ref mut graphics_ctx_objects, + next_object_key: ref mut next_graphics_ctx_object_key, + } = *graphics_ctx; + + let Some(gl_context) = gl_context else { + return; + }; + + let mut activated_gl_shader_program: Option<(ObjectId, GlShaderProgram)> = 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(), + &surface.size.into(), + ) { + 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(), + &surface.size.into(), + ) { + 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(shader_program_obj_id, shader_program) => { + 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(gl_context, &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, + Object::from_raw( + gl_shader_program.into_raw(), + ObjectKind::ShaderProgram, + ), + ); + } + Command::ActivateShader(shader_program_obj_id) => { + 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(gl_context); + + activated_gl_shader_program = + Some((shader_program_obj_id, gl_shader_program)); + } + Command::SetShaderBinding(binding_location, binding_value) => { + 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(&ObjectId::Asset(texture_asset.id())) + else { + tracing::error!( + "Texture {:?} does not exist in rendering object store", + assets.get_label(texture_asset) + ); + continue; + }; + + let gl_texture = GlTexture::from_raw(texture_obj.as_raw()); + + gl_texture + .bind_to_texture_unit(gl_context, binding_location.binding_index); + + // gl_shader_program.set_uniform_at_location( + // curr_gl_ctx, + // GlUniformLocation::from_number( + // binding_location.binding_index as i32, + // ), + // &binding_location.binding_index, + // ); + + continue; + } + + let binding_index = binding_location.binding_index; + + let uniform_buffer_objs = shader_uniform_buffer_objs + .entry(*activated_gl_shader_program_obj_id) + .or_default(); + + let uniform_buffer = + uniform_buffer_objs.entry(binding_index).or_insert_with(|| { + let uniform_buf = + opengl_bindings::buffer::Buffer::<u8>::new(gl_context); + + uniform_buf + .init( + gl_context, + 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( + gl_context, + opengl_bindings::buffer::BindingTarget::UniformBuffer, + binding_index as u32, + ); + + let fvec3_value; + + uniform_buffer + .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 = CF32Vec3::from(value); + + fvec3_value.as_bytes() + } + ShaderBindingValue::FMat4x4(ref value) => { + value.items().as_bytes() + } + ShaderBindingValue::Texture(_) => unreachable!(), + }, + ) + .unwrap(); + } + Command::CreateTexture(texture_asset) => { + if let Err(err) = create_texture_object( + gl_context, + &mut object_store, + &assets, + &texture_asset, + ) { + tracing::error!("Failed to create texture object: {err}"); + } + } + Command::CreateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + 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((ObjectId::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 { + ObjectId::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 + } + }, + ObjectId::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( + gl_context, + &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: ObjectId::Asset( + *curr_shader_program_asset_id, + ), + }, + ); + + object_store.insert( + mesh_object_id, + Object::from_raw(key, ObjectKind::ImplementationSpecific), + ); + + *next_graphics_ctx_object_key += 1; + } + Command::UpdateMesh { + obj_id: mesh_object_id, + mesh, + usage: mesh_usage, + } => { + 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(gl_context, &mesh, mesh_usage) { + tracing::error!("Failed to update mesh: {err}"); + } + } + Command::RemoveMesh(mesh_object_id) => { + 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(gl_context); + } + Command::DrawMesh(mesh_object_id, draw_mesh_opts) => { + 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(gl_context, graphics_mesh, &draw_mesh_opts) { + tracing::error!("Failed to draw mesh: {err}"); + }; + } + Command::UpdateDrawProperties(draw_props, draw_props_update_flags) => { + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) + { + opengl_bindings::misc::set_polygon_mode( + gl_context, + draw_props.polygon_mode_config.face, + draw_props.polygon_mode_config.mode, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) + { + set_enabled( + gl_context, + Capability::Blend, + draw_props.blending_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) + { + gl_blending_configure( + gl_context, + 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( + gl_context, + Capability::DepthTest, + draw_props.depth_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) + { + set_enabled( + gl_context, + Capability::ScissorTest, + draw_props.scissor_test_enabled, + ); + } + + if draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) + { + gl_define_scissor_box( + gl_context, + draw_props.scissor_box.lower_left_corner_pos.into(), + draw_props + .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 draw_props_update_flags + .contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) + { + set_enabled( + gl_context, + Capability::CullFace, + draw_props.face_culling_enabled, + ); + } + } + } + } +} + +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 create_texture_object( + curr_gl_ctx: &MaybeCurrentContextWithFns, + object_store: &mut ObjectStore, + assets: &Assets, + texture_asset: &AssetHandle<Texture>, +) -> Result<(), GlTextureGenerateError> +{ + let object_id = ObjectId::Asset(texture_asset.id()); + + if object_store.contains_non_pending_with_id(&object_id) { + tracing::error!( + texture_object_id=?object_id, + texture_asset_label=?assets.get_label(texture_asset), + " 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, + }; + + object_store.insert( + object_id, + Object::from_raw( + create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)? + .into_raw(), + ObjectKind::Texture, + ), + ); + + Ok(()) +} + +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 create_gl_texture( + current_context: &MaybeCurrentContextWithFns, + image: &Image, + texture_properties: &TextureProperties, +) -> Result<GlTexture, GlTextureGenerateError> +{ + 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: &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(|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::{event, Level}; + + macro_rules! create_event { + ($level: expr) => { + event!($level, ?source, ?ty, id, ?severity, message); + }; + } + + if matches!(severity, MessageSeverity::Notification) { + return; + } + + match ty { + MessageType::Error => { + create_event!(Level::ERROR); + + let backtrace = Backtrace::capture(); + + if matches!(backtrace.status(), BacktraceStatus::Captured) { + tracing::error!("{backtrace}"); + // event!(Level::TRACE, "{backtrace}"); + } + } + MessageType::Other => { + create_event!(Level::INFO); + } + _ => { + create_event!(Level::WARN); + } + }; +} + +#[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: ReprC + Copy> From<Matrix<Value, 4, 4>> + for opengl_bindings::data_types::Matrix<Value, 4, 4> +{ + fn from(matrix: Matrix<Value, 4, 4>) -> Self + { + Self { items: matrix.items } + } +} + +impl<Value: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value> +{ + fn from(dimens: Dimens<Value>) -> Self + { + Self { + width: dimens.width, + height: dimens.height, + } + } +} + +impl From<crate::draw_flags::PolygonMode> for opengl_bindings::misc::PolygonMode +{ + fn from(mode: crate::draw_flags::PolygonMode) -> Self + { + match mode { + crate::draw_flags::PolygonMode::Point => Self::Point, + crate::draw_flags::PolygonMode::Fill => Self::Fill, + crate::draw_flags::PolygonMode::Line => Self::Line, + } + } +} + +impl From<crate::draw_flags::PolygonModeFace> for opengl_bindings::misc::PolygonModeFace +{ + fn from(face: crate::draw_flags::PolygonModeFace) -> Self + { + match face { + crate::draw_flags::PolygonModeFace::Front => Self::Front, + crate::draw_flags::PolygonModeFace::Back => Self::Back, + crate::draw_flags::PolygonModeFace::FrontAndBack => Self::FrontAndBack, + } + } +} + +#[derive(Debug, IntoBytes, Immutable)] +#[repr(C)] +pub struct CF32Vec3 +{ + x: f32, + y: f32, + z: f32, +} + +impl From<Vec3<f32>> for CF32Vec3 +{ + fn from(src: Vec3<f32>) -> Self + { + Self { x: src.x, y: src.y, z: src.z } + } +} + +fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor +{ + match blending_factor { + BlendingFactor::Zero => GlBlendingFactor::Zero, + BlendingFactor::One => GlBlendingFactor::One, + BlendingFactor::SrcColor => GlBlendingFactor::SrcColor, + BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor, + BlendingFactor::DstColor => GlBlendingFactor::DstColor, + BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor, + BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha, + BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha, + BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha, + BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha, + BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor, + BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor, + BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha, + BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha, + } +} + +fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation +{ + match blending_equation { + BlendingEquation::Add => GlBlendingEquation::Add, + BlendingEquation::Subtract => GlBlendingEquation::Subtract, + BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract, + BlendingEquation::Min => GlBlendingEquation::Min, + BlendingEquation::Max => GlBlendingEquation::Max, + } +} |
