summaryrefslogtreecommitdiff
path: root/engine/src/rendering/backend/opengl.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/rendering/backend/opengl.rs')
-rw-r--r--engine/src/rendering/backend/opengl.rs1438
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,
+ }
+}