summaryrefslogtreecommitdiff
path: root/engine/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/renderer')
-rw-r--r--engine/src/renderer/blending.rs89
-rw-r--r--engine/src/renderer/main_render_pass.rs241
-rw-r--r--engine/src/renderer/object.rs137
-rw-r--r--engine/src/renderer/opengl.rs1884
-rw-r--r--engine/src/renderer/opengl/glsl/fragment.glsl73
-rw-r--r--engine/src/renderer/opengl/glsl/light.glsl133
-rw-r--r--engine/src/renderer/opengl/glsl/vertex.glsl24
-rw-r--r--engine/src/renderer/opengl/glsl/vertex_data.glsl11
-rw-r--r--engine/src/renderer/opengl/glutin_compat.rs268
-rw-r--r--engine/src/renderer/opengl/graphics_mesh.rs216
10 files changed, 2276 insertions, 800 deletions
diff --git a/engine/src/renderer/blending.rs b/engine/src/renderer/blending.rs
new file mode 100644
index 0000000..9ae2f82
--- /dev/null
+++ b/engine/src/renderer/blending.rs
@@ -0,0 +1,89 @@
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Config
+{
+ pub source_factor: Factor,
+ pub destination_factor: Factor,
+ pub equation: Equation,
+}
+
+impl Default for Config
+{
+ fn default() -> Self
+ {
+ Self {
+ source_factor: Factor::One,
+ destination_factor: Factor::Zero,
+ equation: Equation::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum Factor
+{
+ /// Factor will be the RGBA color `(0,0,0,0)`
+ Zero,
+
+ /// Factor will be the RGBA color `(1,1,1,1)`
+ One,
+
+ /// Factor will be the source color
+ SrcColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - source color`
+ OneMinusSrcColor,
+
+ /// Factor will be the destination color
+ DstColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - destination color`
+ OneMinusDstColor,
+
+ /// Factor will be the alpha component of the source color.
+ SrcAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - source color alpha`
+ OneMinusSrcAlpha,
+
+ /// Factor will be the alpha component of the destination color.
+ DstAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - destination color alpha`
+ OneMinusDstAlpha,
+
+ /// Factor will be the constant color
+ ConstantColor,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - constant color`
+ OneMinusConstantColor,
+
+ /// Factor will be the alpha component of the constant color.
+ ConstantAlpha,
+
+ /// Factor will be the RGBA color `(1,1,1,1) - constant color alpha`
+ OneMinusConstantAlpha,
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum Equation
+{
+ /// The destination color and source color is added to each other in the blend
+ /// function
+ #[default]
+ Add,
+
+ /// The destination color is subtracted from the source color in the blend function
+ Subtract,
+
+ /// The source color is subtracted from the destination color in the blend function
+ ReverseSubtract,
+
+ /// The blend function will take the component-wise minimum of the destination color
+ /// and the source color
+ Min,
+
+ /// The blend function will take the component-wise maximum of the destination color
+ /// and the source color
+ Max,
+}
diff --git a/engine/src/renderer/main_render_pass.rs b/engine/src/renderer/main_render_pass.rs
new file mode 100644
index 0000000..7492379
--- /dev/null
+++ b/engine/src/renderer/main_render_pass.rs
@@ -0,0 +1,241 @@
+use ecs::Query;
+use ecs::query::term::{With, Without};
+use ecs::sole::Single;
+
+use crate::asset::Assets;
+use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig};
+use crate::model::{MaterialSearchResult, Model};
+use crate::renderer::object::{Id as RendererObjectId, Store as RendererObjectStore};
+use crate::renderer::{
+ BufferClearMask as RendererBufferClearMask,
+ Command as RendererCommand,
+ DrawMeshOptions as RendererDrawMeshOptions,
+ DrawProperties as RendererDrawProperties,
+ DrawPropertiesUpdateFlags as RendererDrawPropertiesUpdateFlags,
+ MeshUsage as RendererMeshUsage,
+ PendingShaderBindings,
+ RenderPass,
+ RenderPasses as RendererRenderPasses,
+ SurfaceSpec,
+};
+use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL;
+use crate::shader::{
+ Context as ShaderContext,
+ ModuleSource as ShaderModuleSource,
+ Shader,
+};
+use crate::texture::{Texture, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL};
+use crate::windowing::window::Window;
+
+type RenderableEntity<'a> = (
+ &'a Model,
+ Option<&'a DrawFlags>,
+ Option<&'a Shader>,
+ Option<&'a mut PendingShaderBindings>,
+);
+
+#[tracing::instrument(skip_all)]
+pub fn add_main_render_passes(
+ renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
+ window_surface_spec_query: Query<(&SurfaceSpec,), (With<Window>,)>,
+ assets: Single<Assets>,
+ shader_context: Single<ShaderContext>,
+ mut render_passes: Single<RendererRenderPasses>,
+ mut object_store: Single<RendererObjectStore>,
+)
+{
+ let Some(default_shader_asset) = assets
+ .get_handle_to_loaded::<ShaderModuleSource>(DEFAULT_SHADER_ASSET_LABEL.clone())
+ else {
+ tracing::error!("Default shader asset is not loaded");
+ return;
+ };
+
+ for (surface_spec,) in &window_surface_spec_query {
+ let render_pass = render_passes.passes.push_front_mut(RenderPass {
+ surface_id: surface_spec.id,
+ commands: Vec::with_capacity(30),
+ draw_properties: RendererDrawProperties::default(),
+ });
+
+ let default_texture_asset = assets
+ .get_handle_to_loaded::<Texture>(TEXTURE_WHITE_1X1_ASSET_LABEL.clone())
+ .expect("Not possible");
+
+ if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset(
+ default_texture_asset.id(),
+ )) {
+ object_store
+ .insert_pending(RendererObjectId::Asset(default_texture_asset.id()));
+
+ render_pass
+ .commands
+ .push(RendererCommand::CreateTexture(default_texture_asset));
+ }
+
+ render_pass.commands.push(RendererCommand::ClearBuffers(
+ RendererBufferClearMask::COLOR | RendererBufferClearMask::DEPTH,
+ ));
+
+ for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query
+ {
+ let shader_asset = match &shader {
+ Some(shader) => &shader.asset_handle,
+ None => &default_shader_asset,
+ };
+
+ let Some(pending_shader_bindings) = pending_shader_bindings.as_mut() else {
+ continue;
+ };
+
+ if pending_shader_bindings.bindings.is_empty()
+ && pending_shader_bindings.surface_specific_bindings.is_empty()
+ {
+ continue;
+ }
+
+ let Some(model_spec) = assets.get(&model.spec_asset) else {
+ continue;
+ };
+
+ let Some(mesh_asset) = &model_spec.mesh_asset else {
+ continue;
+ };
+
+ if assets.get(mesh_asset).is_none() {
+ continue;
+ }
+
+ debug_assert!(model_spec.material_names.len() <= 1);
+
+ let model_material_asset = match model_spec.find_first_material(&assets) {
+ MaterialSearchResult::Found(model_material_asset) => {
+ model_material_asset.clone()
+ // Some(model_material_asset.clone())
+ }
+ MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => {
+ // MaterialSearchResult::NotFound => {
+ continue;
+ } // MaterialSearchResult::NoMaterials => None,
+ };
+
+ if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset(
+ shader_asset.id(),
+ )) {
+ let Some(shader_program) = shader_context.get_program(&shader_asset.id())
+ else {
+ tracing::error!(
+ "Shader context doesn't have a program for shader asset {:?}",
+ assets.get_label(&shader_asset)
+ );
+ continue;
+ };
+
+ object_store.insert_pending(RendererObjectId::Asset(shader_asset.id()));
+
+ render_pass
+ .commands
+ .push(RendererCommand::CreateShaderProgram(
+ RendererObjectId::Asset(shader_asset.id()),
+ shader_program.clone(),
+ ));
+ }
+
+ render_pass.commands.push(RendererCommand::ActivateShader(
+ RendererObjectId::Asset(shader_asset.id()),
+ ));
+
+ let Some(model_material) = assets.get(&model_material_asset) else {
+ // TODO: Handle this case since it may occur
+ unreachable!();
+ };
+
+ for texture_asset in [
+ &model_material.ambient_map,
+ &model_material.diffuse_map,
+ &model_material.specular_map,
+ ]
+ .into_iter()
+ .flatten()
+ {
+ if !object_store.contains_maybe_pending_with_id(&RendererObjectId::Asset(
+ texture_asset.id(),
+ )) {
+ object_store
+ .insert_pending(RendererObjectId::Asset(texture_asset.id()));
+
+ render_pass
+ .commands
+ .push(RendererCommand::CreateTexture(texture_asset.clone()));
+ }
+ }
+
+ for (shader_binding_loc, shader_binding_val) in
+ &pending_shader_bindings.bindings
+ {
+ render_pass.commands.push(RendererCommand::SetShaderBinding(
+ shader_binding_loc.clone(),
+ shader_binding_val.clone(),
+ ));
+ }
+
+ for (shader_binding_surface_id, shader_binding_loc, shader_binding_val) in
+ &pending_shader_bindings.surface_specific_bindings
+ {
+ if *shader_binding_surface_id != surface_spec.id {
+ continue;
+ }
+
+ render_pass.commands.push(RendererCommand::SetShaderBinding(
+ shader_binding_loc.clone(),
+ shader_binding_val.clone(),
+ ));
+ }
+
+ if let Some(draw_flags) = draw_flags.as_deref()
+ && draw_flags.polygon_mode_config != PolygonModeConfig::default()
+ {
+ render_pass
+ .commands
+ .push(RendererCommand::UpdateDrawProperties(
+ RendererDrawProperties {
+ polygon_mode_config: draw_flags.polygon_mode_config.clone(),
+ ..Default::default()
+ },
+ RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG,
+ ));
+ }
+
+ if !object_store
+ .contains_maybe_pending_with_id(&RendererObjectId::Asset(mesh_asset.id()))
+ {
+ object_store.insert_pending(RendererObjectId::Asset(mesh_asset.id()));
+
+ render_pass.commands.push(RendererCommand::CreateMesh {
+ obj_id: RendererObjectId::Asset(mesh_asset.id()),
+ mesh: None,
+ usage: RendererMeshUsage::Static,
+ });
+ }
+
+ render_pass.commands.push(RendererCommand::DrawMesh(
+ RendererObjectId::Asset(mesh_asset.id()),
+ RendererDrawMeshOptions::default(),
+ ));
+
+ if let Some(draw_flags) = draw_flags.as_deref()
+ && draw_flags.polygon_mode_config != PolygonModeConfig::default()
+ {
+ render_pass
+ .commands
+ .push(RendererCommand::UpdateDrawProperties(
+ RendererDrawProperties {
+ polygon_mode_config: PolygonModeConfig::default(),
+ ..Default::default()
+ },
+ RendererDrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG,
+ ));
+ }
+ }
+ }
+}
diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs
new file mode 100644
index 0000000..bdff885
--- /dev/null
+++ b/engine/src/renderer/object.rs
@@ -0,0 +1,137 @@
+use std::collections::HashMap;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+use ecs::Sole;
+
+use crate::asset::Id as AssetId;
+
+pub type RawValue = u32;
+
+/// Renderer object ID.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Id
+{
+ Asset(AssetId),
+ Sequential(SequentialId),
+}
+
+impl Id
+{
+ pub fn new_sequential() -> Self
+ {
+ static NEXT_SEQUENTIAL_ID: AtomicU64 = AtomicU64::new(0);
+
+ Self::Sequential(SequentialId(
+ NEXT_SEQUENTIAL_ID.fetch_add(1, Ordering::Relaxed),
+ ))
+ }
+
+ pub fn into_asset_id(self) -> Option<AssetId>
+ {
+ match self {
+ Self::Asset(asset_id) => Some(asset_id),
+ Self::Sequential(_) => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct SequentialId(u64);
+
+/// Renderer object store.
+#[derive(Debug, Default, Sole)]
+pub struct Store
+{
+ objects: HashMap<Id, Option<Object>>,
+}
+
+impl Store
+{
+ pub fn get_obj(&self, id: &Id) -> Option<&Object>
+ {
+ self.objects.get(id).and_then(|obj| obj.as_ref())
+ }
+
+ pub fn get_texture_obj(&self, id: &Id) -> Option<&Object>
+ {
+ let obj = self.get_obj(id)?;
+
+ if !matches!(obj.kind(), Kind::Texture) {
+ return None;
+ }
+
+ Some(obj)
+ }
+
+ pub fn get_shader_program_obj(&self, id: &Id) -> Option<&Object>
+ {
+ let obj = self.get_obj(id)?;
+
+ if !matches!(obj.kind(), Kind::ShaderProgram) {
+ return None;
+ }
+
+ Some(obj)
+ }
+
+ pub fn contains_maybe_pending_with_id(&self, id: &Id) -> bool
+ {
+ self.objects.contains_key(id)
+ }
+
+ pub fn contains_non_pending_with_id(&self, id: &Id) -> bool
+ {
+ self.objects.get(id).and_then(|obj| obj.as_ref()).is_some()
+ }
+
+ pub fn insert(&mut self, id: Id, object: Object)
+ {
+ self.objects.insert(id, Some(object));
+ }
+
+ pub fn insert_pending(&mut self, id: Id)
+ {
+ self.objects.insert(id, None);
+ }
+
+ pub fn remove(&mut self, id: &Id) -> Option<Option<Object>>
+ {
+ self.objects.remove(id)
+ }
+}
+
+/// Renderer object.
+#[derive(Debug, Clone)]
+pub struct Object
+{
+ raw: RawValue,
+ kind: Kind,
+}
+
+impl Object
+{
+ pub fn from_raw(raw: RawValue, kind: Kind) -> Self
+ {
+ Self { raw, kind }
+ }
+
+ pub fn as_raw(&self) -> RawValue
+ {
+ self.raw
+ }
+
+ pub fn kind(&self) -> Kind
+ {
+ self.kind
+ }
+}
+
+/// Renderer object kind.
+#[derive(Debug, Clone, Copy)]
+#[non_exhaustive]
+pub enum Kind
+{
+ Texture,
+ ShaderProgram,
+ ImplementationSpecific,
+}
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index 5665860..378a89d 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -1,685 +1,1338 @@
//! OpenGL renderer.
+use std::borrow::Cow;
use std::collections::HashMap;
-use std::ffi::{c_void, CString};
-use std::io::{Error as IoError, ErrorKind as IoErrorKind};
-use std::ops::Deref;
-use std::path::Path;
-use std::process::abort;
use ecs::actions::Actions;
-use ecs::component::local::Local;
-use ecs::phase::{PRESENT as PRESENT_PHASE, START as START_PHASE};
use ecs::query::term::Without;
use ecs::sole::Single;
-use ecs::system::{Into as _, System};
-use ecs::{Component, Query};
-
-use crate::camera::{Active as ActiveCamera, Camera};
-use crate::color::Color;
-use crate::data_types::dimens::Dimens;
-use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig};
-use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
-use crate::material::{Flags as MaterialFlags, Material};
-use crate::matrix::Matrix;
-use crate::mesh::Mesh;
-use crate::opengl::buffer::{Buffer, Usage as BufferUsage};
-use crate::opengl::debug::{
- enable_debug_output,
- set_debug_message_callback,
- set_debug_message_control,
+use ecs::{Component, Query, Sole};
+use glutin::config::Config as GlutinConfig;
+use glutin::display::GetGlDisplay;
+use glutin::error::Error as GlutinError;
+use glutin::prelude::{GlDisplay, PossiblyCurrentGlContext};
+use glutin::surface::{
+ GlSurface as _,
+ Surface as GlutinSurface,
+ WindowSurface as GlutinWindowSurface,
+};
+use opengl_bindings::blending::{
+ Configuration as GlBlendingConfig,
+ Equation as GlBlendingEquation,
+ Factor as GlBlendingFactor,
+ configure as gl_blending_configure,
+};
+use opengl_bindings::debug::{
MessageIdsAction,
MessageSeverity,
MessageSource,
MessageType,
+ SetDebugMessageControlError as GlSetDebugMessageControlError,
+ set_debug_message_callback,
+ set_debug_message_control,
};
-use crate::opengl::glsl::{
- preprocess as glsl_preprocess,
- PreprocessingError as GlslPreprocessingError,
+use opengl_bindings::misc::{
+ BufferClearMask as GlBufferClearMask,
+ Capability,
+ clear_buffers,
+ define_scissor_box as gl_define_scissor_box,
+ enable,
+ get_viewport as gl_get_viewport,
+ set_enabled,
+ set_viewport as gl_set_viewport,
};
-use crate::opengl::shader::{
+use opengl_bindings::shader::{
Error as GlShaderError,
Kind as ShaderKind,
Program as GlShaderProgram,
Shader as GlShader,
+ // UniformLocation as GlUniformLocation,
};
-use crate::opengl::texture::{
- set_active_texture_unit,
+use opengl_bindings::texture::{
+ ColorSpace as GlTextureColorSpace,
+ Filtering as GlTextureFiltering,
+ GenerateError as GlTextureGenerateError,
+ PixelDataFormat as GlTexturePixelDataFormat,
Texture as GlTexture,
- TextureUnit,
+ Wrapping as GlTextureWrapping,
};
-use crate::opengl::vertex_array::{
- DataType as VertexArrayDataType,
+use opengl_bindings::vertex_array::{
+ DrawError as GlDrawError,
PrimitiveKind,
VertexArray,
};
-use crate::opengl::{
- clear_buffers,
- enable,
- get_context_flags as get_opengl_context_flags,
+use opengl_bindings::{
+ ContextWithFns,
+ CurrentContextWithFns,
+ MakeContextCurrentError as GlMakeContextCurrentError,
+};
+use raw_window_handle::WindowHandle;
+use safer_ffi::layout::ReprC;
+use zerocopy::{Immutable, IntoBytes};
+
+use crate::asset::{Assets, Handle as AssetHandle};
+use crate::data_types::dimens::Dimens;
+use crate::image::{ColorType as ImageColorType, Image};
+use crate::matrix::Matrix;
+use crate::reflection::EnumReflectionExt;
+use crate::renderer::blending::{Equation as BlendingEquation, Factor as BlendingFactor};
+use crate::renderer::object::{
+ Id as RendererObjectId,
+ Kind as RendererObjectKind,
+ Object as RendererObject,
+ RawValue as RendererObjectRawValue,
+ Store as RendererObjectStore,
+};
+use crate::renderer::opengl::glutin_compat::{
+ DisplayBuilder,
+ Error as GlutinCompatError,
+};
+use crate::renderer::opengl::graphics_mesh::GraphicsMesh;
+use crate::renderer::{
BufferClearMask,
- Capability,
- ContextFlags,
+ Command as RendererCommand,
+ CommandQueue as RendererCommandQueue,
+ DrawMeshOptions,
+ DrawPropertiesUpdateFlags,
+ GraphicsProperties,
+ POST_RENDER_PHASE,
+ RENDER_PHASE,
+ SurfaceId,
+ SurfaceSpec,
+};
+use crate::shader::cursor::BindingValue as ShaderBindingValue;
+use crate::shader::{
+ Context as ShaderContext,
+ Error as ShaderError,
+ Program as ShaderProgram,
+ Stage as ShaderStage,
};
-use crate::projection::{ClipVolume, Projection};
-use crate::texture::{Id as TextureId, Texture};
-use crate::transform::{Position, Scale};
-use crate::util::{defer, Defer, RefOrValue};
+use crate::texture::{
+ Filtering as TextureFiltering,
+ Properties as TextureProperties,
+ Texture,
+ Wrapping as TextureWrapping,
+};
+use crate::util::OptionExt;
use crate::vector::{Vec2, Vec3};
-use crate::vertex::{AttributeComponentType, Vertex};
-use crate::window::Window;
-
-type RenderableEntity<'a> = (
- &'a Mesh,
- &'a Material,
- &'a Option<MaterialFlags>,
- &'a Option<Position>,
- &'a Option<Scale>,
- &'a Option<DrawFlags>,
- &'a Option<GlObjects>,
-);
+use crate::windowing::Context as WindowingContext;
+use crate::windowing::window::{
+ Closed as WindowClosed,
+ CreationAttributes as WindowCreationAttributes,
+ CreationReady,
+ Window,
+};
-#[derive(Debug, Default)]
-#[non_exhaustive]
-pub struct Extension {}
+mod glutin_compat;
+mod graphics_mesh;
-impl ecs::extension::Extension for Extension
+#[derive(Debug, Component)]
+struct WindowGlConfig
{
- fn collect(self, mut collector: ecs::extension::Collector<'_>)
- {
- collector.add_system(*START_PHASE, initialize);
-
- collector.add_system(
- *PRESENT_PHASE,
- render
- .into_system()
- .initialize((GlobalGlObjects::default(),)),
- );
- }
+ gl_config: GlutinConfig,
}
-fn initialize(window: Single<Window>)
+#[derive(Sole, Default)]
+struct GraphicsContext
{
- window
- .make_context_current()
- .expect("Failed to make window context current");
-
- gl::load_with(|symbol| match window.get_proc_address(symbol) {
- Ok(addr) => addr as *const c_void,
- Err(err) => {
- println!(
- "FATAL ERROR: Failed to get adress of OpenGL function {symbol}: {err}",
- );
+ gl_context: Option<ContextWithFns>,
+ surfaces: HashMap<SurfaceId, GraphicsContextSurface>,
+ shader_uniform_buffer_objs:
+ HashMap<RendererObjectId, HashMap<u32, opengl_bindings::buffer::Buffer<u8>>>,
+ objects: HashMap<RendererObjectRawValue, GraphicsContextObject>,
+ next_object_key: RendererObjectRawValue,
+}
- abort();
- }
- });
+#[derive(Debug)]
+struct GraphicsContextSurface
+{
+ window_surface: GlutinSurface<GlutinWindowSurface>,
+ size: Dimens<u32>,
+}
- if get_opengl_context_flags().contains(ContextFlags::DEBUG) {
- initialize_debug();
- }
+#[derive(Debug)]
+enum GraphicsContextObject
+{
+ Mesh
+ {
+ mesh: GraphicsMesh,
+ compatible_shader_program_obj_id: RendererObjectId,
+ },
+}
- let window_size = window.size().expect("Failed to get window size");
+#[derive(Debug, Default)]
+#[non_exhaustive]
+pub struct Extension {}
- set_viewport(Vec2 { x: 0, y: 0 }, window_size);
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ collector.add_system(*RENDER_PHASE, handle_commands);
- window.set_framebuffer_size_callback(|new_window_size| {
- set_viewport(Vec2::ZERO, new_window_size);
- });
+ collector.add_system(*POST_RENDER_PHASE, prepare_windows);
+ collector.add_system(*POST_RENDER_PHASE, init_window_graphics);
- enable(Capability::DepthTest);
- enable(Capability::MultiSample);
+ let _ = collector.add_sole(GraphicsContext::default());
+ }
}
-#[allow(clippy::too_many_arguments)]
-fn render(
- query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
- point_light_query: Query<(&PointLight,)>,
- directional_lights: Query<(&DirectionalLight,)>,
- camera_query: Query<(&Camera, &Position, &ActiveCamera)>,
- window: Single<Window>,
- global_light: Single<GlobalLight>,
- mut gl_objects: Local<GlobalGlObjects>,
+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((camera, camera_pos, _)) = camera_query.iter().next() else {
- tracing::warn!("No current camera. Nothing will be rendered");
+ let Some(display_handle) = windowing_context.display_handle() else {
return;
};
- let point_lights = point_light_query
- .iter()
- .map(|(point_light,)| point_light)
- .collect::<Vec<_>>();
-
- let directional_lights = directional_lights.iter().collect::<Vec<_>>();
-
- let GlobalGlObjects {
- shader_program,
- textures: gl_textures,
- } = &mut *gl_objects;
-
- let shader_program =
- shader_program.get_or_insert_with(|| create_default_shader_program().unwrap());
-
- clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH);
-
- for (
- euid,
- (mesh, material, material_flags, position, scale, draw_flags, gl_objects),
- ) in query.iter_with_euids()
+ for (window_ent_id, (window, mut window_creation_attrs)) in
+ window_query.iter_with_euids()
{
- let material_flags = material_flags
- .map(|material_flags| material_flags.clone())
- .unwrap_or_default();
-
- let gl_objs = match gl_objects.as_deref() {
- Some(gl_objs) => RefOrValue::Ref(gl_objs),
- None => RefOrValue::Value(Some(GlObjects::new(&mesh))),
- };
-
- defer!(|gl_objs| {
- if let RefOrValue::Value(opt_gl_objs) = gl_objs {
- actions.add_components(euid, (opt_gl_objs.take().unwrap(),));
- };
- });
-
- apply_transformation_matrices(
- Transformation {
- position: position.map(|pos| *pos).unwrap_or_default().position,
- scale: scale.map(|scale| *scale).unwrap_or_default().scale,
- },
- shader_program,
- &camera,
- &camera_pos,
- window.size().expect("Failed to get window size"),
- );
+ tracing::debug!("Preparing window entity {window_ent_id} for use in rendering");
- apply_light(
- &material,
- &material_flags,
- &global_light,
- shader_program,
- point_lights.as_slice(),
- directional_lights
- .iter()
- .map(|(dir_light,)| &**dir_light)
- .collect::<Vec<_>>()
- .as_slice(),
- &camera_pos,
- );
-
- for (index, texture) in material.textures.iter().enumerate() {
- let gl_texture = gl_textures
- .entry(texture.id())
- .or_insert_with(|| create_gl_texture(texture));
-
- let texture_unit = TextureUnit::from_num(index).expect("Too many textures");
+ let mut glutin_config_template_builder =
+ glutin::config::ConfigTemplateBuilder::new();
- set_active_texture_unit(texture_unit);
-
- gl_texture.bind();
+ if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt {
+ glutin_config_template_builder = glutin_config_template_builder
+ .with_multisampling(multisampling_sample_cnt);
}
- shader_program.activate();
+ 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;
+ }
+ };
- if let Some(draw_flags) = &draw_flags {
- crate::opengl::set_polygon_mode(
- draw_flags.polygon_mode_config.face,
- draw_flags.polygon_mode_config.mode,
- );
- }
+ 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;
+ }
+ };
- draw_mesh(gl_objs.get().unwrap());
+ *window_creation_attrs = new_window_creation_attrs;
- if draw_flags.is_some() {
- let default_polygon_mode_config = PolygonModeConfig::default();
+ actions.add_components(window_ent_id, (WindowGlConfig { gl_config },));
- crate::opengl::set_polygon_mode(
- default_polygon_mode_config.face,
- default_polygon_mode_config.mode,
- );
+ if window.is_none() {
+ actions.add_components(window_ent_id, (CreationReady,));
}
}
}
-#[derive(Debug, Default, Component)]
-struct GlobalGlObjects
-{
- shader_program: Option<GlShaderProgram>,
- textures: HashMap<TextureId, GlTexture>,
-}
-
-fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
-{
- crate::opengl::set_viewport(position, size);
-}
-
-fn initialize_debug()
+#[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,
+)
{
- enable_debug_output();
-
- set_debug_message_callback(opengl_debug_message_cb);
- set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable);
-}
+ 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"
+ );
-fn draw_mesh(gl_objects: &GlObjects)
-{
- gl_objects.vertex_arr.bind();
+ 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;
+ }
+ };
- if gl_objects.index_buffer.is_some() {
- VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt);
- } else {
- VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt);
- }
-}
+ 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;
+ };
-fn create_gl_texture(texture: &Texture) -> GlTexture
-{
- let mut gl_texture = GlTexture::new();
+ 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;
+ }
+ };
- gl_texture.generate(
- *texture.dimensions(),
- texture.image().as_bytes(),
- texture.pixel_data_format(),
- );
+ 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;
+ }
+ };
- gl_texture.apply_properties(texture.properties());
+ let Ok(curr_gl_context) = gl_context.make_current(&window_surface) else {
+ tracing::error!("Failed to make GL context current");
+ continue;
+ };
- gl_texture
-}
+ if let Err(err) = gl_set_viewport(
+ &curr_gl_context,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &window.inner_size().clone().into(),
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
-const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl");
-const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl");
+ set_enabled(
+ &curr_gl_context,
+ Capability::DepthTest,
+ graphics_props.depth_test,
+ );
-const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl");
-const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl");
+ set_enabled(
+ &curr_gl_context,
+ Capability::MultiSample,
+ graphics_props.multisampling_sample_cnt.is_some(),
+ );
-fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError>
-{
- let mut vertex_shader = GlShader::new(ShaderKind::Vertex);
+ if graphics_props.debug {
+ enable(&curr_gl_context, Capability::DebugOutput);
+ enable(&curr_gl_context, Capability::DebugOutputSynchronous);
+
+ set_debug_message_callback(&curr_gl_context, opengl_debug_message_cb);
+
+ match set_debug_message_control(
+ &curr_gl_context,
+ None,
+ None,
+ None,
+ &[],
+ MessageIdsAction::Disable,
+ ) {
+ Ok(()) => {}
+ Err(GlSetDebugMessageControlError::TooManyIds {
+ id_cnt: _,
+ max_id_cnt: _,
+ }) => {
+ unreachable!() // No ids are given
+ }
+ }
+ }
- vertex_shader.set_source(&*glsl_preprocess(
- VERTEX_GLSL_SHADER_SRC,
- &get_glsl_shader_content,
- )?)?;
+ let surface_id = SurfaceId::new_unique();
- vertex_shader.compile()?;
+ actions.add_components(window_ent_id, (SurfaceSpec { id: surface_id },));
- let mut fragment_shader = GlShader::new(ShaderKind::Fragment);
+ graphics_ctx.surfaces.insert(
+ surface_id,
+ GraphicsContextSurface {
+ window_surface,
+ size: window.inner_size().clone(),
+ },
+ );
+ }
+}
- fragment_shader.set_source(&*glsl_preprocess(
- FRAGMENT_GLSL_SHADER_SRC,
- &get_glsl_shader_content,
- )?)?;
+#[tracing::instrument(skip_all)]
+fn handle_commands(
+ mut graphics_ctx: Single<GraphicsContext>,
+ mut object_store: Single<RendererObjectStore>,
+ mut command_queue: Single<RendererCommandQueue>,
+ 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;
+ };
- fragment_shader.compile()?;
+ let mut opt_curr_gl_ctx: Option<CurrentContextWithFns> = None;
- let mut gl_shader_program = GlShaderProgram::new();
+ let mut activated_gl_shader_program: Option<(RendererObjectId, GlShaderProgram)> =
+ None;
- gl_shader_program.attach(&vertex_shader);
- gl_shader_program.attach(&fragment_shader);
+ 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();
- gl_shader_program.link()?;
+ match command {
+ RendererCommand::RemoveSurface(surface_id) => {
+ let Some(surface) = surfaces.remove(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
- Ok(gl_shader_program)
-}
+ if surface.window_surface.is_current(gl_context.context()) {
+ opt_curr_gl_ctx = None;
-#[derive(Debug, thiserror::Error)]
-enum CreateShaderError
-{
- #[error(transparent)]
- ShaderError(#[from] GlShaderError),
+ if let Err(err) = gl_context.context().make_not_current_in_place() {
+ tracing::error!("Failed to make GL context not current: {err}");
+ }
+ }
- #[error(transparent)]
- PreprocessingError(#[from] GlslPreprocessingError),
+ drop(surface);
+ }
+ RendererCommand::MakeCurrent(surface_id) => {
+ let Some(surface) = surfaces.get(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ let curr_gl_ctx = match gl_context.make_current(&surface.window_surface) {
+ Ok(current_graphics_context) => current_graphics_context,
+ Err(err) => {
+ tracing::error!("Failed to make graphics context current: {err}");
+ continue;
+ }
+ };
+
+ if let Err(err) = gl_set_viewport(
+ &curr_gl_ctx,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &surface.size.into(),
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+
+ opt_curr_gl_ctx = Some(curr_gl_ctx);
+ }
+ RendererCommand::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;
+
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ continue;
+ };
+
+ if !surface.window_surface.is_current(gl_context.context()) {
+ continue;
+ }
+
+ if let Err(err) = gl_set_viewport(
+ &curr_gl_ctx,
+ &Vec2 { x: 0, y: 0 }.into(),
+ &surface.size.into(),
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+ }
+ RendererCommand::ClearBuffers(buffer_clear_mask) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let mut clear_mask = GlBufferClearMask::empty();
+
+ clear_mask.set(
+ GlBufferClearMask::COLOR,
+ buffer_clear_mask.contains(BufferClearMask::COLOR),
+ );
+
+ clear_mask.set(
+ GlBufferClearMask::DEPTH,
+ buffer_clear_mask.contains(BufferClearMask::DEPTH),
+ );
+
+ clear_mask.set(
+ GlBufferClearMask::STENCIL,
+ buffer_clear_mask.contains(BufferClearMask::STENCIL),
+ );
+
+ clear_buffers(&curr_gl_ctx, clear_mask);
+ }
+ RendererCommand::SwapBuffers(surface_id) => {
+ let Some(surface) = surfaces.get(&surface_id) else {
+ tracing::error!(surface_id=?surface_id, "Surface does not exist");
+ continue;
+ };
+
+ if let Err(err) =
+ surface.window_surface.swap_buffers(gl_context.context())
+ {
+ tracing::error!("Failed to swap buffers: {err}");
+ }
+ }
+ RendererCommand::CreateShaderProgram(
+ shader_program_obj_id,
+ shader_program,
+ ) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ if object_store.contains_non_pending_with_id(&shader_program_obj_id) {
+ tracing::error!(
+ shader_program_object_id=?shader_program_obj_id,
+ shader_asset_label=?shader_program_obj_id
+ .into_asset_id()
+ .and_then(|asset_id| assets.get_label_by_id(asset_id)),
+ "Object store already contains a object with this ID"
+ );
+ continue;
+ }
+
+ let gl_shader_program =
+ match create_shader_program(&curr_gl_ctx, &shader_program) {
+ Ok(gl_shader_program) => gl_shader_program,
+ Err(err) => {
+ tracing::error!("Failed to create shader program: {err}");
+ continue;
+ }
+ };
+
+ object_store.insert(
+ shader_program_obj_id,
+ RendererObject::from_raw(
+ gl_shader_program.into_raw(),
+ RendererObjectKind::ShaderProgram,
+ ),
+ );
+ }
+ RendererCommand::ActivateShader(shader_program_obj_id) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let Some(shader_program_obj) =
+ object_store.get_shader_program_obj(&shader_program_obj_id)
+ else {
+ tracing::error!("Shader object does not exist or has a wrong kind");
+ continue;
+ };
+
+ let gl_shader_program =
+ GlShaderProgram::from_raw(shader_program_obj.as_raw());
+
+ gl_shader_program.activate(&curr_gl_ctx);
+
+ activated_gl_shader_program =
+ Some((shader_program_obj_id, gl_shader_program));
+ }
+ RendererCommand::SetShaderBinding(binding_location, binding_value) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let Some((activated_gl_shader_program_obj_id, _)) =
+ &activated_gl_shader_program
+ else {
+ tracing::error!("No shader program is activated");
+ continue;
+ };
+
+ if let ShaderBindingValue::Texture(texture_asset) = &binding_value {
+ let Some(texture_obj) = object_store
+ .get_texture_obj(&RendererObjectId::Asset(texture_asset.id()))
+ else {
+ tracing::error!(
+ "Texture {:?} does not exist in renderer object store",
+ assets.get_label(texture_asset)
+ );
+ continue;
+ };
+
+ let gl_texture = GlTexture::from_raw(texture_obj.as_raw());
+
+ gl_texture.bind_to_texture_unit(
+ curr_gl_ctx,
+ binding_location.binding_index,
+ );
+
+ // gl_shader_program.set_uniform_at_location(
+ // curr_gl_ctx,
+ // GlUniformLocation::from_number(
+ // binding_location.binding_index as i32,
+ // ),
+ // &binding_location.binding_index,
+ // );
+
+ continue;
+ }
+
+ let binding_index = binding_location.binding_index;
+
+ let uniform_buffer_objs = shader_uniform_buffer_objs
+ .entry(*activated_gl_shader_program_obj_id)
+ .or_default();
+
+ let uniform_buffer =
+ uniform_buffer_objs.entry(binding_index).or_insert_with(|| {
+ let uniform_buf =
+ opengl_bindings::buffer::Buffer::<u8>::new(curr_gl_ctx);
+
+ uniform_buf
+ .init(
+ curr_gl_ctx,
+ binding_location.binding_size,
+ opengl_bindings::buffer::Usage::Dynamic,
+ )
+ .unwrap();
+
+ uniform_buf
+ });
+
+ // The index into the uniform buffer binding target is for whatever
+ // shader program is currently bound so the uniform buffer object has
+ // to be re-bound so that a uniform buffer from another shader isn't
+ // used
+ uniform_buffer.bind_to_indexed_target(
+ curr_gl_ctx,
+ opengl_bindings::buffer::BindingTarget::UniformBuffer,
+ binding_index as u32,
+ );
+
+ let fvec3_value;
+
+ uniform_buffer
+ .store_at_byte_offset(
+ curr_gl_ctx,
+ binding_location.byte_offset,
+ match binding_value {
+ ShaderBindingValue::Uint(ref value) => value.as_bytes(),
+ ShaderBindingValue::Int(ref value) => value.as_bytes(),
+ ShaderBindingValue::Float(ref value) => value.as_bytes(),
+ ShaderBindingValue::FVec3(value) => {
+ fvec3_value = CF32Vec3::from(value);
+
+ fvec3_value.as_bytes()
+ }
+ ShaderBindingValue::FMat4x4(ref value) => {
+ value.items().as_bytes()
+ }
+ ShaderBindingValue::Texture(_) => unreachable!(),
+ },
+ )
+ .unwrap();
+ }
+ RendererCommand::CreateTexture(texture_asset) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ if let Err(err) = create_texture_object(
+ curr_gl_ctx,
+ &mut object_store,
+ &assets,
+ &texture_asset,
+ ) {
+ tracing::error!("Failed to create texture object: {err}");
+ }
+ }
+ RendererCommand::CreateMesh {
+ obj_id: mesh_object_id,
+ mesh,
+ usage: mesh_usage,
+ } => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ if object_store.contains_non_pending_with_id(&mesh_object_id) {
+ tracing::error!(
+ mesh_object_id=?mesh_object_id,
+ mesh_asset_label=?mesh_object_id
+ .into_asset_id()
+ .and_then(|asset_id| assets.get_label_by_id(asset_id)),
+ "Object store already contains a object with this ID"
+ );
+ continue;
+ }
+
+ let Some((RendererObjectId::Asset(curr_shader_program_asset_id), _)) =
+ &activated_gl_shader_program
+ else {
+ tracing::error!("No shader program is activated");
+ continue;
+ };
+
+ let curr_shader_program_metadata = shader_context
+ .get_program_metadata(curr_shader_program_asset_id)
+ .expect("Not possible");
+
+ let Some(vertex_desc) = &curr_shader_program_metadata.vertex_desc else {
+ tracing::error!(
+ "Current shader program does not have a vertex description"
+ );
+ continue;
+ };
+
+ let key = *next_graphics_ctx_object_key;
+
+ let mesh = match mesh_object_id {
+ RendererObjectId::Asset(mesh_asset_id) => match mesh.as_ref() {
+ Some(mesh) => mesh,
+ None => {
+ let Some(mesh) =
+ assets.get(&AssetHandle::from_id(mesh_asset_id))
+ else {
+ tracing::error!(
+ asset_id=?mesh_asset_id,
+ "Mesh asset does not exist"
+ );
+ continue;
+ };
+
+ mesh
+ }
+ },
+ RendererObjectId::Sequential(_) => {
+ let Some(mesh) = mesh.as_ref() else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ "Object ID is sequential but no mesh data is given"
+ );
+ continue;
+ };
+
+ mesh
+ }
+ };
+
+ let graphics_mesh = match GraphicsMesh::new(
+ &curr_gl_ctx,
+ &mesh,
+ mesh_usage,
+ &vertex_desc,
+ ) {
+ Ok(graphics_mesh) => graphics_mesh,
+ Err(err) => {
+ tracing::error!("Failed to create mesh: {err}");
+ continue;
+ }
+ };
+
+ graphics_ctx_objects.insert(
+ key,
+ GraphicsContextObject::Mesh {
+ mesh: graphics_mesh,
+ compatible_shader_program_obj_id: RendererObjectId::Asset(
+ *curr_shader_program_asset_id,
+ ),
+ },
+ );
+
+ object_store.insert(
+ mesh_object_id,
+ RendererObject::from_raw(
+ key,
+ RendererObjectKind::ImplementationSpecific,
+ ),
+ );
+
+ *next_graphics_ctx_object_key += 1;
+ }
+ RendererCommand::UpdateMesh {
+ obj_id: mesh_object_id,
+ mesh,
+ usage: mesh_usage,
+ } => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj_key) = object_store
+ .get_obj(&mesh_object_id)
+ .map(|obj| obj.as_raw())
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ "Object store does not contain a mesh object with this ID"
+ );
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj) =
+ graphics_ctx_objects.get_mut(&mesh_graphics_ctx_obj_key)
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context does not contain a mesh object with this key"
+ );
+ continue;
+ };
+
+ #[allow(irrefutable_let_patterns)]
+ let GraphicsContextObject::Mesh {
+ mesh: graphics_mesh,
+ compatible_shader_program_obj_id: _,
+ } = mesh_graphics_ctx_obj
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context object with this key is not a mesh"
+ );
+ continue;
+ };
+
+ if let Err(err) = graphics_mesh.update(curr_gl_ctx, &mesh, mesh_usage) {
+ tracing::error!("Failed to update mesh: {err}");
+ }
+ }
+ RendererCommand::RemoveMesh(mesh_object_id) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj_key) = object_store
+ .remove(&mesh_object_id)
+ .flatten()
+ .map(|obj| obj.as_raw())
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ "Object store does not contain a mesh object with this ID"
+ );
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj) =
+ graphics_ctx_objects.remove(&mesh_graphics_ctx_obj_key)
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context does not contain a mesh object with this key"
+ );
+ continue;
+ };
+
+ #[allow(irrefutable_let_patterns)]
+ let GraphicsContextObject::Mesh { mesh: mut graphics_mesh, .. } =
+ mesh_graphics_ctx_obj
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context object with this key is not a mesh"
+ );
+ continue;
+ };
+
+ graphics_mesh.destroy(curr_gl_ctx);
+ }
+ RendererCommand::DrawMesh(mesh_object_id, draw_mesh_opts) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj_key) = object_store
+ .get_obj(&mesh_object_id)
+ .map(|obj| obj.as_raw())
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ "Object store does not contain a mesh object with this ID"
+ );
+ continue;
+ };
+
+ let Some(mesh_graphics_ctx_obj) =
+ graphics_ctx_objects.get(&mesh_graphics_ctx_obj_key)
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context does not contain a mesh object with this key"
+ );
+ continue;
+ };
+
+ #[allow(irrefutable_let_patterns)]
+ let GraphicsContextObject::Mesh {
+ mesh: graphics_mesh,
+ compatible_shader_program_obj_id,
+ } = mesh_graphics_ctx_obj
+ else {
+ tracing::error!(
+ object_id=?mesh_object_id,
+ key=mesh_graphics_ctx_obj_key,
+ "Graphics context object with this key is not a mesh"
+ );
+ continue;
+ };
+
+ if Some(compatible_shader_program_obj_id)
+ != activated_gl_shader_program.as_ref().map(
+ |(activated_gl_shader_program_obj_id, _)| {
+ activated_gl_shader_program_obj_id
+ },
+ )
+ {
+ tracing::error!(concat!(
+ "Activated shader program is not the ",
+ "compatible shader program of the mesh"
+ ));
+ continue;
+ }
+
+ if let Err(err) = draw_mesh(&curr_gl_ctx, graphics_mesh, &draw_mesh_opts)
+ {
+ tracing::error!("Failed to draw mesh: {err}");
+ };
+ }
+ RendererCommand::UpdateDrawProperties(
+ draw_props,
+ draw_props_update_flags,
+ ) => {
+ let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
+ tracing::error!("No GL context is current");
+ continue;
+ };
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG)
+ {
+ opengl_bindings::misc::set_polygon_mode(
+ &curr_gl_ctx,
+ draw_props.polygon_mode_config.face,
+ draw_props.polygon_mode_config.mode,
+ );
+ }
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::BLENDING_ENABLED)
+ {
+ set_enabled(
+ curr_gl_ctx,
+ Capability::Blend,
+ draw_props.blending_enabled,
+ );
+ }
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::BLENDING_CONFIG)
+ {
+ gl_blending_configure(
+ curr_gl_ctx,
+ GlBlendingConfig::default()
+ .with_source_factor(blending_factor_to_gl(
+ draw_props.blending_config.source_factor,
+ ))
+ .with_destination_factor(blending_factor_to_gl(
+ draw_props.blending_config.destination_factor,
+ ))
+ .with_equation(blending_equation_to_gl(
+ draw_props.blending_config.equation,
+ )),
+ );
+ }
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::DEPTH_TEST_ENABLED)
+ {
+ set_enabled(
+ curr_gl_ctx,
+ Capability::DepthTest,
+ draw_props.depth_test_enabled,
+ );
+ }
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::SCISSOR_TEST_ENABLED)
+ {
+ set_enabled(
+ curr_gl_ctx,
+ Capability::ScissorTest,
+ draw_props.scissor_test_enabled,
+ );
+ }
+
+ if draw_props_update_flags
+ .contains(DrawPropertiesUpdateFlags::SCISSOR_BOX)
+ {
+ gl_define_scissor_box(
+ curr_gl_ctx,
+ draw_props.scissor_box.lower_left_corner_pos.into(),
+ draw_props
+ .scissor_box
+ .size
+ .unwrap_or_else(|| {
+ let (_, viewport_size) = gl_get_viewport(curr_gl_ctx);
+
+ Dimens::<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(
+ curr_gl_ctx,
+ Capability::CullFace,
+ draw_props.face_culling_enabled,
+ );
+ }
+ }
+ }
+ }
}
-fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error>
+fn create_gl_context(
+ gl_config: &GlutinConfig,
+ graphics_props: &GraphicsProperties,
+ window_handle: WindowHandle<'_>,
+ surface: &GlutinSurface<GlutinWindowSurface>,
+) -> Result<ContextWithFns, CreateGlContextError>
{
- if path == Path::new("vertex_data.glsl") {
- return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec());
- }
-
- if path == Path::new("light.glsl") {
- return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec());
+ 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)?;
- Err(IoError::new(
- IoErrorKind::NotFound,
- format!("Content for shader file {} not found", path.display()),
- ))
+ ContextWithFns::new(glutin_context, &surface)
+ .map_err(CreateGlContextError::MakeContextCurrent)
}
-#[derive(Debug, Component)]
-struct GlObjects
+#[derive(Debug, thiserror::Error)]
+enum CreateGlContextError
{
- /// Vertex and index buffer has to live as long as the vertex array
- _vertex_buffer: Buffer<Vertex>,
- index_buffer: Option<Buffer<u32>>,
- element_cnt: u32,
+ #[error("Glutin context creation failed")]
+ CreateGlutinContext(#[source] GlutinError),
- vertex_arr: VertexArray,
+ #[error("Making GL context current failed")]
+ MakeContextCurrent(#[source] GlMakeContextCurrentError),
}
-impl GlObjects
+#[tracing::instrument(skip_all)]
+fn create_texture_object(
+ curr_gl_ctx: &CurrentContextWithFns<'_>,
+ renderer_object_store: &mut RendererObjectStore,
+ assets: &Assets,
+ texture_asset: &AssetHandle<Texture>,
+) -> Result<(), GlTextureGenerateError>
{
- #[tracing::instrument(skip_all)]
- fn new(mesh: &Mesh) -> Self
- {
- tracing::trace!(
- "Creating vertex array, vertex buffer{}",
- if mesh.indices().is_some() {
- " and index buffer"
- } else {
- ""
- }
- );
-
- let mut vertex_arr = VertexArray::new();
- let mut vertex_buffer = Buffer::new();
-
- vertex_buffer.store(mesh.vertices(), BufferUsage::Static);
+ let object_id = RendererObjectId::Asset(texture_asset.id());
- vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0);
-
- let mut offset = 0u32;
+ if renderer_object_store.contains_non_pending_with_id(&object_id) {
+ tracing::error!(
+ texture_object_id=?object_id,
+ texture_asset_label=?assets.get_label(texture_asset),
+ "Renderer object store already contains object with this ID"
+ );
+ return Ok(());
+ }
- for attrib in Vertex::attrs() {
- vertex_arr.enable_attrib(attrib.index);
+ let Some(texture) = assets.get(&texture_asset) else {
+ tracing::error!("Texture asset is not loaded",);
+ return Ok(());
+ };
- vertex_arr.set_attrib_format(
- attrib.index,
- match attrib.component_type {
- AttributeComponentType::Float => VertexArrayDataType::Float,
- },
- false,
- offset,
+ 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"
+ )
);
- vertex_arr.set_attrib_vertex_buf_binding(attrib.index, 0);
-
- offset += attrib.component_size * attrib.component_cnt as u32;
- }
-
- if let Some(indices) = mesh.indices() {
- let mut index_buffer = Buffer::new();
-
- index_buffer.store(indices, BufferUsage::Static);
-
- vertex_arr.bind_element_buffer(&index_buffer);
-
- return Self {
- _vertex_buffer: vertex_buffer,
- 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,
- };
- }
-
- Self {
- _vertex_buffer: vertex_buffer,
- index_buffer: None,
- element_cnt: mesh
- .vertices()
- .len()
- .try_into()
- .expect("Mesh vertex count does not fit into a 32-bit unsigned int"),
- vertex_arr,
+ &texture.image.to_rgba8()
}
- }
-}
-
-fn apply_transformation_matrices(
- transformation: Transformation,
- gl_shader_program: &mut GlShaderProgram,
- camera: &Camera,
- camera_pos: &Position,
- window_size: Dimens<u32>,
-)
-{
- gl_shader_program
- .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation));
-
- let view_matrix = create_view_matrix(camera, &camera_pos.position);
-
- gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix);
+ _ => &texture.image,
+ };
- #[allow(clippy::cast_precision_loss)]
- let proj_matrix = match &camera.projection {
- Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh(
- window_size.width as f32 / window_size.height as f32,
- ClipVolume::NegOneToOne,
+ renderer_object_store.insert(
+ object_id,
+ RendererObject::from_raw(
+ create_gl_texture(curr_gl_ctx, texture_image, &texture.properties)?
+ .into_raw(),
+ RendererObjectKind::Texture,
),
- Projection::Orthographic(orthographic_proj) => {
- orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne)
- }
- };
+ );
- gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix);
+ Ok(())
}
-fn apply_light<PointLightHolder>(
- material: &Material,
- material_flags: &MaterialFlags,
- global_light: &GlobalLight,
- gl_shader_program: &mut GlShaderProgram,
- point_lights: &[PointLightHolder],
- directional_lights: &[&DirectionalLight],
- camera_pos: &Position,
-) where
- PointLightHolder: Deref<Target = PointLight>,
+fn draw_mesh(
+ current_context: &CurrentContextWithFns<'_>,
+ graphics_mesh: &GraphicsMesh,
+ opts: &DrawMeshOptions,
+) -> Result<(), GlDrawError>
{
- debug_assert!(
- point_lights.len() < 64,
- "Shader cannot handle more than 64 point lights"
- );
-
- debug_assert!(
- directional_lights.len() < 64,
- "Shader cannot handle more than 64 directional lights"
- );
-
- for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
- &create_light_uniform_name(
- "directional_lights",
- dir_light_index,
- "direction",
- ),
- &dir_light.direction,
- );
-
- set_light_phong_uniforms(
- gl_shader_program,
- "directional_lights",
- dir_light_index,
- *dir_light,
- );
+ 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),
+ )?;
}
- // There probably won't be more than 2147483648 directional lights
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program
- .set_uniform_1i(c"directional_light_cnt", directional_lights.len() as i32);
-
- for (point_light_index, point_light) in point_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
- &create_light_uniform_name("point_lights", point_light_index, "position"),
- &point_light.position,
- );
-
- set_light_phong_uniforms(
- gl_shader_program,
- "point_lights",
- point_light_index,
- &**point_light,
- );
-
- set_light_attenuation_uniforms(
- gl_shader_program,
- "point_lights",
- point_light_index,
- point_light,
- );
- }
+ Ok(())
+}
- // There probably won't be more than 2147483648 point lights
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform_1i(c"point_light_cnt", point_lights.len() as i32);
+fn create_gl_texture(
+ current_context: &CurrentContextWithFns<'_>,
+ image: &Image,
+ texture_properties: &TextureProperties,
+) -> Result<GlTexture, GlTextureGenerateError>
+{
+ let gl_texture = GlTexture::new(current_context);
- gl_shader_program.set_uniform_vec_3fv(
- c"material.ambient",
- &if material_flags.use_ambient_color {
- material.ambient.clone()
+ 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 {
- global_light.ambient.clone()
- }
- .into(),
- );
-
- gl_shader_program
- .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into());
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program
- .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into());
+ GlTextureColorSpace::Linear
+ },
+ )?;
- let texture_map = material
- .textures
- .iter()
- .enumerate()
- .map(|(index, texture)| (texture.id(), index))
- .collect::<HashMap<_, _>>();
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.ambient_map",
- *texture_map.get(&material.ambient_map).unwrap() as i32,
+ gl_texture.set_wrap(
+ current_context,
+ texture_wrapping_to_gl(texture_properties.wrap),
);
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.diffuse_map",
- *texture_map.get(&material.diffuse_map).unwrap() as i32,
+ gl_texture.set_magnifying_filter(
+ current_context,
+ texture_filtering_to_gl(texture_properties.magnifying_filter),
);
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.specular_map",
- *texture_map.get(&material.specular_map).unwrap() as i32,
+ gl_texture.set_minifying_filter(
+ current_context,
+ texture_filtering_to_gl(texture_properties.minifying_filter),
);
- gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess);
-
- gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position);
+ Ok(gl_texture)
}
-fn set_light_attenuation_uniforms(
- gl_shader_program: &mut GlShaderProgram,
- light_array: &str,
- light_index: usize,
- light: &PointLight,
-)
+fn create_shader_program(
+ current_context: &CurrentContextWithFns<'_>,
+ shader_program: &ShaderProgram,
+) -> Result<GlShaderProgram, CreateShaderError>
{
- gl_shader_program.set_uniform_1fv(
- &create_light_uniform_name(
- light_array,
- light_index,
- "attenuation_props.constant",
- ),
- light.attenuation_params.constant,
- );
+ let shader_program_reflection = shader_program.reflection(0).expect("Not possible");
- gl_shader_program.set_uniform_1fv(
- &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
- light.attenuation_params.linear,
- );
+ 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)
+ })?;
- gl_shader_program.set_uniform_1fv(
- &create_light_uniform_name(
- light_array,
- light_index,
- "attenuation_props.quadratic",
- ),
- light.attenuation_params.quadratic,
- );
-}
+ 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()),
+ })?;
-fn set_light_phong_uniforms(
- gl_shader_program: &mut GlShaderProgram,
- light_array: &str,
- light_index: usize,
- light: &impl Light,
-)
-{
- gl_shader_program.set_uniform_vec_3fv(
- &create_light_uniform_name(light_array, light_index, "phong.diffuse"),
- &light.diffuse().clone().into(),
- );
+ let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex);
- gl_shader_program.set_uniform_vec_3fv(
- &create_light_uniform_name(light_array, light_index, "phong.specular"),
- &light.specular().clone().into(),
- );
-}
+ vertex_shader.set_source(
+ current_context,
+ &vertex_shader_entry_point_code.as_str().unwrap(),
+ )?;
-trait Light
-{
- fn diffuse(&self) -> &Color<f32>;
- fn specular(&self) -> &Color<f32>;
-}
+ vertex_shader.compile(current_context)?;
-impl Light for PointLight
-{
- fn diffuse(&self) -> &Color<f32>
- {
- &self.diffuse
- }
+ let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment);
- fn specular(&self) -> &Color<f32>
- {
- &self.specular
- }
-}
+ fragment_shader.set_source(
+ current_context,
+ &fragment_shader_entry_point_code.as_str().unwrap(),
+ )?;
-impl Light for DirectionalLight
-{
- fn diffuse(&self) -> &Color<f32>
- {
- &self.diffuse
- }
+ fragment_shader.compile(current_context)?;
- fn specular(&self) -> &Color<f32>
- {
- &self.specular
- }
-}
+ let gl_shader_program = GlShaderProgram::new(current_context);
-fn create_light_uniform_name(
- light_array: &str,
- light_index: usize,
- light_field: &str,
-) -> CString
-{
- unsafe {
- CString::from_vec_with_nul_unchecked(
- format!("{light_array}[{light_index}].{light_field}\0").into(),
- )
- }
+ 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)
}
-fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4>
+#[derive(Debug, thiserror::Error)]
+enum CreateShaderError
{
- let mut view = Matrix::new();
+ #[error(
+ "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}"
+ )]
+ GetShaderEntryPointCodeFailed
+ {
+ #[source]
+ err: ShaderError,
+ stage: ShaderStage,
+ entrypoint: Cow<'static, str>,
+ },
- view.look_at(&camera_pos, &camera.target, &camera.global_up);
+ #[error("No entrypoint was found for shader stage {0:?}")]
+ NoShaderStageEntrypointFound(ShaderStage),
- view
+ #[error(transparent)]
+ ShaderError(#[from] GlShaderError),
}
#[tracing::instrument(skip_all)]
@@ -693,7 +1346,7 @@ fn opengl_debug_message_cb(
{
use std::backtrace::{Backtrace, BacktraceStatus};
- use tracing::{event, Level};
+ use tracing::{Level, event};
macro_rules! create_event {
($level: expr) => {
@@ -712,7 +1365,8 @@ fn opengl_debug_message_cb(
let backtrace = Backtrace::capture();
if matches!(backtrace.status(), BacktraceStatus::Captured) {
- event!(Level::TRACE, "{backtrace}");
+ tracing::error!("{backtrace}");
+ // event!(Level::TRACE, "{backtrace}");
}
}
MessageType::Other => {
@@ -724,19 +1378,131 @@ fn opengl_debug_message_cb(
};
}
-#[derive(Debug)]
-struct Transformation
+#[inline]
+fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping
{
- position: Vec3<f32>,
- scale: Vec3<f32>,
+ match texture_wrapping {
+ TextureWrapping::Repeat => GlTextureWrapping::Repeat,
+ TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat,
+ TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge,
+ TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder,
+ }
}
-fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4, 4>
+#[inline]
+fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering
{
- let mut matrix = Matrix::new_identity();
+ match texture_filtering {
+ TextureFiltering::Linear => GlTextureFiltering::Linear,
+ TextureFiltering::Nearest => GlTextureFiltering::Nearest,
+ }
+}
- matrix.translate(&transformation.position);
- matrix.scale(&transformation.scale);
+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 }
+ }
+}
- matrix
+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,
+ }
}
diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl
deleted file mode 100644
index 5bf5ff2..0000000
--- a/engine/src/renderer/opengl/glsl/fragment.glsl
+++ /dev/null
@@ -1,73 +0,0 @@
-#version 330 core
-
-#preinclude "light.glsl"
-#preinclude "vertex_data.glsl"
-
-#define MAX_LIGHT_CNT 64
-
-out vec4 FragColor;
-
-in VertexData vertex_data;
-
-uniform vec3 view_pos;
-uniform sampler2D input_texture;
-uniform Material material;
-
-uniform PointLight point_lights[MAX_LIGHT_CNT];
-uniform int point_light_cnt;
-
-uniform DirectionalLight directional_lights[MAX_LIGHT_CNT];
-uniform int directional_light_cnt;
-
-void main()
-{
- vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords);
-
- vec3 directional_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) {
- CalculatedLight calculated_dir_light;
-
- calc_light(
- // Negated since we want the light to point from the light direction
- normalize(-directional_lights[dl_index].direction),
- directional_lights[dl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_dir_light
- );
-
- directional_light_sum +=
- calculated_dir_light.diffuse + calculated_dir_light.specular;
- }
-
- vec3 point_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) {
- vec3 light_direction =
- normalize(point_lights[pl_index].position - vertex_data.world_space_pos);
-
- CalculatedLight calculated_point_light;
-
- calc_light(
- light_direction,
- point_lights[pl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_point_light
- );
-
- float attenuation =
- calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos);
-
- calculated_point_light.diffuse *= attenuation;
- calculated_point_light.specular *= attenuation;
-
- point_light_sum +=
- calculated_point_light.diffuse + calculated_point_light.specular;
- }
-
- FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0);
-}
diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl
deleted file mode 100644
index f12b5fe..0000000
--- a/engine/src/renderer/opengl/glsl/light.glsl
+++ /dev/null
@@ -1,133 +0,0 @@
-#version 330 core
-
-#ifndef LIGHT_GLSL
-#define LIGHT_GLSL
-
-#preinclude "vertex_data.glsl"
-
-struct Material
-{
- vec3 ambient;
- vec3 diffuse;
- vec3 specular;
- sampler2D ambient_map;
- sampler2D diffuse_map;
- sampler2D specular_map;
- float shininess;
-};
-
-struct LightPhong
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-struct AttenuationProperties
-{
- float constant;
- float linear;
- float quadratic;
-};
-
-struct PointLight
-{
- LightPhong phong;
- vec3 position;
- AttenuationProperties attenuation_props;
-};
-
-struct DirectionalLight
-{
- LightPhong phong;
- vec3 direction;
-};
-
-struct CalculatedLight
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-vec3 calc_ambient_light(in Material material, in vec2 texture_coords)
-{
- return vec3(texture(material.ambient_map, texture_coords)) * material.ambient;
-}
-
-vec3 calc_diffuse_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec2 texture_coords
-)
-{
- float diff = max(dot(norm, light_dir), 0.0);
-
- return light_phong.diffuse * (
- diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse)
- );
-}
-
-vec3 calc_specular_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec3 view_pos,
- in vec3 frag_pos,
- in vec2 texture_coords
-)
-{
- vec3 view_direction = normalize(view_pos - frag_pos);
-
- vec3 halfway_direction = normalize(light_dir + view_direction);
-
- float spec =
- pow(max(dot(norm, halfway_direction), 0.0), material.shininess);
-
- return light_phong.specular * (
- spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular)
- );
-}
-
-float calc_attenuation(in PointLight point_light, in vec3 position)
-{
- float light_distance = length(point_light.position - position);
-
- return 1.0 / (point_light.attenuation_props.constant
- + point_light.attenuation_props.linear
- * light_distance + point_light.attenuation_props.quadratic
- * pow(light_distance, 2));
-}
-
-void calc_light(
- in vec3 light_direction,
- in LightPhong light_phong,
- in VertexData vertex_data,
- in vec3 view_pos,
- in Material material,
- out CalculatedLight calculated_light
-)
-{
- vec3 norm = normalize(vertex_data.world_space_normal);
-
- calculated_light.diffuse = calc_diffuse_light(
- material,
- light_phong,
- light_direction,
- norm,
- vertex_data.texture_coords
- );
-
- calculated_light.specular = calc_specular_light(
- material,
- light_phong,
- light_direction,
- norm,
- view_pos,
- vertex_data.world_space_pos,
- vertex_data.texture_coords
- );
-}
-
-#endif
diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl
deleted file mode 100644
index b57caa6..0000000
--- a/engine/src/renderer/opengl/glsl/vertex.glsl
+++ /dev/null
@@ -1,24 +0,0 @@
-#version 330 core
-
-#preinclude "vertex_data.glsl"
-
-layout (location = 0) in vec3 pos;
-layout (location = 1) in vec2 texture_coords;
-layout (location = 2) in vec3 normal;
-
-out VertexData vertex_data;
-
-uniform mat4 model;
-uniform mat4 view;
-uniform mat4 projection;
-
-void main()
-{
- gl_Position = projection * view * model * vec4(pos, 1.0);
-
- vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0));
- vertex_data.texture_coords = texture_coords;
-
- // TODO: Do this using CPU for performance increase
- vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal;
-}
diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl
deleted file mode 100644
index 486d445..0000000
--- a/engine/src/renderer/opengl/glsl/vertex_data.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef VERTEX_DATA_GLSL
-#define VERTEX_DATA_GLSL
-
-struct VertexData
-{
- vec2 texture_coords;
- vec3 world_space_pos;
- vec3 world_space_normal;
-};
-
-#endif
diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs
new file mode 100644
index 0000000..cfd6ea7
--- /dev/null
+++ b/engine/src/renderer/opengl/glutin_compat.rs
@@ -0,0 +1,268 @@
+// 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;
+#[cfg(x11_platform)]
+use glutin::platform::x11::X11GlConfigExt;
+use glutin::prelude::*;
+use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle};
+
+use crate::windowing::window::CreationAttributes as WindowCreationAttributes;
+
+#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))]
+compile_error!("Please select at least one api backend");
+
+/// 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
+{
+ /// Create new display builder.
+ pub fn new() -> Self
+ {
+ Default::default()
+ }
+
+ /// 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!(wgl_backend) {
+ 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.
+ #[cfg(wgl_backend)]
+ let template_builder = if let Some(raw_window_handle) = raw_window_handle {
+ 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(wgl_backend)) 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)?;
+
+ #[cfg(not(wgl_backend))]
+ let window_attrs =
+ { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) };
+
+ #[cfg(wgl_backend)]
+ let window_attrs = self.window_attributes;
+
+ 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,
+}
+
+fn create_display(
+ display_handle: &DisplayHandle<'_>,
+ _api_preference: ApiPreference,
+ _raw_window_handle: Option<RawWindowHandle>,
+) -> Result<Display, GlutinError>
+{
+ #[cfg(egl_backend)]
+ let _preference = DisplayApiPreference::Egl;
+
+ #[cfg(glx_backend)]
+ let _preference = DisplayApiPreference::Glx(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ ));
+
+ #[cfg(cgl_backend)]
+ let _preference = DisplayApiPreference::Cgl;
+
+ #[cfg(wgl_backend)]
+ let _preference = DisplayApiPreference::Wgl(_raw_window_handle);
+
+ #[cfg(all(egl_backend, glx_backend))]
+ let _preference = 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,
+ )),
+ };
+
+ #[cfg(all(wgl_backend, egl_backend))]
+ let _preference = match _api_preference {
+ ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle),
+ ApiPreference::FallbackEgl => {
+ DisplayApiPreference::WglThenEgl(_raw_window_handle)
+ }
+ };
+
+ let handle = display_handle.as_raw();
+ unsafe { Ok(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(wgl_backend))]
+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(x11_platform)]
+ let attributes = if let Some(x11_visual) = gl_config.x11_visual() {
+ attributes.with_x11_visual(x11_visual.visual_id() as _)
+ } else {
+ attributes
+ };
+
+ 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/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs
new file mode 100644
index 0000000..c5ff0c6
--- /dev/null
+++ b/engine/src/renderer/opengl/graphics_mesh.rs
@@ -0,0 +1,216 @@
+use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns;
+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 crate::mesh::{Mesh, VertexAttrType};
+use crate::renderer::MeshUsage;
+use crate::shader::VertexDescription as ShaderVertexDescription;
+
+#[derive(Debug)]
+pub struct GraphicsMesh
+{
+ /// Vertex and index buffer has to live as long as the vertex array
+ vertex_buffer: GlBuffer<u8>,
+ 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,
+ vertex_desc: &ShaderVertexDescription,
+ ) -> 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)?;
+
+ let vertex_buf_binding_index = 0;
+
+ 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 ({}) is too large. Must be less than {max_value}",
+ value
+ );
+ }
+ }
+ }
+
+ for vertex_attr_props in mesh.vertex_buf().vertex_attr_props() {
+ let vertex_field_desc = vertex_desc
+ .fields
+ .iter()
+ .find(|vertex_field_desc| {
+ *vertex_field_desc.name == vertex_attr_props.name
+ })
+ .unwrap();
+
+ let attrib_index: u32 =
+ vertex_field_desc.varying_input_offset.try_into().unwrap();
+
+ vertex_arr.enable_attrib(current_context, attrib_index);
+
+ vertex_arr.set_attrib_format(
+ current_context,
+ 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(),
+ }
+ }
+ },
+ );
+
+ vertex_arr.set_attrib_vertex_buf_binding(
+ current_context,
+ attrib_index,
+ vertex_buf_binding_index,
+ );
+ }
+
+ 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_buffer,
+ 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_buffer,
+ 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(());
+ }
+
+ 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 destroy(&mut 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),
+}
+
+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,
+ }
+}