summaryrefslogtreecommitdiff
path: root/engine/src/rendering/backend
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/rendering/backend')
-rw-r--r--engine/src/rendering/backend/opengl.rs2054
-rw-r--r--engine/src/rendering/backend/opengl/glutin_compat.rs256
-rw-r--r--engine/src/rendering/backend/opengl/graphics_mesh.rs263
3 files changed, 2573 insertions, 0 deletions
diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs
new file mode 100644
index 0000000..0a04d6e
--- /dev/null
+++ b/engine/src/rendering/backend/opengl.rs
@@ -0,0 +1,2054 @@
+//! OpenGL rendering backend.
+
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::convert::Infallible;
+use std::hint::cold_path;
+use std::num::NonZero;
+
+use ecs::query::term::With;
+use glutin::config::Config as GlutinConfig;
+use glutin::display::GetGlDisplay;
+use glutin::error::Error as GlutinError;
+use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext};
+use glutin::surface::{
+ GlSurface as _,
+ Surface as GlutinSurface,
+ WindowSurface as GlutinWindowSurface,
+};
+use intmap::IntMap;
+use opengl_bindings::blending::{
+ configure as gl_blending_configure,
+ Configuration as GlBlendingConfig,
+ Equation as GlBlendingEquation,
+ Factor as GlBlendingFactor,
+};
+use opengl_bindings::buffer::{
+ BindingTarget as GlBufferBindingTarget,
+ Buffer as GlBuffer,
+};
+use opengl_bindings::debug::{
+ set_debug_message_callback,
+ set_debug_message_control,
+ MessageIdsAction,
+ MessageSeverity,
+ MessageSource,
+ MessageType,
+ SetDebugMessageControlError as GlSetDebugMessageControlError,
+};
+use opengl_bindings::framebuffer::{
+ bind as gl_bind_framebuffer,
+ Attachment as GlFramebufferAttachment,
+ ColorAttachment as GlFramebufferColorAttachment,
+ Framebuffer as GlFramebuffer,
+ Target as GlFramebufferTarget,
+};
+use opengl_bindings::misc::{
+ clear_buffers,
+ define_scissor_box as gl_define_scissor_box,
+ enable,
+ get_viewport as gl_get_viewport,
+ set_depth_function as gl_set_depth_function,
+ set_enabled,
+ set_face_culling_mode as gl_set_face_culling_mode,
+ set_viewport as gl_set_viewport,
+ BufferClearMask as GlBufferClearMask,
+ Capability,
+ DepthFunction as GlDepthFunction,
+ FaceCullingMode as GlFaceCullingMode,
+ PolygonMode as GlPolygonMode,
+ PolygonModeFace as GlPolygonModeFace,
+};
+use opengl_bindings::shader::{
+ Error as GlShaderError,
+ Kind as ShaderKind,
+ Program as GlShaderProgram,
+ Shader as GlShader,
+};
+use opengl_bindings::texture::{
+ CubeMapFace as GlCubeMapTextureFace,
+ Error as GlTextureError,
+ Filtering as GlTextureFiltering,
+ PixelDataFormat as GlTexturePixelDataFormat,
+ Texture as GlTexture,
+ Wrapping as GlTextureWrapping,
+};
+use opengl_bindings::vertex_array::{
+ DrawError as GlDrawError,
+ PrimitiveKind,
+ VertexArray,
+};
+use opengl_bindings::{
+ MakeContextCurrentError as GlMakeContextCurrentError,
+ MaybeCurrentContextWithFns,
+};
+use raw_window_handle::WindowHandle;
+use safer_ffi::layout::ReprC;
+use zerocopy::IntoBytes;
+
+use crate::asset::Assets;
+use crate::data_types::color::Color;
+use crate::data_types::dimens::Dimens;
+use crate::draw_flags::{PolygonMode, PolygonModeFace};
+use crate::ecs::actions::Actions;
+use crate::ecs::query::term::Without;
+use crate::ecs::sole::Single;
+use crate::ecs::{Component, Query, Sole};
+use crate::reflection::EnumReflectionExt;
+use crate::rendering::backend::opengl::glutin_compat::{
+ DisplayBuilder,
+ Error as GlutinCompatError,
+};
+use crate::rendering::backend::opengl::graphics_mesh::GraphicsMesh;
+use crate::rendering::blending::{
+ Equation as BlendingEquation,
+ Factor as BlendingFactor,
+};
+use crate::rendering::object::{
+ Id as ObjectId,
+ Kind as ObjectKind,
+ Object,
+ RawValue as ObjectRawValue,
+ Store as ObjectStore,
+};
+use crate::rendering::shader::cursor::{
+ Binding as ShaderBinding,
+ BindingLocation as ShaderBindingLocation,
+ BindingValue as ShaderBindingValue,
+};
+use crate::rendering::shader::{
+ Error as ShaderError,
+ Program as ShaderProgram,
+ ProgramMetadata as ShaderProgramMetadata,
+ Stage as ShaderStage,
+};
+use crate::rendering::{
+ AssetOrValue,
+ BufferClearMask,
+ Command,
+ CommandQueue,
+ DepthFunction,
+ DrawMeshOptions,
+ DrawProperties,
+ DrawPropertiesUpdateFlags,
+ FaceCullingMode,
+ FramebufferProperties,
+ FramebufferPropertiesUpdateFlags,
+ GraphicsProperties,
+ Surface,
+ SurfaceId,
+ TargetWindow,
+ TextureCreation,
+ TexturePixelDataFormat,
+ TextureUpdate,
+ POST_RENDER_PHASE,
+ RENDER_PHASE,
+};
+use crate::texture::{
+ Border as TextureBorder,
+ CubeMapFace as CubeMapTextureFace,
+ Filtering as TextureFiltering,
+ Properties as TextureProperties,
+ Wrapping as TextureWrapping,
+};
+use crate::util::OptionExt;
+use crate::vector::{Vec2, Vec3};
+use crate::windowing::dpi::PhysicalSize;
+use crate::windowing::window::{
+ Closed as WindowClosed,
+ CreationAttributes as WindowCreationAttributes,
+ CreationReady,
+ Window,
+};
+use crate::windowing::Context as WindowingContext;
+
+mod glutin_compat;
+mod graphics_mesh;
+
+#[derive(Debug, Component)]
+struct WindowGlConfig
+{
+ gl_config: GlutinConfig,
+}
+
+#[derive(Sole, Default)]
+struct GraphicsContext
+{
+ gl_context: Option<MaybeCurrentContextWithFns>,
+ surfaces: HashMap<SurfaceId, GraphicsContextSurface>,
+ backend_resources: BackendResourceStore,
+}
+
+#[derive(Debug, Default)]
+struct BackendResourceStore
+{
+ inner: IntMap<BackendResourceId, BackendResource>,
+ next_id: BackendResourceId,
+}
+
+impl BackendResourceStore
+{
+ fn try_create_resource<Err>(
+ &mut self,
+ object_store: &mut ObjectStore,
+ object_id: ObjectId,
+ resource_fn: impl FnOnce() -> Result<BackendResource, Err>,
+ ) -> Result<(), Err>
+ {
+ if object_store.contains_non_pending_with_id(&object_id) {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ "Object store already contains a object with this ID"
+ );
+ return Ok(());
+ }
+
+ let resource = resource_fn()?;
+
+ let res_id = self.next_id;
+
+ self.next_id.0 += 1;
+
+ let object_kind = resource.object_kind();
+
+ self.inner.insert(res_id, resource);
+
+ object_store.insert(object_id, Object::from_raw(res_id.0, object_kind));
+
+ Ok(())
+ }
+
+ fn get_resource(
+ &self,
+ object_store: &ObjectStore,
+ object_id: ObjectId,
+ object_kind: ObjectKind,
+ ) -> Option<&BackendResource>
+ {
+ let Some(object) = object_store.get_obj(&object_id) else {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ "Object does not exist in the rendering object store"
+ );
+ return None;
+ };
+
+ if object.kind() != object_kind {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ expected_object_kind = %object_kind,
+ found_object_kind = %object.kind(),
+ "Unexpected object kind of object in object store"
+ );
+ return None;
+ }
+
+ let resource_id = BackendResourceId(object.as_raw());
+
+ let Some(resource) = self.inner.get(resource_id) else {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ ?resource_id,
+ "Backend resource does not exist"
+ );
+ return None;
+ };
+
+ debug_assert_eq!(resource.object_kind(), object_kind);
+
+ Some(resource)
+ }
+
+ fn get_resource_mut(
+ &mut self,
+ object_store: &ObjectStore,
+ object_id: ObjectId,
+ object_kind: ObjectKind,
+ ) -> Option<&mut BackendResource>
+ {
+ let Some(object) = object_store.get_obj(&object_id) else {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ "Object does not exist in the rendering object store"
+ );
+ return None;
+ };
+
+ if object.kind() != object_kind {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ expected_object_kind = %object_kind,
+ found_object_kind = %object.kind(),
+ "Unexpected object kind of object in object store"
+ );
+ return None;
+ }
+
+ let resource_id = BackendResourceId(object.as_raw());
+
+ let Some(resource) = self.inner.get_mut(resource_id) else {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ ?resource_id,
+ "Backend resource does not exist"
+ );
+ return None;
+ };
+
+ debug_assert_eq!(resource.object_kind(), object_kind);
+
+ Some(resource)
+ }
+
+ fn remove_resource(
+ &mut self,
+ object_store: &mut ObjectStore,
+ object_id: ObjectId,
+ object_kind: ObjectKind,
+ curr_gl_ctx: &MaybeCurrentContextWithFns,
+ )
+ {
+ if let Some(object) = object_store.get_obj(&object_id) {
+ if object.kind() != object_kind {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ expected_object_kind = %object_kind,
+ found_object_kind = %object.kind(),
+ "Unexpected object kind"
+ );
+ return;
+ }
+ }
+
+ let Some(object) = object_store.remove(&object_id).flatten() else {
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ "Object does not exist in the rendering object store"
+ );
+ return;
+ };
+
+ let resource_id = BackendResourceId(object.as_raw());
+
+ let Some(resource) = self.inner.remove(resource_id) else {
+ cold_path();
+ tracing::error!(
+ ?object_id,
+ %object_kind,
+ ?resource_id,
+ "Backend resource does not exist"
+ );
+ return;
+ };
+
+ resource.destroy(curr_gl_ctx);
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct BackendResourceId(ObjectRawValue);
+
+impl intmap::IntKey for BackendResourceId
+{
+ type Int = ObjectRawValue;
+
+ const PRIME: Self::Int = <ObjectRawValue as intmap::IntKey>::PRIME;
+
+ fn into_int(self) -> Self::Int
+ {
+ self.0.into_int()
+ }
+}
+
+#[derive(Debug)]
+struct GraphicsContextSurface
+{
+ window_surface: GlutinSurface<GlutinWindowSurface>,
+ size: PhysicalSize<u32>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct ShaderBindingIndex(u32);
+
+impl intmap::IntKey for ShaderBindingIndex
+{
+ type Int = u32;
+
+ const PRIME: Self::Int = <u32 as intmap::IntKey>::PRIME;
+
+ fn into_int(self) -> Self::Int
+ {
+ self.0
+ }
+}
+
+#[derive(Debug, Clone)]
+enum BackendShaderBinding
+{
+ Uniform(GlBuffer<u8>),
+ Texture(GlTexture),
+}
+
+impl BackendShaderBinding
+{
+ fn bind(
+ &self,
+ gl_context: &MaybeCurrentContextWithFns,
+ binding_index: ShaderBindingIndex,
+ )
+ {
+ match self {
+ BackendShaderBinding::Uniform(uniform_buf) => {
+ uniform_buf.bind_to_indexed_target(
+ gl_context,
+ GlBufferBindingTarget::UniformBuffer,
+ binding_index.0,
+ );
+ }
+ BackendShaderBinding::Texture(texture) => {
+ texture.bind_to_texture_unit(gl_context, binding_index.0);
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+enum BackendResource
+{
+ Mesh
+ {
+ mesh: GraphicsMesh,
+ vertex_attrs_updated_for_shader: Option<ObjectId>,
+ },
+ Shader
+ {
+ program: GlShaderProgram,
+ program_metadata: ShaderProgramMetadata,
+ bindings: IntMap<ShaderBindingIndex, BackendShaderBinding>,
+ },
+ Texture
+ {
+ texture: GlTexture,
+ pixel_data_format: TexturePixelDataFormat,
+ kind: TextureKind,
+ },
+ Framebuffer
+ {
+ framebuffer: GlFramebuffer
+ },
+}
+
+impl BackendResource
+{
+ fn object_kind(&self) -> ObjectKind
+ {
+ match self {
+ Self::Mesh { .. } => ObjectKind::Mesh,
+ Self::Shader { .. } => ObjectKind::ShaderProgram,
+ Self::Texture { .. } => ObjectKind::Texture,
+ Self::Framebuffer { .. } => ObjectKind::Framebuffer,
+ }
+ }
+
+ fn destroy(&self, curr_gl_ctx: &MaybeCurrentContextWithFns)
+ {
+ match self {
+ Self::Mesh { mesh, .. } => {
+ mesh.destroy(curr_gl_ctx);
+ }
+ Self::Shader { program, .. } => {
+ program.clone().delete(curr_gl_ctx);
+ }
+ Self::Texture { texture, .. } => {
+ texture.clone().delete(curr_gl_ctx);
+ }
+ Self::Framebuffer { framebuffer } => {
+ framebuffer.clone().delete(curr_gl_ctx);
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+enum TextureKind
+{
+ Texture2D,
+ CubeMap,
+}
+
+#[derive(Debug, Default)]
+#[non_exhaustive]
+pub struct Extension {}
+
+impl crate::ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: crate::ecs::extension::Collector<'_>)
+ {
+ collector.add_system(*RENDER_PHASE, handle_commands);
+
+ collector.add_system(*POST_RENDER_PHASE, prepare_windows);
+ collector.add_system(*POST_RENDER_PHASE, init_window_graphics);
+
+ let _ = collector.add_sole(GraphicsContext::default());
+ }
+}
+
+fn prepare_windows(
+ window_query: Query<(
+ Option<&Window>,
+ &mut WindowCreationAttributes,
+ With<TargetWindow>,
+ Without<CreationReady>,
+ Without<WindowGlConfig>,
+ Without<WindowClosed>,
+ )>,
+ windowing_context: Single<WindowingContext>,
+ graphics_props: Single<GraphicsProperties>,
+ mut actions: Actions,
+) -> Result<(), crate::Error>
+{
+ let windowing_context = windowing_context.get()?;
+ let graphics_props = graphics_props.get()?;
+
+ let Some(display_handle) = windowing_context.display_handle() else {
+ return Ok(());
+ };
+
+ for (window_ent_id, (window, mut window_creation_attrs)) in
+ window_query.iter_with_euids()
+ {
+ tracing::debug!("Preparing window entity {window_ent_id} for use in rendering");
+
+ let mut glutin_config_template_builder =
+ glutin::config::ConfigTemplateBuilder::new();
+
+ if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt {
+ glutin_config_template_builder = glutin_config_template_builder
+ .with_multisampling(multisampling_sample_cnt);
+ }
+
+ let window_handle = match window
+ .as_ref()
+ .and_then(|window| unsafe {
+ windowing_context.get_window_as_handle(&window.wid())
+ })
+ .transpose()
+ {
+ Ok(window_handle) => window_handle,
+ Err(err) => {
+ tracing::error!("Failed to get window handle: {err}");
+ continue;
+ }
+ };
+
+ let (new_window_creation_attrs, gl_config) = match DisplayBuilder::default()
+ .with_window_attributes(window_creation_attrs.clone())
+ .build(
+ window_handle,
+ &display_handle,
+ glutin_config_template_builder,
+ |mut cfgs| cfgs.next(),
+ ) {
+ Ok((new_window_creation_attrs, gl_config)) => {
+ (new_window_creation_attrs, gl_config)
+ }
+ Err(GlutinCompatError::WindowRequired) => {
+ actions.add_components(window_ent_id, (CreationReady,));
+ continue;
+ }
+ Err(err) => {
+ tracing::error!("Failed to create platform graphics display: {err}");
+ continue;
+ }
+ };
+
+ *window_creation_attrs = new_window_creation_attrs;
+
+ actions.add_components(window_ent_id, (WindowGlConfig { gl_config },));
+
+ if window.is_none() {
+ actions.add_components(window_ent_id, (CreationReady,));
+ }
+ }
+
+ Ok(())
+}
+
+#[tracing::instrument(skip_all)]
+fn init_window_graphics(
+ window_query: Query<(
+ &Window,
+ &WindowGlConfig,
+ With<TargetWindow>,
+ Without<Surface>,
+ )>,
+ windowing_context: Single<WindowingContext>,
+ graphics_props: Single<GraphicsProperties>,
+ mut graphics_ctx: Single<GraphicsContext>,
+ mut actions: Actions,
+) -> Result<(), crate::Error>
+{
+ let Ok(graphics_ctx) = graphics_ctx.get_mut() else {
+ unreachable!();
+ };
+
+ let windowing_context = windowing_context.get()?;
+ let graphics_props = graphics_props.get()?;
+
+ for (window_ent_id, (window, window_gl_config)) in window_query.iter_with_euids() {
+ tracing::info!(
+ window_entity_id=%window_ent_id,
+ window_title=&*window.title,
+ "Initializing graphics for window"
+ );
+
+ let display = window_gl_config.gl_config.display();
+
+ let window_handle =
+ match unsafe { windowing_context.get_window_as_handle(&window.wid()) }
+ .transpose()
+ {
+ Ok(Some(window_handle)) => window_handle,
+ Ok(None) => {
+ tracing::error!(
+ wid = ?window.wid(),
+ entity_id = %window_ent_id,
+ "Windowing context does not contain window"
+ );
+ continue;
+ }
+ Err(err) => {
+ tracing::error!("Failed to get window handle: {err}");
+ continue;
+ }
+ };
+
+ let Ok(window_inner_size) =
+ PhysicalSize::<NonZero<u32>>::try_convert_from(window.inner_size)
+ else {
+ tracing::error!(
+ "Cannot create a surface for a window with a width/height of 0",
+ );
+ continue;
+ };
+
+ let window_surface = match unsafe {
+ display.create_window_surface(
+ &window_gl_config.gl_config,
+ &glutin::surface::SurfaceAttributesBuilder::<
+ glutin::surface::WindowSurface,
+ >::new()
+ .build(
+ window_handle.as_raw(),
+ window_inner_size.width,
+ window_inner_size.height,
+ ),
+ )
+ } {
+ Ok(window_surface) => window_surface,
+ Err(err) => {
+ tracing::error!("Failed to create window surface: {err}");
+ continue;
+ }
+ };
+
+ let gl_context = match graphics_ctx.gl_context.get_or_try_insert_with_fn(|| {
+ create_gl_context(
+ &window_gl_config.gl_config,
+ graphics_props,
+ window_handle,
+ &window_surface,
+ )
+ }) {
+ Ok(gl_context) => gl_context,
+ Err(err) => {
+ tracing::error!("Failed to create GL context: {err}");
+ continue;
+ }
+ };
+
+ if let Err(err) = gl_context.make_current(&window_surface) {
+ tracing::error!("Failed to make GL context current: {err}");
+ continue;
+ }
+
+ if let Err(err) = gl_set_viewport(
+ gl_context,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &opengl_bindings::data_types::Dimens {
+ width: window.inner_size.width,
+ height: window.inner_size.height,
+ },
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+
+ set_enabled(gl_context, Capability::DepthTest, graphics_props.depth_test);
+
+ set_enabled(
+ gl_context,
+ Capability::MultiSample,
+ graphics_props.multisampling_sample_cnt.is_some(),
+ );
+
+ if graphics_props.debug {
+ enable(gl_context, Capability::DebugOutput);
+ enable(gl_context, Capability::DebugOutputSynchronous);
+
+ set_debug_message_callback(gl_context, opengl_debug_message_cb);
+
+ match set_debug_message_control(
+ gl_context,
+ None,
+ None,
+ None,
+ &[],
+ MessageIdsAction::Disable,
+ ) {
+ Ok(()) => {}
+ Err(GlSetDebugMessageControlError::TooManyIds {
+ id_cnt: _,
+ max_id_cnt: _,
+ }) => {
+ unreachable!() // No ids are given
+ }
+ }
+ }
+
+ let surface_id = SurfaceId::new_unique();
+
+ actions.add_components(
+ window_ent_id,
+ (Surface {
+ id: surface_id,
+ size: window.inner_size,
+ },),
+ );
+
+ graphics_ctx.surfaces.insert(
+ surface_id,
+ GraphicsContextSurface {
+ window_surface,
+ size: window.inner_size,
+ },
+ );
+ }
+
+ Ok(())
+}
+
+#[tracing::instrument(skip_all)]
+fn handle_commands(
+ mut graphics_ctx: Single<GraphicsContext>,
+ mut object_store: Single<ObjectStore>,
+ mut command_queue: Single<CommandQueue>,
+ assets: Single<Assets>,
+) -> Result<(), crate::Error>
+{
+ let Ok(graphics_ctx) = graphics_ctx.get_mut() else {
+ unreachable!();
+ };
+
+ let object_store = object_store.get_mut()?;
+ let command_queue = command_queue.get_mut()?;
+ let assets = assets.get()?;
+
+ let GraphicsContext {
+ ref gl_context,
+ ref mut surfaces,
+ ref mut backend_resources,
+ } = *graphics_ctx;
+
+ let Some(gl_context) = gl_context else {
+ return Ok(());
+ };
+
+ let mut activated_gl_shader_program: Option<(
+ ObjectId,
+ GlShaderProgram,
+ ShaderProgramMetadata,
+ )> = None;
+
+ for command in command_queue.drain() {
+ let tracing_span = tracing::info_span!(
+ "handle_cmd",
+ command = %command.get_variant_reflection().name,
+ );
+ let _tracing_span_enter = tracing_span.enter();
+
+ match command {
+ Command::RemoveSurface(surface_id) => {
+ let Some(surface) = surfaces.remove(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ if surface.window_surface.is_current(gl_context.context()) {
+ if let Err(err) = gl_context.context().make_not_current_in_place() {
+ tracing::error!("Failed to make GL context not current: {err}");
+ }
+
+ if let Err(err) = gl_context.make_current_surfaceless() {
+ tracing::error!("Failed to make GL context current: {err}");
+ }
+ }
+
+ drop(surface);
+ }
+ Command::MakeCurrent(surface_id) => {
+ let Some(surface) = surfaces.get(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ if let Err(err) = gl_context.make_current(&surface.window_surface) {
+ tracing::error!("Failed to make graphics context current: {err}");
+ continue;
+ }
+
+ if let Err(err) = gl_set_viewport(
+ gl_context,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &opengl_bindings::data_types::Dimens {
+ width: surface.size.width,
+ height: surface.size.height,
+ },
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+ }
+ Command::SetSurfaceSize(surface_id, new_surface_size) => {
+ let Some(surface) = surfaces.get_mut(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ surface.size = new_surface_size;
+
+ if !surface.window_surface.is_current(gl_context.context()) {
+ continue;
+ }
+
+ if let Err(err) = gl_set_viewport(
+ gl_context,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &opengl_bindings::data_types::Dimens {
+ width: surface.size.width,
+ height: surface.size.height,
+ },
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+ }
+ Command::ClearBuffers(buffer_clear_mask) => {
+ let mut clear_mask = GlBufferClearMask::empty();
+
+ clear_mask.set(
+ GlBufferClearMask::COLOR,
+ buffer_clear_mask.contains(BufferClearMask::COLOR),
+ );
+
+ clear_mask.set(
+ GlBufferClearMask::DEPTH,
+ buffer_clear_mask.contains(BufferClearMask::DEPTH),
+ );
+
+ clear_mask.set(
+ GlBufferClearMask::STENCIL,
+ buffer_clear_mask.contains(BufferClearMask::STENCIL),
+ );
+
+ clear_buffers(gl_context, clear_mask);
+ }
+ Command::SwapBuffers(surface_id) => {
+ let Some(surface) = surfaces.get(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ if let Err(err) =
+ surface.window_surface.swap_buffers(gl_context.context())
+ {
+ tracing::error!("Failed to swap buffers: {err}");
+ }
+ }
+ Command::CreateShaderProgram(object_id, shader_program) => {
+ if let Err(err) = backend_resources
+ .try_create_resource::<CreateShaderError>(
+ object_store,
+ object_id,
+ || {
+ Ok(BackendResource::Shader {
+ program: create_shader_program(
+ gl_context,
+ &shader_program,
+ )?,
+ program_metadata: shader_program.metadata().clone(),
+ bindings: IntMap::with_capacity(4),
+ })
+ },
+ )
+ {
+ tracing::error!("Failed to create shader program: {err}");
+ }
+ }
+ Command::ActivateShader(shader_object_id) => {
+ let Some(resource) = backend_resources.get_resource(
+ object_store,
+ shader_object_id,
+ ObjectKind::ShaderProgram,
+ ) else {
+ continue;
+ };
+
+ let BackendResource::Shader { program, program_metadata, bindings } =
+ resource
+ else {
+ unreachable!();
+ };
+
+ program.activate(gl_context);
+
+ for (binding_index, binding) in bindings {
+ binding.bind(gl_context, binding_index);
+ }
+
+ activated_gl_shader_program =
+ Some((shader_object_id, program.clone(), program_metadata.clone()));
+ }
+ Command::SetShaderBinding(
+ shader_object_id,
+ ShaderBinding {
+ location: binding_location,
+ value: binding_value,
+ },
+ ) => {
+ let binding = set_shader_binding(
+ backend_resources,
+ object_store,
+ gl_context,
+ shader_object_id,
+ &binding_location,
+ binding_value,
+ );
+
+ if let Some(binding) = binding {
+ if Some(shader_object_id)
+ == activated_gl_shader_program
+ .as_ref()
+ .map(|(obj_id, ..)| *obj_id)
+ {
+ binding.bind(
+ gl_context,
+ ShaderBindingIndex(binding_location.binding_index),
+ );
+ }
+ }
+ }
+ Command::CreateTexture {
+ obj_id,
+ pixel_data_format,
+ creation,
+ properties,
+ } => {
+ let kind = match &creation {
+ TextureCreation::Texture2D { .. } => TextureKind::Texture2D,
+ TextureCreation::CubeMap { .. } => TextureKind::CubeMap,
+ };
+
+ if let Err(err) = backend_resources
+ .try_create_resource::<opengl_bindings::texture::Error>(
+ object_store,
+ obj_id,
+ || {
+ let gl_texture = create_gl_texture(
+ gl_context,
+ creation,
+ pixel_data_format,
+ &properties,
+ )?;
+
+ Ok(BackendResource::Texture {
+ texture: gl_texture,
+ pixel_data_format,
+ kind,
+ })
+ },
+ )
+ {
+ tracing::error!("Failed to create texture: {err}");
+ }
+ }
+ Command::UpdateTexture {
+ obj_id,
+ pixels,
+ pixel_data_format,
+ update,
+ } => {
+ let Some(texture_resource) = backend_resources.get_resource(
+ object_store,
+ obj_id,
+ ObjectKind::Texture,
+ ) else {
+ continue;
+ };
+
+ let BackendResource::Texture {
+ texture: gl_texture,
+ pixel_data_format: tex_pixel_data_format,
+ kind: tex_metadata,
+ } = texture_resource
+ else {
+ unreachable!();
+ };
+
+ if !matches!(
+ (&update, tex_metadata),
+ (TextureUpdate::Texture2D { .. }, TextureKind::Texture2D)
+ | (TextureUpdate::CubeMap { .. }, TextureKind::CubeMap)
+ ) {
+ tracing::error!(
+ texture_object_id = ?obj_id,
+ "Texture is of incorrect kind"
+ );
+ continue;
+ }
+
+ if &pixel_data_format != tex_pixel_data_format {
+ cold_path();
+ tracing::error!(
+ texture_object_id = ?obj_id,
+ expected_pixel_data_format = ?*tex_pixel_data_format,
+ found_pixel_data_format = ?pixel_data_format,
+ "Texture has incorrect pixel data format"
+ );
+ continue;
+ }
+
+ if let Err(err) = update_texture_object(
+ gl_context,
+ gl_texture,
+ pixels,
+ pixel_data_format,
+ update,
+ ) {
+ tracing::error!("Failed to update texture object: {err}");
+ }
+ }
+ Command::RemoveTexture(object_id) => {
+ backend_resources.remove_resource(
+ object_store,
+ object_id,
+ ObjectKind::Texture,
+ gl_context,
+ );
+ }
+ Command::CreateFramebuffer(object_id) => {
+ backend_resources.try_create_resource::<Infallible>(
+ object_store,
+ object_id,
+ || {
+ Ok(BackendResource::Framebuffer {
+ framebuffer: GlFramebuffer::new(gl_context),
+ })
+ },
+ );
+ }
+ Command::RemoveFramebuffer(object_id) => {
+ backend_resources.remove_resource(
+ object_store,
+ object_id,
+ ObjectKind::Framebuffer,
+ gl_context,
+ );
+ }
+ Command::UpdateFramebufferProperties {
+ object_id,
+ properties,
+ update_flags,
+ } => {
+ update_framebuffer_properties(
+ backend_resources,
+ object_store,
+ gl_context,
+ object_id,
+ properties,
+ update_flags,
+ );
+ }
+ Command::CreateMesh { obj_id, mesh, usage: mesh_usage } => {
+ let mesh = match &mesh {
+ AssetOrValue::Asset(mesh_asset) => {
+ let Some(mesh) = assets.get(mesh_asset) else {
+ tracing::error!(
+ asset_id=?mesh_asset.id(),
+ "Mesh asset does not exist"
+ );
+ continue;
+ };
+
+ mesh
+ }
+ AssetOrValue::Value(mesh) => mesh,
+ };
+
+ if let Err(err) = backend_resources
+ .try_create_resource::<graphics_mesh::Error>(
+ object_store,
+ obj_id,
+ || {
+ Ok(BackendResource::Mesh {
+ mesh: GraphicsMesh::new(gl_context, mesh, mesh_usage)?,
+ vertex_attrs_updated_for_shader: None,
+ })
+ },
+ )
+ {
+ tracing::error!("Failed to create mesh: {err}");
+ }
+ }
+ Command::UpdateMesh { obj_id, mesh, usage: mesh_usage } => {
+ let Some(resource) = backend_resources.get_resource_mut(
+ object_store,
+ obj_id,
+ ObjectKind::Mesh,
+ ) else {
+ continue;
+ };
+
+ let BackendResource::Mesh { mesh: graphics_mesh, .. } = resource else {
+ unreachable!();
+ };
+
+ if let Err(err) = graphics_mesh.update(gl_context, &mesh, mesh_usage) {
+ tracing::error!("Failed to update mesh: {err}");
+ }
+ }
+ Command::RemoveMesh(object_id) => {
+ backend_resources.remove_resource(
+ object_store,
+ object_id,
+ ObjectKind::Mesh,
+ gl_context,
+ );
+ }
+ Command::DrawMesh(object_id, draw_mesh_opts) => {
+ let Some(resource) = backend_resources.get_resource_mut(
+ object_store,
+ object_id,
+ ObjectKind::Mesh,
+ ) else {
+ continue;
+ };
+
+ let BackendResource::Mesh {
+ mesh: graphics_mesh,
+ vertex_attrs_updated_for_shader,
+ } = resource
+ else {
+ unreachable!();
+ };
+
+ let Some((shader_program_obj_id, _, shader_program_metadata)) =
+ &activated_gl_shader_program
+ else {
+ tracing::error!("No shader program is activated");
+ continue;
+ };
+
+ if vertex_attrs_updated_for_shader.is_none_or(
+ |vertex_attrs_updated_for_shader| {
+ vertex_attrs_updated_for_shader != *shader_program_obj_id
+ },
+ ) {
+ let Some(shader_program_vertex_desc) =
+ &shader_program_metadata.vertex_desc
+ else {
+ tracing::error!(
+ "Activated shader program does not have a vertex description"
+ );
+ continue;
+ };
+
+ if let Err(err) = graphics_mesh.update_vertex_attrs_for_shader(
+ gl_context,
+ shader_program_vertex_desc,
+ ) {
+ tracing::error!("Cannot draw mesh: {err}");
+ continue;
+ }
+
+ *vertex_attrs_updated_for_shader = Some(*shader_program_obj_id);
+ }
+
+ if let Err(err) = draw_mesh(gl_context, graphics_mesh, &draw_mesh_opts) {
+ tracing::error!("Failed to draw mesh: {err}");
+ }
+ }
+ Command::UpdateDrawProperties(properties, update_flags) => {
+ update_draw_properties(
+ backend_resources,
+ object_store,
+ gl_context,
+ properties,
+ update_flags,
+ );
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn create_gl_context(
+ gl_config: &GlutinConfig,
+ graphics_props: &GraphicsProperties,
+ window_handle: WindowHandle<'_>,
+ surface: &GlutinSurface<GlutinWindowSurface>,
+) -> Result<MaybeCurrentContextWithFns, CreateGlContextError>
+{
+ let display = gl_config.display();
+
+ let glutin_context = unsafe {
+ display.create_context(
+ gl_config,
+ &glutin::context::ContextAttributesBuilder::new()
+ .with_debug(graphics_props.debug)
+ .build(Some(window_handle.as_raw())),
+ )
+ }
+ .map_err(CreateGlContextError::CreateGlutinContext)?;
+
+ MaybeCurrentContextWithFns::new(glutin_context, surface)
+ .map_err(CreateGlContextError::MakeContextCurrent)
+}
+
+#[derive(Debug, thiserror::Error)]
+enum CreateGlContextError
+{
+ #[error("Glutin context creation failed")]
+ CreateGlutinContext(#[source] GlutinError),
+
+ #[error("Making GL context current failed")]
+ MakeContextCurrent(#[source] GlMakeContextCurrentError),
+}
+
+#[tracing::instrument(skip_all)]
+fn update_texture_object(
+ curr_gl_ctx: &MaybeCurrentContextWithFns,
+ gl_texture: &GlTexture,
+ pixels: Box<[u8]>,
+ pixel_data_format: TexturePixelDataFormat,
+ update: TextureUpdate,
+) -> Result<(), GlTextureError>
+{
+ match update {
+ TextureUpdate::Texture2D { size, offset } => {
+ gl_texture.store_image_2d(
+ curr_gl_ctx,
+ 0,
+ offset.into(),
+ size.into(),
+ tex_pixel_data_format_into_gl(pixel_data_format),
+ &pixels,
+ )?;
+ }
+ TextureUpdate::CubeMap { size, offset, face } => {
+ gl_texture.store_image_3d(
+ curr_gl_ctx,
+ 0,
+ Vec3 {
+ x: offset.x,
+ y: offset.y,
+ z: cube_map_texture_face_to_gl(face) as u32,
+ }
+ .into(),
+ [size.width, size.height, 1],
+ tex_pixel_data_format_into_gl(pixel_data_format),
+ &pixels,
+ )?;
+ }
+ }
+
+ gl_texture.generate_mipmap(curr_gl_ctx);
+
+ Ok(())
+}
+
+#[tracing::instrument(skip_all)]
+fn update_draw_properties(
+ backend_resources: &BackendResourceStore,
+ object_store: &ObjectStore,
+ gl_context: &MaybeCurrentContextWithFns,
+ properties: DrawProperties,
+ update_flags: DrawPropertiesUpdateFlags,
+)
+{
+ if update_flags.contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG) {
+ opengl_bindings::misc::set_polygon_mode(
+ gl_context,
+ polygon_mode_face_to_gl(properties.polygon_mode_config.face),
+ polygon_mode_to_gl(properties.polygon_mode_config.mode),
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED) {
+ set_enabled(gl_context, Capability::Blend, properties.blending_enabled);
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG) {
+ gl_blending_configure(
+ gl_context,
+ GlBlendingConfig::default()
+ .with_source_factor(blending_factor_to_gl(
+ properties.blending_config.source_factor,
+ ))
+ .with_destination_factor(blending_factor_to_gl(
+ properties.blending_config.destination_factor,
+ ))
+ .with_equation(blending_equation_to_gl(
+ properties.blending_config.equation,
+ )),
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED) {
+ set_enabled(
+ gl_context,
+ Capability::DepthTest,
+ properties.depth_test_enabled,
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::DEPTH_FUNCTION) {
+ gl_set_depth_function(
+ gl_context,
+ match properties.depth_function {
+ DepthFunction::Never => GlDepthFunction::Never,
+ DepthFunction::Less => GlDepthFunction::Less,
+ DepthFunction::Equal => GlDepthFunction::Equal,
+ DepthFunction::LessOrEqual => GlDepthFunction::LessOrEqual,
+ DepthFunction::Greater => GlDepthFunction::Greater,
+ DepthFunction::NotEqual => GlDepthFunction::NotEqual,
+ DepthFunction::GreaterOrEqual => GlDepthFunction::GreaterOrEqual,
+ DepthFunction::Always => GlDepthFunction::Always,
+ },
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED) {
+ set_enabled(
+ gl_context,
+ Capability::ScissorTest,
+ properties.scissor_test_enabled,
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::SCISSOR_BOX) {
+ gl_define_scissor_box(
+ gl_context,
+ properties.scissor_box.lower_left_corner_pos.into(),
+ properties
+ .scissor_box
+ .size
+ .unwrap_or_else(|| {
+ let (_, viewport_size) = gl_get_viewport(gl_context);
+
+ Dimens::<u16> {
+ width: viewport_size
+ .width
+ .try_into()
+ .expect("Viewport width too large"),
+ height: viewport_size
+ .height
+ .try_into()
+ .expect("Viewport height too large"),
+ }
+ })
+ .into(),
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::FACE_CULLING_ENABLED) {
+ set_enabled(
+ gl_context,
+ Capability::CullFace,
+ properties.face_culling_enabled,
+ );
+ }
+
+ if update_flags.contains(DrawPropertiesUpdateFlags::FACE_CULLING_MODE) {
+ gl_set_face_culling_mode(
+ gl_context,
+ match properties.face_culling_mode {
+ FaceCullingMode::Front => GlFaceCullingMode::Front,
+ FaceCullingMode::Back => GlFaceCullingMode::Back,
+ FaceCullingMode::FrontAndBack => GlFaceCullingMode::FrontAndBack,
+ },
+ );
+ }
+
+ 'if_draw_fb: {
+ if update_flags.contains(DrawPropertiesUpdateFlags::DRAW_FRAMEBUFFER) {
+ let Some(framebuffer_obj_id) = properties.draw_framebuffer else {
+ gl_bind_framebuffer(gl_context, GlFramebufferTarget::Draw, None);
+ break 'if_draw_fb;
+ };
+
+ let Some(framebuffer_resource) = backend_resources.get_resource(
+ object_store,
+ framebuffer_obj_id,
+ ObjectKind::Framebuffer,
+ ) else {
+ break 'if_draw_fb;
+ };
+
+ let BackendResource::Framebuffer { framebuffer } = framebuffer_resource
+ else {
+ unreachable!();
+ };
+
+ gl_bind_framebuffer(gl_context, GlFramebufferTarget::Draw, Some(framebuffer));
+ }
+ }
+
+ 'if_read_fb: {
+ if update_flags.contains(DrawPropertiesUpdateFlags::READ_FRAMEBUFFER) {
+ let Some(framebuffer_obj_id) = properties.read_framebuffer else {
+ gl_bind_framebuffer(gl_context, GlFramebufferTarget::Read, None);
+ break 'if_read_fb;
+ };
+
+ let Some(framebuffer_resource) = backend_resources.get_resource(
+ object_store,
+ framebuffer_obj_id,
+ ObjectKind::Framebuffer,
+ ) else {
+ break 'if_read_fb;
+ };
+
+ let BackendResource::Framebuffer { framebuffer } = framebuffer_resource
+ else {
+ unreachable!();
+ };
+
+ gl_bind_framebuffer(gl_context, GlFramebufferTarget::Read, Some(framebuffer));
+ }
+ }
+}
+
+#[tracing::instrument(skip_all)]
+fn update_framebuffer_properties(
+ backend_resources: &BackendResourceStore,
+ object_store: &ObjectStore,
+ current_context: &MaybeCurrentContextWithFns,
+ framebuffer_object_id: ObjectId,
+ properties: FramebufferProperties,
+ update_flags: FramebufferPropertiesUpdateFlags,
+)
+{
+ let Some(resource) = backend_resources.get_resource(
+ object_store,
+ framebuffer_object_id,
+ ObjectKind::Framebuffer,
+ ) else {
+ return;
+ };
+
+ let BackendResource::Framebuffer { framebuffer } = resource else {
+ unreachable!();
+ };
+
+ 'if_dt: {
+ if update_flags.contains(FramebufferPropertiesUpdateFlags::DEPTH_TEXTURE) {
+ let Some(framebuffer_depth_texture) = &properties.depth_texture else {
+ framebuffer
+ .detach_texture(current_context, GlFramebufferAttachment::Depth);
+
+ break 'if_dt;
+ };
+
+ let Some(depth_texture_resource) = backend_resources.get_resource(
+ object_store,
+ framebuffer_depth_texture.object_id,
+ ObjectKind::Texture,
+ ) else {
+ break 'if_dt;
+ };
+
+ let BackendResource::Texture { texture: depth_texture, .. } =
+ depth_texture_resource
+ else {
+ unreachable!();
+ };
+
+ framebuffer.attach_texture(
+ current_context,
+ GlFramebufferAttachment::Depth,
+ depth_texture.clone(),
+ framebuffer_depth_texture.mipmap_level,
+ );
+ }
+ }
+
+ if update_flags.contains(FramebufferPropertiesUpdateFlags::COLOR_DRAW_BUFFER) {
+ framebuffer.set_draw_buffer(
+ current_context,
+ properties.color_draw_buffer.map(|color_texture_index| {
+ GlFramebufferColorAttachment(color_texture_index.0)
+ }),
+ );
+ }
+}
+
+fn set_shader_binding(
+ backend_resources: &mut BackendResourceStore,
+ object_store: &ObjectStore,
+ gl_context: &MaybeCurrentContextWithFns,
+ shader_object_id: ObjectId,
+ binding_location: &ShaderBindingLocation,
+ binding_value: ShaderBindingValue,
+) -> Option<BackendShaderBinding>
+{
+ if let ShaderBindingValue::Texture(texture_object_id, _) = &binding_value {
+ let gl_texture = match backend_resources.get_resource(
+ object_store,
+ *texture_object_id,
+ ObjectKind::Texture,
+ ) {
+ Some(BackendResource::Texture { texture, .. }) => texture.clone(),
+ Some(_) => unreachable!(),
+ None => return None,
+ };
+
+ let shader_bindings = match backend_resources.get_resource_mut(
+ object_store,
+ shader_object_id,
+ ObjectKind::ShaderProgram,
+ ) {
+ Some(BackendResource::Shader { bindings, .. }) => bindings,
+ Some(_) => unreachable!(),
+ None => return None,
+ };
+
+ // TODO: Textures should probably also be handled somehow here
+ if let Some(BackendShaderBinding::Uniform(prev_binding_uniform_buf)) =
+ shader_bindings.remove(ShaderBindingIndex(binding_location.binding_index))
+ {
+ prev_binding_uniform_buf.delete(gl_context);
+ }
+
+ return Some(
+ shader_bindings
+ .entry(ShaderBindingIndex(binding_location.binding_index))
+ .set_or_insert_with(|| BackendShaderBinding::Texture(gl_texture.clone()))
+ .clone(),
+ );
+
+ // if Some(shader_object_id)
+ // == activated_gl_shader_program
+ // .as_ref()
+ // .map(|(obj_id, ..)| *obj_id)
+ // {
+ // BackendShaderBinding::Texture(gl_texture.clone()).bind(
+ // gl_context,
+ // ShaderBindingIndex(binding_location.binding_index),
+ // );
+ // }
+ }
+
+ let shader_bindings = match backend_resources.get_resource_mut(
+ object_store,
+ shader_object_id,
+ ObjectKind::ShaderProgram,
+ ) {
+ Some(BackendResource::Shader { bindings, .. }) => bindings,
+ Some(_) => unreachable!(),
+ None => return None,
+ };
+
+ let binding = if let Some(binding @ BackendShaderBinding::Uniform(_)) =
+ shader_bindings
+ .get(ShaderBindingIndex(binding_location.binding_index))
+ .cloned()
+ {
+ binding
+ } else {
+ let uniform_buf = GlBuffer::<u8>::new(gl_context);
+
+ uniform_buf
+ .init(
+ gl_context,
+ binding_location.binding_size,
+ opengl_bindings::buffer::Usage::Dynamic,
+ )
+ .unwrap();
+
+ shader_bindings
+ .entry(ShaderBindingIndex(binding_location.binding_index))
+ .set_or_insert_with(|| BackendShaderBinding::Uniform(uniform_buf))
+ .clone()
+ };
+
+ let BackendShaderBinding::Uniform(binding_uniform_buf) = &binding else {
+ unreachable!();
+ };
+
+ let fvec3_value;
+ let mut color_value = [0.0; 4];
+
+ binding_uniform_buf
+ .store_at_byte_offset(
+ gl_context,
+ binding_location.byte_offset,
+ match binding_value {
+ ShaderBindingValue::Uint(ref value) => value.as_bytes(),
+ ShaderBindingValue::Int(ref value) => value.as_bytes(),
+ ShaderBindingValue::Float(ref value) => value.as_bytes(),
+ ShaderBindingValue::FVec3(value) => {
+ fvec3_value = [value.x, value.y, value.z];
+ fvec3_value.as_bytes()
+ }
+ ShaderBindingValue::Color(value) => match value {
+ Color::Rgb(value) => {
+ color_value[..3].copy_from_slice(&[value.r, value.g, value.b]);
+
+ color_value[..3].as_bytes()
+ }
+ Color::Rgba(value) => {
+ color_value[..4]
+ .copy_from_slice(&[value.r, value.g, value.b, value.a]);
+
+ color_value[..4].as_bytes()
+ }
+ },
+ ShaderBindingValue::FMat4x4(ref value) => value.items().as_bytes(),
+ ShaderBindingValue::Texture(_, _) => unreachable!(),
+ },
+ )
+ .unwrap();
+
+ Some(binding)
+
+ // if Some(shader_object_id)
+ // == activated_gl_shader_program
+ // .as_ref()
+ // .map(|(obj_id, ..)| *obj_id)
+ // {
+ // BackendShaderBinding::Uniform(binding_uniform_buf.clone()).bind(
+ // gl_context,
+ // ShaderBindingIndex(binding_location.binding_index),
+ // );
+ // }
+}
+
+fn draw_mesh(
+ current_context: &MaybeCurrentContextWithFns,
+ graphics_mesh: &GraphicsMesh,
+ opts: &DrawMeshOptions,
+) -> Result<(), GlDrawError>
+{
+ graphics_mesh.vertex_arr.bind(current_context);
+
+ if graphics_mesh.index_buffer.is_some() {
+ VertexArray::draw_elements(
+ current_context,
+ opengl_bindings::vertex_array::DrawElementsOptions {
+ primitive_kind: PrimitiveKind::Triangles,
+ element_offset: opts.element_offset,
+ element_cnt: opts.element_cnt.unwrap_or(graphics_mesh.element_cnt),
+ vertex_offset: opts.vertex_offset,
+ },
+ )?;
+ } else {
+ VertexArray::draw_arrays(
+ current_context,
+ PrimitiveKind::Triangles,
+ opts.vertex_offset,
+ opts.element_cnt.unwrap_or(graphics_mesh.element_cnt),
+ )?;
+ }
+
+ Ok(())
+}
+
+fn tex_pixel_data_format_into_gl(
+ tex_pixel_data_format: TexturePixelDataFormat,
+) -> GlTexturePixelDataFormat
+{
+ match tex_pixel_data_format {
+ TexturePixelDataFormat::Rgb(data_type) => {
+ GlTexturePixelDataFormat::Rgb(match data_type {
+ crate::rendering::RgbTextureDataType::UnsignedByte => {
+ opengl_bindings::texture::RgbDataType::UnsignedByte
+ }
+ })
+ }
+ TexturePixelDataFormat::Srgb(data_type) => {
+ GlTexturePixelDataFormat::Srgb(match data_type {
+ crate::rendering::SrgbTextureDataType::UnsignedByte => {
+ opengl_bindings::texture::SrgbDataType::UnsignedByte
+ }
+ })
+ }
+ TexturePixelDataFormat::Rgba(data_type) => {
+ GlTexturePixelDataFormat::Rgba(match data_type {
+ crate::rendering::RgbaTextureDataType::UnsignedByte => {
+ opengl_bindings::texture::RgbaDataType::UnsignedByte
+ }
+ })
+ }
+ TexturePixelDataFormat::Srgba(data_type) => {
+ GlTexturePixelDataFormat::Srgba(match data_type {
+ crate::rendering::SrgbaTextureDataType::UnsignedByte => {
+ opengl_bindings::texture::SrgbaDataType::UnsignedByte
+ }
+ })
+ }
+ TexturePixelDataFormat::Depth(data_type) => {
+ GlTexturePixelDataFormat::DepthComponent(match data_type {
+ crate::rendering::DepthTextureDataType::Float32 => {
+ opengl_bindings::texture::DepthComponentDataType::Float32
+ }
+ })
+ }
+ }
+}
+
+fn create_gl_texture(
+ curr_gl_context: &MaybeCurrentContextWithFns,
+ texture_creation: TextureCreation,
+ texture_pixel_data_format: TexturePixelDataFormat,
+ texture_properties: &TextureProperties,
+) -> Result<GlTexture, GlTextureError>
+{
+ let gl_texture = match texture_creation {
+ TextureCreation::Texture2D { size, image } => {
+ GlTexture::builder().size(size.into()).create_2d(
+ curr_gl_context,
+ image.as_deref(),
+ tex_pixel_data_format_into_gl(texture_pixel_data_format),
+ )
+ }
+ TextureCreation::CubeMap { size, images } => {
+ GlTexture::builder().size(size.into()).create_cube_map(
+ curr_gl_context,
+ images.as_ref().map(|images| {
+ images.each_ref().map(|(face, image)| {
+ (cube_map_texture_face_to_gl(*face), &**image)
+ })
+ }),
+ tex_pixel_data_format_into_gl(texture_pixel_data_format),
+ )
+ }
+ }?;
+
+ gl_texture.set_wrap(
+ curr_gl_context,
+ texture_wrapping_to_gl(texture_properties.wrap),
+ );
+
+ gl_texture.set_magnifying_filter(
+ curr_gl_context,
+ texture_filtering_to_gl(texture_properties.magnifying_filter),
+ );
+
+ gl_texture.set_minifying_filter(
+ curr_gl_context,
+ texture_filtering_to_gl(texture_properties.minifying_filter),
+ );
+
+ if let Some(border) = texture_properties.border.as_ref() {
+ gl_texture.set_float_border_values(
+ curr_gl_context,
+ match border {
+ TextureBorder::RgbaF32(color) => [color.r, color.g, color.b, color.a],
+ TextureBorder::Depth(depth) => [*depth, 0.0, 0.0, 0.0],
+ },
+ );
+ }
+
+ Ok(gl_texture)
+}
+
+fn cube_map_texture_face_to_gl(face: CubeMapTextureFace) -> GlCubeMapTextureFace
+{
+ match face {
+ CubeMapTextureFace::PositiveX => GlCubeMapTextureFace::PositiveX,
+ CubeMapTextureFace::NegativeX => GlCubeMapTextureFace::NegativeX,
+ CubeMapTextureFace::PositiveY => GlCubeMapTextureFace::PositiveY,
+ CubeMapTextureFace::NegativeY => GlCubeMapTextureFace::NegativeY,
+ CubeMapTextureFace::PositiveZ => GlCubeMapTextureFace::PositiveZ,
+ CubeMapTextureFace::NegativeZ => GlCubeMapTextureFace::NegativeZ,
+ }
+}
+
+fn create_shader_program(
+ current_context: &MaybeCurrentContextWithFns,
+ shader_program: &ShaderProgram,
+) -> Result<GlShaderProgram, CreateShaderError>
+{
+ let shader_program_reflection = shader_program.reflection(0).expect("Not possible");
+
+ let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection
+ .entry_points()
+ .enumerate()
+ .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex)
+ .ok_or_else(|| {
+ CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex)
+ })?;
+
+ let vertex_shader_entry_point_code = shader_program
+ .get_entry_point_code(vs_entry_point_index.try_into().expect(
+ "Vertex shader entry point index does not fit in 32-bit unsigned int",
+ ))
+ .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
+ err,
+ stage: ShaderStage::Vertex,
+ entrypoint: vs_entry_point_reflection
+ .name()
+ .map_or("(none)".into(), |name| name.to_string().into()),
+ })?;
+
+ let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection
+ .entry_points()
+ .enumerate()
+ .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment)
+ .ok_or_else(|| {
+ CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment)
+ })?;
+
+ let fragment_shader_entry_point_code = shader_program
+ .get_entry_point_code(fs_entry_point_index.try_into().expect(
+ "Fragment shader entry point index does not fit in 32-bit unsigned int",
+ ))
+ .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
+ err,
+ stage: ShaderStage::Fragment,
+ entrypoint: fs_entry_point_reflection
+ .name()
+ .map_or("(none)".into(), |name| name.to_string().into()),
+ })?;
+
+ let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex);
+
+ vertex_shader.set_source(
+ current_context,
+ vertex_shader_entry_point_code.as_str().unwrap(),
+ )?;
+
+ vertex_shader.compile(current_context)?;
+
+ let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment);
+
+ fragment_shader.set_source(
+ current_context,
+ fragment_shader_entry_point_code.as_str().unwrap(),
+ )?;
+
+ fragment_shader.compile(current_context)?;
+
+ let gl_shader_program = GlShaderProgram::new(current_context);
+
+ gl_shader_program.attach(current_context, &vertex_shader);
+ gl_shader_program.attach(current_context, &fragment_shader);
+
+ gl_shader_program.link(current_context)?;
+
+ Ok(gl_shader_program)
+}
+
+#[derive(Debug, thiserror::Error)]
+enum CreateShaderError
+{
+ #[error(
+ "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}"
+ )]
+ GetShaderEntryPointCodeFailed
+ {
+ #[source]
+ err: ShaderError,
+ stage: ShaderStage,
+ entrypoint: Cow<'static, str>,
+ },
+
+ #[error("No entrypoint was found for shader stage {0:?}")]
+ NoShaderStageEntrypointFound(ShaderStage),
+
+ #[error(transparent)]
+ ShaderError(#[from] GlShaderError),
+}
+
+// This function not having tracing instrumentation is intentional. It would not provide
+// any value
+fn opengl_debug_message_cb(
+ source: MessageSource,
+ ty: MessageType,
+ id: u32,
+ severity: MessageSeverity,
+ message: &str,
+)
+{
+ macro_rules! emit_log_with_message {
+ ($level: expr) => {
+ tracing::event!($level, ?source, ?ty, id, ?severity, "{message}");
+ };
+ }
+
+ if matches!(severity, MessageSeverity::Notification) {
+ return;
+ }
+
+ match ty {
+ MessageType::Error => {
+ emit_log_with_message!(tracing::Level::ERROR);
+
+ let backtrace = std::backtrace::Backtrace::capture();
+
+ if backtrace.status() == std::backtrace::BacktraceStatus::Captured {
+ tracing::error!("{backtrace}");
+ }
+ }
+ MessageType::Other => {
+ emit_log_with_message!(tracing::Level::INFO);
+ }
+ _ => {
+ emit_log_with_message!(tracing::Level::WARN);
+ }
+ }
+}
+
+#[inline]
+fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping
+{
+ match texture_wrapping {
+ TextureWrapping::Repeat => GlTextureWrapping::Repeat,
+ TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat,
+ TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge,
+ TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder,
+ }
+}
+
+#[inline]
+fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering
+{
+ match texture_filtering {
+ TextureFiltering::Linear => GlTextureFiltering::Linear,
+ TextureFiltering::Nearest => GlTextureFiltering::Nearest,
+ }
+}
+
+impl<Value: ReprC + Copy> From<Vec2<Value>> for opengl_bindings::data_types::Vec2<Value>
+{
+ fn from(vec2: Vec2<Value>) -> Self
+ {
+ Self { x: vec2.x, y: vec2.y }
+ }
+}
+
+impl<Value: ReprC + IntoBytes + Copy> From<Vec3<Value>>
+ for opengl_bindings::data_types::Vec3<Value>
+{
+ fn from(vec3: Vec3<Value>) -> Self
+ {
+ Self { x: vec3.x, y: vec3.y, z: vec3.z }
+ }
+}
+
+impl<Value: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value>
+{
+ fn from(dimens: Dimens<Value>) -> Self
+ {
+ Self {
+ width: dimens.width,
+ height: dimens.height,
+ }
+ }
+}
+
+fn polygon_mode_to_gl(mode: PolygonMode) -> GlPolygonMode
+{
+ match mode {
+ PolygonMode::Point => GlPolygonMode::Point,
+ PolygonMode::Fill => GlPolygonMode::Fill,
+ PolygonMode::Line => GlPolygonMode::Line,
+ }
+}
+
+fn polygon_mode_face_to_gl(face: PolygonModeFace) -> GlPolygonModeFace
+{
+ match face {
+ PolygonModeFace::Front => GlPolygonModeFace::Front,
+ PolygonModeFace::Back => GlPolygonModeFace::Back,
+ PolygonModeFace::FrontAndBack => GlPolygonModeFace::FrontAndBack,
+ }
+}
+
+fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor
+{
+ match blending_factor {
+ BlendingFactor::Zero => GlBlendingFactor::Zero,
+ BlendingFactor::One => GlBlendingFactor::One,
+ BlendingFactor::SrcColor => GlBlendingFactor::SrcColor,
+ BlendingFactor::OneMinusSrcColor => GlBlendingFactor::OneMinusSrcColor,
+ BlendingFactor::DstColor => GlBlendingFactor::DstColor,
+ BlendingFactor::OneMinusDstColor => GlBlendingFactor::OneMinusDstColor,
+ BlendingFactor::SrcAlpha => GlBlendingFactor::SrcAlpha,
+ BlendingFactor::OneMinusSrcAlpha => GlBlendingFactor::OneMinusSrcAlpha,
+ BlendingFactor::DstAlpha => GlBlendingFactor::DstAlpha,
+ BlendingFactor::OneMinusDstAlpha => GlBlendingFactor::OneMinusDstAlpha,
+ BlendingFactor::ConstantColor => GlBlendingFactor::ConstantColor,
+ BlendingFactor::OneMinusConstantColor => GlBlendingFactor::OneMinusConstantColor,
+ BlendingFactor::ConstantAlpha => GlBlendingFactor::ConstantAlpha,
+ BlendingFactor::OneMinusConstantAlpha => GlBlendingFactor::OneMinusConstantAlpha,
+ }
+}
+
+fn blending_equation_to_gl(blending_equation: BlendingEquation) -> GlBlendingEquation
+{
+ match blending_equation {
+ BlendingEquation::Add => GlBlendingEquation::Add,
+ BlendingEquation::Subtract => GlBlendingEquation::Subtract,
+ BlendingEquation::ReverseSubtract => GlBlendingEquation::ReverseSubtract,
+ BlendingEquation::Min => GlBlendingEquation::Min,
+ BlendingEquation::Max => GlBlendingEquation::Max,
+ }
+}
+
+trait IntMapEntryExt<'map, Value>
+{
+ fn set_or_insert_with(self, func: impl FnOnce() -> Value) -> &'map mut Value;
+}
+
+impl<'map, Key, Value> IntMapEntryExt<'map, Value> for intmap::Entry<'map, Key, Value>
+where
+ Key: intmap::IntKey,
+ Value: 'map,
+{
+ fn set_or_insert_with(self, func: impl FnOnce() -> Value) -> &'map mut Value
+ {
+ match self {
+ Self::Occupied(occupied) => {
+ let value = occupied.into_mut();
+
+ *value = func();
+
+ value
+ }
+ Self::Vacant(vacant) => vacant.insert(func()),
+ }
+ }
+}
diff --git a/engine/src/rendering/backend/opengl/glutin_compat.rs b/engine/src/rendering/backend/opengl/glutin_compat.rs
new file mode 100644
index 0000000..bba8c55
--- /dev/null
+++ b/engine/src/rendering/backend/opengl/glutin_compat.rs
@@ -0,0 +1,256 @@
+// Original file:
+// https://github.com/rust-windowing/glutin/blob/
+// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs
+//
+// Copyright © 2022 Kirill Chibisov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+//! This library provides helpers for cross-platform [`glutin`] bootstrapping
+//! with [`winit`].
+
+#![deny(rust_2018_idioms)]
+#![deny(rustdoc::broken_intra_doc_links)]
+#![deny(clippy::all)]
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
+#![cfg_attr(clippy, deny(warnings))]
+
+use glutin::config::{Config, ConfigTemplateBuilder};
+use glutin::display::{Display, DisplayApiPreference};
+use glutin::error::Error as GlutinError;
+use glutin::prelude::*;
+use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle};
+
+use crate::windowing::window::CreationAttributes as WindowCreationAttributes;
+
+#[cfg(all(not(windows), not(target_os = "macos"), not(target_os = "linux")))]
+compile_error!("Unsupported platform");
+
+#[cfg(target_family = "wasm")]
+compile_error!("Wasm targets are not supported");
+
+/// The helper to perform [`Display`] creation and OpenGL platform
+/// bootstrapping with the help of [`winit`] with little to no platform specific
+/// code.
+///
+/// This is only required for the initial setup. If you want to create
+/// additional windows just use the [`finalize_window`] function and the
+/// configuration you've used either for the original window or picked with the
+/// existing [`Display`].
+///
+/// [`winit`]: winit
+/// [`Display`]: glutin::display::Display
+#[derive(Default, Debug, Clone)]
+pub struct DisplayBuilder
+{
+ preference: ApiPreference,
+ window_attributes: WindowCreationAttributes,
+}
+
+impl DisplayBuilder
+{
+ /// The preference in picking the configuration.
+ #[allow(dead_code)]
+ pub fn with_preference(mut self, preference: ApiPreference) -> Self
+ {
+ self.preference = preference;
+ self
+ }
+
+ /// The window attributes to use when building a window.
+ ///
+ /// By default no window is created.
+ pub fn with_window_attributes(
+ mut self,
+ window_creation_attrs: WindowCreationAttributes,
+ ) -> Self
+ {
+ self.window_attributes = window_creation_attrs;
+ self
+ }
+
+ /// Initialize the OpenGL platform and create a compatible window to use
+ /// with it when the [`WindowAttributes`] was passed with
+ /// [`Self::with_window_attributes()`]. It's optional, since on some
+ /// platforms like `Android` it is not available early on, so you want to
+ /// find configuration and later use it with the [`finalize_window`].
+ /// But if you don't care about such platform you can always pass
+ /// [`WindowAttributes`].
+ ///
+ /// # Api-specific
+ ///
+ /// **WGL:** - [`WindowAttributes`] **must** be passed in
+ /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired,
+ /// otherwise only builtin functions like `glClear` will be available.
+ pub fn build<ConfigPickerFn>(
+ self,
+ window_handle: Option<WindowHandle<'_>>,
+ display_handle: &DisplayHandle<'_>,
+ template_builder: ConfigTemplateBuilder,
+ config_picker_fn: ConfigPickerFn,
+ ) -> Result<(WindowCreationAttributes, Config), Error>
+ where
+ ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>,
+ {
+ // XXX with WGL backend window should be created first.
+ let raw_window_handle = if cfg!(windows) {
+ let Some(window_handle) = window_handle else {
+ return Err(Error::WindowRequired);
+ };
+
+ Some(window_handle.as_raw())
+ } else {
+ None
+ };
+
+ let gl_display =
+ create_display(display_handle, self.preference, raw_window_handle)
+ .map_err(Error::CreateDisplayFailed)?;
+
+ // XXX the native window must be passed to config picker when WGL is used
+ // otherwise very limited OpenGL features will be supported.
+ let template_builder = if let Some(raw_window_handle) =
+ raw_window_handle.filter(|_| cfg!(windows))
+ {
+ template_builder.compatible_with_native_window(raw_window_handle)
+ } else {
+ template_builder
+ };
+
+ let template = template_builder.build();
+
+ // SAFETY: The RawWindowHandle passed on the config template
+ // (when cfg(windows)) will always point to a valid object since it is
+ // derived from the window_handle argument which when Some is a WindowHandle and
+ // WindowHandles always point to a valid object
+ let gl_configs = unsafe { gl_display.find_configs(template) }
+ .map_err(Error::FindConfigsFailed)?;
+
+ let picked_gl_config =
+ config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?;
+
+ let window_attrs = cfg_select! {
+ windows => self.window_attributes,
+ _ => {
+ finalize_window_creation_attrs(self.window_attributes, &picked_gl_config)
+ }
+ };
+
+ Ok((window_attrs, picked_gl_config))
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to create display")]
+ CreateDisplayFailed(#[source] GlutinError),
+
+ #[error("Failed to find configs")]
+ FindConfigsFailed(#[source] GlutinError),
+
+ #[error("No config was picked by config picker function")]
+ NoConfigPicked,
+
+ #[error("Window required for building display on current platform")]
+ WindowRequired,
+}
+
+#[allow(unused_variables)]
+fn create_display(
+ display_handle: &DisplayHandle<'_>,
+ api_preference: ApiPreference,
+ raw_window_handle: Option<RawWindowHandle>,
+) -> Result<Display, GlutinError>
+{
+ let preference = cfg_select! {
+ windows => match api_preference {
+ ApiPreference::PreferEgl => {
+ DisplayApiPreference::EglThenWgl(raw_window_handle)
+ }
+ ApiPreference::FallbackEgl => {
+ DisplayApiPreference::WglThenEgl(raw_window_handle)
+ }
+ },
+ target_os = "linux" => match api_preference {
+ ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ )),
+ ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ )),
+ },
+ target_os = "macos" => DisplayApiPreference::Cgl,
+ };
+
+ let handle = display_handle.as_raw();
+
+ unsafe { Display::new(handle, preference) }
+}
+
+/// Finalize [`Window`] creation by applying the options from the [`Config`], be
+/// aware that it could remove incompatible options from the window builder like
+/// `transparency`, when the provided config doesn't support it.
+///
+/// [`Window`]: winit::window::Window
+/// [`Config`]: glutin::config::Config
+#[cfg(not(windows))]
+fn finalize_window_creation_attrs(
+ mut attributes: WindowCreationAttributes,
+ gl_config: &Config,
+) -> WindowCreationAttributes
+{
+ // Disable transparency if the end config doesn't support it.
+ if gl_config.supports_transparency() == Some(false) {
+ attributes = attributes.with_transparent(false);
+ }
+
+ #[cfg(target_os = "linux")]
+ if let Some(x11_visual) = glutin::platform::x11::X11GlConfigExt::x11_visual(gl_config)
+ {
+ return attributes.with_x_visual_id(Some(
+ crate::windowing::window::XVisualID::try_from(x11_visual.visual_id())
+ .expect("X visual ID is too large"),
+ ));
+ }
+
+ attributes
+}
+
+/// Simplified version of the [`DisplayApiPreference`] which is used to simplify
+/// cross platform window creation.
+///
+/// To learn about platform differences the [`DisplayApiPreference`] variants.
+///
+/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference
+#[allow(dead_code)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum ApiPreference
+{
+ /// Prefer `EGL` over system provider like `GLX` and `WGL`.
+ PreferEgl,
+
+ /// Fallback to `EGL` when failed to create the system profile.
+ ///
+ /// This behavior is used by default. However consider using
+ /// [`Self::PreferEgl`] if you don't care about missing EGL features.
+ #[default]
+ FallbackEgl,
+}
diff --git a/engine/src/rendering/backend/opengl/graphics_mesh.rs b/engine/src/rendering/backend/opengl/graphics_mesh.rs
new file mode 100644
index 0000000..899f50f
--- /dev/null
+++ b/engine/src/rendering/backend/opengl/graphics_mesh.rs
@@ -0,0 +1,263 @@
+use std::hint::cold_path;
+
+use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage};
+use opengl_bindings::vertex_array::{
+ AttributeFormat as GlVertexArrayAttributeFormat,
+ BindVertexBufferError as GlVertexArrayBindVertexBufferError,
+ DataType as GlVertexArrayDataType,
+ VertexArray as GlVertexArray,
+ VertexBufferSpec as GlVertexArrayVertexBufferSpec,
+};
+use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns;
+
+use crate::mesh::vertex_buffer::VertexAttrProperties as MeshVertexAttrProperties;
+use crate::mesh::{Mesh, VertexAttrType};
+use crate::rendering::shader::{
+ VertexDescription as ShaderVertexDescription,
+ VertexInputSemName,
+};
+use crate::rendering::MeshUsage;
+
+const VERTEX_BUF_BINDING_INDEX: u32 = 0;
+
+#[derive(Debug)]
+pub struct GraphicsMesh
+{
+ /// Vertex and index buffer has to live as long as the vertex array
+ vertex_buffer: GlBuffer<u8>,
+ vertex_attr_props: Vec<MeshVertexAttrProperties>,
+ last_max_vertex_attr_index: u32,
+ pub index_buffer: Option<GlBuffer<u32>>,
+ pub element_cnt: u32,
+ pub vertex_arr: GlVertexArray,
+}
+
+impl GraphicsMesh
+{
+ #[tracing::instrument(skip_all)]
+ pub fn new(
+ current_context: &GlCurrentContextWithFns,
+ mesh: &Mesh,
+ mesh_usage: MeshUsage,
+ ) -> Result<Self, Error>
+ {
+ let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage);
+
+ let vertex_arr = GlVertexArray::new(current_context);
+ let vertex_buffer = GlBuffer::new(current_context);
+
+ vertex_buffer
+ .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage)
+ .map_err(Error::StoreVerticesFailed)?;
+
+ if let Err(err) = vertex_arr.bind_vertex_buffer(
+ current_context,
+ VERTEX_BUF_BINDING_INDEX,
+ &vertex_buffer,
+ GlVertexArrayVertexBufferSpec {
+ offset: 0,
+ vertex_size: mesh.vertex_buf().vertex_size(),
+ },
+ ) {
+ match err {
+ GlVertexArrayBindVertexBufferError::OffsetValueTooLarge {
+ value: _,
+ max_value: _,
+ } => unreachable!(),
+ GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge {
+ value,
+ max_value,
+ } => {
+ panic!(
+ "Size of vertex ({value}) is too large. Must be less than {max_value}"
+ );
+ }
+ }
+ }
+
+ if let Some(indices) = mesh.indices() {
+ let index_buffer = GlBuffer::new(current_context);
+
+ index_buffer
+ .store(current_context, indices, buffer_usage)
+ .map_err(Error::StoreIndicesFailed)?;
+
+ vertex_arr.bind_element_buffer(current_context, &index_buffer);
+
+ return Ok(Self {
+ vertex_buffer,
+ vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(),
+ last_max_vertex_attr_index: 0,
+ index_buffer: Some(index_buffer),
+ element_cnt: indices
+ .len()
+ .try_into()
+ .expect("Mesh index count does not fit into a 32-bit unsigned int"),
+ vertex_arr,
+ });
+ }
+
+ Ok(Self {
+ vertex_buffer,
+ vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(),
+ last_max_vertex_attr_index: 0,
+ index_buffer: None,
+ element_cnt: mesh
+ .vertex_buf()
+ .len()
+ .try_into()
+ .expect("Mesh vertex count does not fit into a 32-bit unsigned int"),
+ vertex_arr,
+ })
+ }
+
+ pub fn update(
+ &mut self,
+ current_context: &GlCurrentContextWithFns,
+ mesh: &Mesh,
+ mesh_usage: MeshUsage,
+ ) -> Result<(), Error>
+ {
+ let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage);
+
+ self.vertex_buffer
+ .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage)
+ .map_err(Error::StoreVerticesFailed)?;
+
+ if let Some(indices) = mesh.indices() {
+ let index_buffer = self
+ .index_buffer
+ .get_or_insert_with(|| GlBuffer::new(current_context));
+
+ index_buffer
+ .store(current_context, indices, buffer_usage)
+ .map_err(Error::StoreIndicesFailed)?;
+
+ self.vertex_arr
+ .bind_element_buffer(current_context, index_buffer);
+
+ self.element_cnt = indices
+ .len()
+ .try_into()
+ .expect("Mesh index count does not fit into a 32-bit unsigned int");
+
+ return Ok(());
+ }
+
+ if let Some(index_buffer) = self.index_buffer.take() {
+ index_buffer.delete(current_context);
+ self.vertex_arr.unbind_element_buffer(current_context);
+ }
+
+ self.element_cnt = mesh
+ .vertex_buf()
+ .len()
+ .try_into()
+ .expect("Mesh vertex count does not fit into a 32-bit unsigned int");
+
+ Ok(())
+ }
+
+ pub fn update_vertex_attrs_for_shader(
+ &mut self,
+ curr_gl_ctx: &GlCurrentContextWithFns,
+ shader_vertex_desc: &ShaderVertexDescription,
+ ) -> Result<(), VertexAttrsUpdatingError>
+ {
+ for index in 0..self.last_max_vertex_attr_index {
+ self.vertex_arr.disable_attrib(curr_gl_ctx, index);
+ }
+
+ let mut max_vertex_attr_index = 0u32;
+
+ for vertex_input_desc in &*shader_vertex_desc.inputs {
+ let Some(vertex_attr_props) =
+ self.vertex_attr_props.iter().find(|vertex_attr_props| {
+ vertex_input_desc
+ .semantic_name
+ .matches_vertex_label(&vertex_attr_props.label)
+ })
+ else {
+ cold_path();
+ return Err(VertexAttrsUpdatingError::MissingVertexAttr(
+ vertex_input_desc.semantic_name.clone(),
+ ));
+ };
+
+ let attrib_index: u32 = vertex_input_desc.index.try_into().unwrap();
+
+ self.vertex_arr.enable_attrib(curr_gl_ctx, attrib_index);
+
+ self.vertex_arr.set_attrib_format(
+ curr_gl_ctx,
+ attrib_index,
+ match &vertex_attr_props.ty {
+ VertexAttrType::Float32 => GlVertexArrayAttributeFormat {
+ data_type: GlVertexArrayDataType::Float,
+ count: 1,
+ normalized: false,
+ offset: vertex_attr_props.byte_offset.try_into().unwrap(),
+ },
+ VertexAttrType::Float32Array { length } => {
+ GlVertexArrayAttributeFormat {
+ data_type: GlVertexArrayDataType::Float,
+ count: (*length).try_into().unwrap(),
+ normalized: false,
+ offset: vertex_attr_props.byte_offset.try_into().unwrap(),
+ }
+ }
+ },
+ );
+
+ self.vertex_arr.set_attrib_vertex_buf_binding(
+ curr_gl_ctx,
+ attrib_index,
+ VERTEX_BUF_BINDING_INDEX,
+ );
+
+ max_vertex_attr_index = max_vertex_attr_index.max(attrib_index);
+ }
+
+ self.last_max_vertex_attr_index = max_vertex_attr_index;
+
+ Ok(())
+ }
+
+ pub fn destroy(&self, curr_gl_ctx: &GlCurrentContextWithFns)
+ {
+ self.vertex_arr.delete(curr_gl_ctx);
+ self.vertex_buffer.delete(curr_gl_ctx);
+
+ if let Some(index_buffer) = &self.index_buffer {
+ index_buffer.delete(curr_gl_ctx);
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to store vertices in vertex buffer")]
+ StoreVerticesFailed(#[source] opengl_bindings::buffer::Error),
+
+ #[error("Failed to store indices in index buffer")]
+ StoreIndicesFailed(#[source] opengl_bindings::buffer::Error),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum VertexAttrsUpdatingError
+{
+ #[error(
+ "Mesh is missing equivalent vertex attribute for shader's vertex input: {0}"
+ )]
+ MissingVertexAttr(VertexInputSemName),
+}
+
+fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage
+{
+ match mesh_usage {
+ MeshUsage::Stream => GlBufferUsage::Stream,
+ MeshUsage::Static => GlBufferUsage::Static,
+ MeshUsage::Dynamic => GlBufferUsage::Dynamic,
+ }
+}