summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/Cargo.toml17
-rw-r--r--engine/build.rs63
-rw-r--r--engine/src/camera/fly.rs54
-rw-r--r--engine/src/data_types/color.rs1
-rw-r--r--engine/src/data_types/dimens.rs14
-rw-r--r--engine/src/data_types/matrix.rs2
-rw-r--r--engine/src/data_types/vector.rs25
-rw-r--r--engine/src/input.rs247
-rw-r--r--engine/src/input/keyboard.rs6
-rw-r--r--engine/src/input/mouse.rs6
-rw-r--r--engine/src/lib.rs5
-rw-r--r--engine/src/opengl/buffer.rs101
-rw-r--r--engine/src/opengl/debug.rs145
-rw-r--r--engine/src/opengl/mod.rs127
-rw-r--r--engine/src/opengl/shader.rs270
-rw-r--r--engine/src/opengl/texture.rs169
-rw-r--r--engine/src/opengl/util.rs30
-rw-r--r--engine/src/opengl/vertex_array.rs158
-rw-r--r--engine/src/renderer.rs70
-rw-r--r--engine/src/renderer/opengl.rs1420
-rw-r--r--engine/src/renderer/opengl/glutin_compat.rs268
-rw-r--r--engine/src/renderer/opengl/graphics_mesh.rs120
-rw-r--r--engine/src/renderer/opengl/vertex.rs9
-rw-r--r--engine/src/util.rs142
-rw-r--r--engine/src/window.rs755
-rw-r--r--engine/src/windowing.rs669
-rw-r--r--engine/src/windowing/keyboard.rs763
-rw-r--r--engine/src/windowing/mouse.rs136
-rw-r--r--engine/src/windowing/window.rs171
-rw-r--r--engine/src/windowing/window/platform.rs12
30 files changed, 3522 insertions, 2453 deletions
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index a62f458..6ddcf12 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -4,18 +4,31 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-glfw = { path = "../glfw", features = ["opengl"] }
+glutin = "0.32.3"
+raw-window-handle = "0.6.2"
thiserror = "1.0.49"
-gl = "0.14.0"
bitflags = "2.4.0"
tracing = "0.1.39"
seq-macro = "0.3.5"
paste = "1.0.14"
+parking_lot = "0.12.3"
+crossbeam-channel = "0.5.15"
+safer-ffi = "0.1.13"
+nu-ansi-term = "0.46.0"
ecs = { path = "../ecs" }
util-macros = { path = "../util-macros" }
+opengl-bindings = { path = "../opengl-bindings" }
+
+[dependencies.winit]
+version = "0.30.11"
+default-features = false
+features = ["rwh_06", "wayland", "wayland-dlopen", "x11"]
[dependencies.image_rs]
version = "0.24.7"
default-features = false
features = ["png", "jpeg"]
package = "image"
+
+[build-dependencies]
+cfg_aliases = "0.2.1"
diff --git a/engine/build.rs b/engine/build.rs
new file mode 100644
index 0000000..58029fc
--- /dev/null
+++ b/engine/build.rs
@@ -0,0 +1,63 @@
+// Original file:
+// https://github.com/rust-windowing/glutin/blob/
+// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/build.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.
+
+// Keep this in sync with glutin's build.rs.
+
+use cfg_aliases::cfg_aliases;
+
+fn main()
+{
+ // Setup alias to reduce `cfg` boilerplate.
+ cfg_aliases! {
+ // Systems.
+ android_platform: { target_os = "android" },
+ wasm_platform: { target_family = "wasm" },
+ macos_platform: { target_os = "macos" },
+ ios_platform: { target_os = "ios" },
+ apple: { any(ios_platform, macos_platform) },
+ free_unix: { all(unix, not(apple), not(android_platform)) },
+
+ // Native displays.
+ x11_platform: { all(free_unix, not(wasm_platform)) },
+ wayland_platform: { all(free_unix, not(wasm_platform)) },
+ // x11_platform: { all(feature = "x11", free_unix, not(wasm_platform)) },
+ // wayland_platform: { all(feature = "wayland", free_unix, not(wasm_platform)) },
+
+ // Backends.
+ egl_backend: {
+ all(any(windows, unix), not(apple), not(wasm_platform))
+ },
+ glx_backend: { all(x11_platform, not(wasm_platform)) },
+ wgl_backend: { all(windows, not(wasm_platform)) },
+ cgl_backend: { all(macos_platform, not(wasm_platform)) },
+
+ // Backends.
+ // egl_backend: {
+ // all(feature = "egl", any(windows, unix), not(apple), not(wasm_platform))
+ // },
+ // glx_backend: { all(feature = "glx", x11_platform, not(wasm_platform)) },
+ // wgl_backend: { all(feature = "wgl", windows, not(wasm_platform)) },
+ // cgl_backend: { all(macos_platform, not(wasm_platform)) },
+ }
+}
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs
index b1214db..a034851 100644
--- a/engine/src/camera/fly.rs
+++ b/engine/src/camera/fly.rs
@@ -8,7 +8,8 @@ use ecs::{Component, Query};
use crate::builder;
use crate::camera::{Active as ActiveCamera, Camera};
use crate::delta_time::DeltaTime;
-use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys};
+use crate::input::keyboard::{Key, KeyState, Keyboard};
+use crate::input::mouse::Motion as MouseMotion;
use crate::transform::WorldPosition;
use crate::vector::{Vec2, Vec3};
@@ -60,12 +61,7 @@ impl ecs::extension::Extension for Extension
{
fn collect(self, mut collector: ecs::extension::Collector<'_>)
{
- collector.add_system(
- *UPDATE_PHASE,
- update
- .into_system()
- .initialize((CursorState::default(), self.0)),
- );
+ collector.add_system(*UPDATE_PHASE, update.into_system().initialize((self.0,)));
}
}
@@ -77,35 +73,29 @@ pub struct Options
fn update(
camera_query: Query<(&mut Camera, &mut WorldPosition, &mut Fly, &ActiveCamera)>,
- keys: Single<Keys>,
- cursor: Single<Cursor>,
- cursor_flags: Single<CursorFlags>,
+ keyboard: Single<Keyboard>,
+ mouse_motion: Single<MouseMotion>,
delta_time: Single<DeltaTime>,
- mut cursor_state: Local<CursorState>,
options: Local<Options>,
)
{
for (mut camera, mut camera_world_pos, mut fly_camera, _) in &camera_query {
- if cursor.has_moved && cursor_flags.is_first_move.flag {
- tracing::debug!("First cursor move");
-
- cursor_state.last_pos = cursor.position;
- }
-
let delta_time = delta_time.duration;
- let mut x_offset = cursor.position.x - cursor_state.last_pos.x;
- let mut y_offset = cursor_state.last_pos.y - cursor.position.y;
+ // tracing::info!("Mouse motion: {:?}", mouse_motion.position_delta);
- cursor_state.last_pos = cursor.position;
+ if mouse_motion.position_delta != (Vec2 { x: 0.0, y: 0.0 }) {
+ let x_offset =
+ mouse_motion.position_delta.x * f64::from(options.mouse_sensitivity);
- x_offset *= f64::from(options.mouse_sensitivity);
- y_offset *= f64::from(options.mouse_sensitivity);
+ let y_offset =
+ (-mouse_motion.position_delta.y) * f64::from(options.mouse_sensitivity);
- fly_camera.current_yaw += x_offset;
- fly_camera.current_pitch += y_offset;
+ fly_camera.current_yaw += x_offset;
+ fly_camera.current_pitch += y_offset;
- fly_camera.current_pitch = fly_camera.current_pitch.clamp(-89.0, 89.0);
+ fly_camera.current_pitch = fly_camera.current_pitch.clamp(-89.0, 89.0);
+ }
// TODO: This casting to a f32 from a f64 is horrible. fix it
#[allow(clippy::cast_possible_truncation)]
@@ -122,24 +112,24 @@ fn update(
camera.global_up = cam_right.cross(&direction).normalize();
- if keys.get_key_state(Key::W) == KeyState::Pressed {
+ if keyboard.get_key_state(Key::W) == KeyState::Pressed {
camera_world_pos.position +=
direction * fly_camera.speed * delta_time.as_secs_f32();
}
- if keys.get_key_state(Key::S) == KeyState::Pressed {
+ if keyboard.get_key_state(Key::S) == KeyState::Pressed {
camera_world_pos.position -=
direction * fly_camera.speed * delta_time.as_secs_f32();
}
- if keys.get_key_state(Key::A) == KeyState::Pressed {
+ if keyboard.get_key_state(Key::A) == KeyState::Pressed {
let cam_left = -direction.cross(&Vec3::UP).normalize();
camera_world_pos.position +=
cam_left * fly_camera.speed * delta_time.as_secs_f32();
}
- if keys.get_key_state(Key::D) == KeyState::Pressed {
+ if keyboard.get_key_state(Key::D) == KeyState::Pressed {
let cam_right = direction.cross(&Vec3::UP).normalize();
camera_world_pos.position +=
@@ -149,9 +139,3 @@ fn update(
camera.target = camera_world_pos.position + direction;
}
}
-
-#[derive(Debug, Default, Component)]
-struct CursorState
-{
- last_pos: Vec2<f64>,
-}
diff --git a/engine/src/data_types/color.rs b/engine/src/data_types/color.rs
index cef3b92..c5316e6 100644
--- a/engine/src/data_types/color.rs
+++ b/engine/src/data_types/color.rs
@@ -1,7 +1,6 @@
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Default)]
-#[repr(C)]
pub struct Color<Value>
{
pub red: Value,
diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs
index d8d0247..8bf239f 100644
--- a/engine/src/data_types/dimens.rs
+++ b/engine/src/data_types/dimens.rs
@@ -1,3 +1,5 @@
+use std::num::NonZeroU32;
+
/// 2D dimensions.
#[derive(Debug, Clone, Copy)]
pub struct Dimens<Value>
@@ -22,6 +24,18 @@ impl<Value> From<(Value, Value)> for Dimens<Value>
}
}
+impl Dimens<u32>
+{
+ #[must_use]
+ pub fn try_into_nonzero(self) -> Option<Dimens<NonZeroU32>>
+ {
+ Some(Dimens {
+ width: NonZeroU32::new(self.width)?,
+ height: NonZeroU32::new(self.height)?,
+ })
+ }
+}
+
/// 3D dimensions.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Dimens3<Value>
diff --git a/engine/src/data_types/matrix.rs b/engine/src/data_types/matrix.rs
index 3a29ae2..b754b62 100644
--- a/engine/src/data_types/matrix.rs
+++ b/engine/src/data_types/matrix.rs
@@ -4,7 +4,7 @@ use crate::vector::Vec3;
pub struct Matrix<Value, const ROWS: usize, const COLUMNS: usize>
{
/// Items must be layed out this way for it to work with OpenGL shaders.
- items: [[Value; ROWS]; COLUMNS],
+ pub items: [[Value; ROWS]; COLUMNS],
}
impl<Value, const ROWS: usize, const COLUMNS: usize> Matrix<Value, ROWS, COLUMNS>
diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs
index 100c709..dc6df30 100644
--- a/engine/src/data_types/vector.rs
+++ b/engine/src/data_types/vector.rs
@@ -3,7 +3,6 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use crate::color::Color;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
-#[repr(C)]
pub struct Vec2<Value>
{
pub x: Value,
@@ -15,6 +14,29 @@ impl Vec2<u32>
pub const ZERO: Self = Self { x: 0, y: 0 };
}
+impl<Value> Add for Vec2<Value>
+where
+ Value: Add<Value, Output = Value>,
+{
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output
+ {
+ Self::Output { x: self.x + rhs.x, y: self.y + rhs.y }
+ }
+}
+
+impl<Value> AddAssign for Vec2<Value>
+where
+ Value: Add<Value, Output = Value> + Clone,
+{
+ fn add_assign(&mut self, rhs: Self)
+ {
+ self.x = self.x.clone() + rhs.x;
+ self.y = self.y.clone() + rhs.y;
+ }
+}
+
impl<Value> Add<Value> for Vec2<Value>
where
Value: Add<Output = Value> + Clone,
@@ -76,7 +98,6 @@ where
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
-#[repr(C)]
pub struct Vec3<Value>
{
pub x: Value,
diff --git a/engine/src/input.rs b/engine/src/input.rs
index c53175e..f8c9dfd 100644
--- a/engine/src/input.rs
+++ b/engine/src/input.rs
@@ -1,257 +1,32 @@
-use std::collections::HashMap;
-
+use ecs::declare_entity;
use ecs::extension::Collector as ExtensionCollector;
-use ecs::pair::{ChildOf, Pair};
-use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE};
-use ecs::sole::Single;
-use ecs::{declare_entity, Sole};
-
-use crate::vector::Vec2;
-use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE};
+use ecs::pair::{DependsOn, Pair};
+use ecs::phase::Phase;
-mod reexports
-{
- pub use crate::window::{Key, KeyState};
-}
+use crate::windowing::PHASE as WINDOWING_PHASE;
-pub use reexports::*;
+pub mod keyboard;
+pub mod mouse;
declare_entity!(
- SET_PREV_KEY_STATE_PHASE,
+ pub PHASE,
(
Phase,
Pair::builder()
- .relation::<ChildOf>()
- .target_id(*WINDOW_UPDATE_PHASE)
+ .relation::<DependsOn>()
+ .target_id(*WINDOWING_PHASE)
.build()
)
);
-#[derive(Debug, Sole)]
-pub struct Keys
-{
- map: HashMap<Key, KeyData>,
- pending: Vec<(Key, KeyState)>,
-}
-
-impl Keys
-{
- #[must_use]
- pub fn new() -> Self
- {
- Self {
- map: Key::KEYS
- .iter()
- .map(|key| {
- (
- *key,
- KeyData {
- state: KeyState::Released,
- prev_tick_state: KeyState::Released,
- },
- )
- })
- .collect(),
- pending: Vec::with_capacity(Key::KEYS.len()),
- }
- }
-
- #[must_use]
- pub fn get_key_state(&self, key: Key) -> KeyState
- {
- let Some(key_data) = self.map.get(&key) else {
- unreachable!();
- };
-
- key_data.state
- }
-
- #[must_use]
- pub fn get_prev_key_state(&self, key: Key) -> KeyState
- {
- let Some(key_data) = self.map.get(&key) else {
- unreachable!();
- };
-
- key_data.prev_tick_state
- }
-
- pub fn set_key_state(&mut self, key: Key, new_key_state: KeyState)
- {
- let Some(key_data) = self.map.get_mut(&key) else {
- unreachable!();
- };
-
- key_data.state = new_key_state;
- }
-
- #[must_use]
- pub fn is_anything_pressed(&self) -> bool
- {
- self.map
- .values()
- .any(|key_data| matches!(key_data.state, KeyState::Pressed))
- }
-}
-
-impl Default for Keys
-{
- fn default() -> Self
- {
- Self::new()
- }
-}
-
-#[derive(Debug, Default, Clone, Sole)]
-pub struct Cursor
-{
- pub position: Vec2<f64>,
- pub has_moved: bool,
-}
-
-#[derive(Debug, Clone, Sole)]
-pub struct CursorFlags
-{
- /// This flag is set in two situations:
- /// A: The window has just started
- /// B: The window has gained focus again after losing focus.
- ///
- /// This flag only lasts a single tick then it is cleared (at the beginning of the
- /// next tick).
- pub is_first_move: CursorFlag,
-}
-
-impl Default for CursorFlags
-{
- fn default() -> Self
- {
- Self {
- is_first_move: CursorFlag { flag: true, ..Default::default() },
- }
- }
-}
-
-#[derive(Debug, Default, Clone)]
-pub struct CursorFlag
-{
- pub flag: bool,
- pub pending_clear: bool,
-}
-
-impl CursorFlag
-{
- pub fn clear(&mut self)
- {
- self.flag = false;
- self.pending_clear = false;
- }
-}
-
/// Input extension.
#[derive(Debug, Default)]
pub struct Extension {}
impl ecs::extension::Extension for Extension
{
- fn collect(self, mut collector: ExtensionCollector<'_>)
+ fn collect(self, _collector: ExtensionCollector<'_>)
{
- collector.add_declared_entity(&SET_PREV_KEY_STATE_PHASE);
-
- collector.add_system(*START_PHASE, initialize);
- collector.add_system(*PRE_UPDATE_PHASE, maybe_clear_cursor_is_first_move);
- collector.add_system(*SET_PREV_KEY_STATE_PHASE, set_pending_key_states);
-
- collector.add_sole(Keys::default()).ok();
- collector.add_sole(Cursor::default()).ok();
- collector.add_sole(CursorFlags::default()).ok();
- }
-}
-
-fn initialize(
- keys: Single<Keys>,
- cursor: Single<Cursor>,
- cursor_flags: Single<CursorFlags>,
- window: Single<Window>,
-)
-{
- let keys_weak_ref = keys.to_weak_ref();
-
- window.set_key_callback(move |key, _scancode, key_state, _modifiers| {
- let keys_ref = keys_weak_ref.access().expect("No world");
-
- let mut keys = keys_ref.to_single();
-
- keys.pending.push((key, key_state));
- });
-
- let cursor_weak_ref = cursor.to_weak_ref();
-
- window.set_cursor_pos_callback(move |cursor_position| {
- let cursor_ref = cursor_weak_ref.access().expect("No world");
-
- let mut cursor = cursor_ref.to_single();
-
- cursor.position = Vec2 {
- x: cursor_position.x,
- y: cursor_position.y,
- };
-
- cursor.has_moved = true;
- });
-
- let cursor_flags_weak_ref = cursor_flags.to_weak_ref();
-
- window.set_focus_callback(move |is_focused| {
- tracing::trace!("Window is focused: {is_focused}");
-
- let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world");
-
- cursor_flags_ref.to_single().is_first_move.flag = is_focused;
- });
-}
-
-fn maybe_clear_cursor_is_first_move(
- cursor: Single<Cursor>,
- mut cursor_flags: Single<CursorFlags>,
-)
-{
- if cursor_flags.is_first_move.pending_clear {
- tracing::trace!("Clearing is_first_move");
-
- // This flag was set for the whole previous tick so it can be cleared now
- cursor_flags.is_first_move.clear();
-
- return;
- }
-
- if cursor.has_moved && cursor_flags.is_first_move.flag {
- tracing::trace!("Setting flag to clear is_first_move next tick");
-
- // Make this system clear is_first_move the next time it runs
- cursor_flags.is_first_move.pending_clear = true;
+ // TODO: Add input mapping
}
}
-
-fn set_pending_key_states(mut keys: Single<Keys>)
-{
- let Keys { map, pending } = &mut *keys;
-
- for key_data in map.values_mut() {
- key_data.prev_tick_state = key_data.state;
- }
-
- for (key, key_state) in pending {
- let Some(key_data) = map.get_mut(key) else {
- unreachable!();
- };
-
- key_data.state = *key_state;
- }
-}
-
-#[derive(Debug)]
-struct KeyData
-{
- state: KeyState,
- prev_tick_state: KeyState,
-}
diff --git a/engine/src/input/keyboard.rs b/engine/src/input/keyboard.rs
new file mode 100644
index 0000000..d226df0
--- /dev/null
+++ b/engine/src/input/keyboard.rs
@@ -0,0 +1,6 @@
+mod reexports
+{
+ pub use crate::windowing::keyboard::{Key, KeyState, Keyboard};
+}
+
+pub use reexports::*;
diff --git a/engine/src/input/mouse.rs b/engine/src/input/mouse.rs
new file mode 100644
index 0000000..90091f3
--- /dev/null
+++ b/engine/src/input/mouse.rs
@@ -0,0 +1,6 @@
+mod reexports
+{
+ pub use crate::windowing::mouse::{Button, ButtonState, Buttons, Motion};
+}
+
+pub use reexports::*;
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index a18cebb..d5531c1 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -36,7 +36,7 @@ pub mod projection;
pub mod renderer;
pub mod texture;
pub mod transform;
-pub mod window;
+pub mod windowing;
pub extern crate ecs;
@@ -57,6 +57,9 @@ impl Engine
#[must_use]
pub fn new() -> Self
{
+ #[cfg(windows)]
+ nu_ansi_term::enable_ansi_support().unwrap();
+
let mut world = World::new();
world.add_sole(DeltaTime::default()).ok();
diff --git a/engine/src/opengl/buffer.rs b/engine/src/opengl/buffer.rs
deleted file mode 100644
index eded553..0000000
--- a/engine/src/opengl/buffer.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-use std::marker::PhantomData;
-use std::mem::size_of_val;
-use std::ptr::null;
-
-#[derive(Debug)]
-pub struct Buffer<Item>
-{
- buf: gl::types::GLuint,
- _pd: PhantomData<Item>,
-}
-
-impl<Item> Buffer<Item>
-{
- pub fn new() -> Self
- {
- let mut buffer = gl::types::GLuint::default();
-
- unsafe {
- gl::CreateBuffers(1, &mut buffer);
- };
-
- Self { buf: buffer, _pd: PhantomData }
- }
-
- /// Stores items in the currently bound buffer.
- pub fn store(&mut self, items: &[Item], usage: Usage)
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::NamedBufferData(
- self.buf,
- size_of_val(items) as gl::types::GLsizeiptr,
- items.as_ptr().cast(),
- usage.into_gl(),
- );
- }
- }
-
- pub fn store_mapped<Value>(
- &mut self,
- values: &[Value],
- usage: Usage,
- mut map_func: impl FnMut(&Value) -> Item,
- )
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::NamedBufferData(
- self.buf,
- (size_of::<Item>() * values.len()) as gl::types::GLsizeiptr,
- null(),
- usage.into_gl(),
- );
- }
-
- for (index, value) in values.iter().enumerate() {
- let item = map_func(value);
-
- unsafe {
- gl::NamedBufferSubData(
- self.buf,
- (index * size_of::<Item>()) as gl::types::GLintptr,
- size_of::<Item>() as gl::types::GLsizeiptr,
- (&raw const item).cast(),
- );
- }
- }
- }
-
- pub fn object(&self) -> gl::types::GLuint
- {
- self.buf
- }
-}
-
-/// Buffer usage.
-#[derive(Debug)]
-#[allow(dead_code)]
-pub enum Usage
-{
- /// The buffer data is set only once and used by the GPU at most a few times.
- Stream,
-
- /// The buffer data is set only once and used many times.
- Static,
-
- /// The buffer data is changed a lot and used many times.
- Dynamic,
-}
-
-impl Usage
-{
- fn into_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Stream => gl::STREAM_DRAW,
- Self::Static => gl::STATIC_DRAW,
- Self::Dynamic => gl::DYNAMIC_DRAW,
- }
- }
-}
diff --git a/engine/src/opengl/debug.rs b/engine/src/opengl/debug.rs
deleted file mode 100644
index 203590a..0000000
--- a/engine/src/opengl/debug.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::ffi::c_void;
-use std::io::{stderr, Write};
-use std::panic::catch_unwind;
-use std::ptr::null_mut;
-use std::sync::Mutex;
-
-use crate::opengl::util::gl_enum;
-
-pub type MessageCallback = fn(
- source: MessageSource,
- ty: MessageType,
- id: u32,
- severity: MessageSeverity,
- message: &str,
-);
-
-pub fn enable_debug_output()
-{
- unsafe {
- gl::Enable(gl::DEBUG_OUTPUT);
- gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
- }
-}
-
-pub fn set_debug_message_callback(cb: MessageCallback)
-{
- *DEBUG_MESSAGE_CB.lock().unwrap() = Some(cb);
-
- unsafe {
- gl::DebugMessageCallback(Some(debug_message_cb), null_mut());
- }
-}
-
-pub fn set_debug_message_control(
- source: Option<MessageSource>,
- ty: Option<MessageType>,
- severity: Option<MessageSeverity>,
- ids: &[u32],
- ids_action: MessageIdsAction,
-)
-{
- // Ids shouldn't realistically be large enough to cause a panic here
- let ids_len: i32 = ids.len().try_into().unwrap();
-
- unsafe {
- gl::DebugMessageControl(
- source.map_or(gl::DONT_CARE, |source| source as u32),
- ty.map_or(gl::DONT_CARE, |ty| ty as u32),
- severity.map_or(gl::DONT_CARE, |severity| severity as u32),
- ids_len,
- ids.as_ptr(),
- ids_action as u8,
- );
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-#[allow(dead_code)]
-pub enum MessageIdsAction
-{
- Enable = 1,
- Disable = 0,
-}
-
-gl_enum! {
-pub enum MessageSource
-{
- Api = gl::DEBUG_SOURCE_API,
- WindowSystem = gl::DEBUG_SOURCE_WINDOW_SYSTEM,
- ShaderCompiler = gl::DEBUG_SOURCE_SHADER_COMPILER,
- ThirdParty = gl::DEBUG_SOURCE_THIRD_PARTY,
- Application = gl::DEBUG_SOURCE_APPLICATION,
- Other = gl::DEBUG_SOURCE_OTHER,
-}
-}
-
-gl_enum! {
-pub enum MessageType
-{
- DeprecatedBehavior = gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR,
- Error = gl::DEBUG_TYPE_ERROR,
- Marker = gl::DEBUG_TYPE_MARKER,
- Other = gl::DEBUG_TYPE_OTHER,
- Performance = gl::DEBUG_TYPE_PERFORMANCE,
- PopGroup = gl::DEBUG_TYPE_POP_GROUP,
- PushGroup = gl::DEBUG_TYPE_PUSH_GROUP,
- Portability = gl::DEBUG_TYPE_PORTABILITY,
- UndefinedBehavior = gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR,
-}
-}
-
-gl_enum! {
-pub enum MessageSeverity
-{
- High = gl::DEBUG_SEVERITY_HIGH,
- Medium = gl::DEBUG_SEVERITY_MEDIUM,
- Low = gl::DEBUG_SEVERITY_LOW,
- Notification = gl::DEBUG_SEVERITY_NOTIFICATION,
-}
-}
-
-static DEBUG_MESSAGE_CB: Mutex<Option<MessageCallback>> = Mutex::new(None);
-
-extern "system" fn debug_message_cb(
- source: gl::types::GLenum,
- ty: gl::types::GLenum,
- id: gl::types::GLuint,
- severity: gl::types::GLenum,
- message_length: gl::types::GLsizei,
- message: *const gl::types::GLchar,
- _user_param: *mut c_void,
-)
-{
- // Unwinds are catched because unwinding from Rust code into foreign code is UB.
- let res = catch_unwind(|| {
- let cb_lock = DEBUG_MESSAGE_CB.lock().unwrap();
-
- if let Some(cb) = *cb_lock {
- let msg_source = MessageSource::from_gl(source).unwrap();
- let msg_type = MessageType::from_gl(ty).unwrap();
- let msg_severity = MessageSeverity::from_gl(severity).unwrap();
-
- let msg_length = usize::try_from(message_length).unwrap();
-
- // SAFETY: The received message should be a valid ASCII string
- let message = unsafe {
- std::str::from_utf8_unchecked(std::slice::from_raw_parts(
- message.cast(),
- msg_length,
- ))
- };
-
- cb(msg_source, msg_type, id, msg_severity, message);
- }
- });
-
- if res.is_err() {
- // eprintln is not used since it can panic and unwinds are unwanted because
- // unwinding from Rust code into foreign code is UB.
- stderr()
- .write_all(b"ERROR: Panic in debug message callback")
- .ok();
- println!();
- }
-}
diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs
index 53e0120..2208ac6 100644
--- a/engine/src/opengl/mod.rs
+++ b/engine/src/opengl/mod.rs
@@ -1,128 +1 @@
-use bitflags::bitflags;
-use gl::types::GLint;
-
-use crate::data_types::dimens::Dimens;
-use crate::vector::Vec2;
-
-pub mod buffer;
pub mod glsl;
-pub mod shader;
-pub mod texture;
-pub mod vertex_array;
-
-mod util;
-
-pub mod debug;
-
-pub fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
-{
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::Viewport(
- position.x as i32,
- position.y as i32,
- size.width as i32,
- size.height as i32,
- );
- }
-}
-
-pub fn clear_buffers(mask: BufferClearMask)
-{
- unsafe {
- gl::Clear(mask.bits());
- }
-}
-
-pub fn set_polygon_mode(face: impl Into<PolygonModeFace>, mode: impl Into<PolygonMode>)
-{
- unsafe {
- gl::PolygonMode(face.into() as u32, mode.into() as u32);
- }
-}
-
-pub fn enable(capacity: Capability)
-{
- unsafe {
- gl::Enable(capacity as u32);
- }
-}
-
-pub fn get_context_flags() -> ContextFlags
-{
- let mut context_flags: GLint = 0;
-
- unsafe {
- gl::GetIntegerv(gl::CONTEXT_FLAGS as u32, &mut context_flags);
- }
-
- ContextFlags::from_bits_truncate(context_flags as u32)
-}
-
-bitflags! {
- #[derive(Debug, Clone, Copy)]
- pub struct BufferClearMask: u32 {
- const COLOR = gl::COLOR_BUFFER_BIT;
- const DEPTH = gl::DEPTH_BUFFER_BIT;
- const STENCIL = gl::STENCIL_BUFFER_BIT;
- }
-}
-
-#[derive(Debug)]
-#[repr(u32)]
-pub enum Capability
-{
- DepthTest = gl::DEPTH_TEST,
- MultiSample = gl::MULTISAMPLE,
-}
-
-#[derive(Debug)]
-#[repr(u32)]
-pub enum PolygonMode
-{
- Point = gl::POINT,
- Line = gl::LINE,
- Fill = gl::FILL,
-}
-
-impl From<crate::draw_flags::PolygonMode> for 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,
- }
- }
-}
-
-#[derive(Debug)]
-#[repr(u32)]
-pub enum PolygonModeFace
-{
- Front = gl::FRONT,
- Back = gl::BACK,
- FrontAndBack = gl::FRONT_AND_BACK,
-}
-
-impl From<crate::draw_flags::PolygonModeFace> for 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,
- }
- }
-}
-
-bitflags! {
-#[derive(Debug, Clone, Copy)]
-pub struct ContextFlags: u32 {
- const FORWARD_COMPATIBLE = gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;
- const DEBUG = gl::CONTEXT_FLAG_DEBUG_BIT;
- const ROBUST_ACCESS = gl::CONTEXT_FLAG_ROBUST_ACCESS_BIT;
-}
-}
diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs
deleted file mode 100644
index a626fc7..0000000
--- a/engine/src/opengl/shader.rs
+++ /dev/null
@@ -1,270 +0,0 @@
-use std::ffi::CStr;
-use std::ptr::null_mut;
-
-use crate::matrix::Matrix;
-use crate::vector::Vec3;
-
-#[derive(Debug)]
-pub struct Shader
-{
- shader: gl::types::GLuint,
-}
-
-impl Shader
-{
- pub fn new(kind: Kind) -> Self
- {
- let shader = unsafe { gl::CreateShader(kind.into_gl()) };
-
- Self { shader }
- }
-
- pub fn set_source(&mut self, source: &str) -> Result<(), Error>
- {
- if !source.is_ascii() {
- return Err(Error::SourceNotAscii);
- }
-
- unsafe {
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl::ShaderSource(
- self.shader,
- 1,
- &source.as_ptr().cast(),
- &(source.len() as gl::types::GLint),
- );
- }
-
- Ok(())
- }
-
- pub fn compile(&mut self) -> Result<(), Error>
- {
- unsafe {
- gl::CompileShader(self.shader);
- }
-
- let mut compile_success = gl::types::GLint::default();
-
- unsafe {
- gl::GetShaderiv(self.shader, gl::COMPILE_STATUS, &mut compile_success);
- }
-
- if compile_success == 0 {
- let info_log = self.get_info_log();
-
- return Err(Error::CompileFailed(info_log));
- }
-
- Ok(())
- }
-
- fn get_info_log(&self) -> String
- {
- let mut buf = vec![gl::types::GLchar::default(); 512];
-
- unsafe {
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl::GetShaderInfoLog(
- self.shader,
- buf.len() as gl::types::GLsizei,
- null_mut(),
- buf.as_mut_ptr(),
- );
- }
-
- let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
-
- unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
- }
-}
-
-impl Drop for Shader
-{
- fn drop(&mut self)
- {
- unsafe {
- gl::DeleteShader(self.shader);
- }
- }
-}
-
-/// Shader kind.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum Kind
-{
- Vertex,
- Fragment,
-}
-
-impl Kind
-{
- fn into_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Vertex => gl::VERTEX_SHADER,
- Self::Fragment => gl::FRAGMENT_SHADER,
- }
- }
-}
-
-/// Shader program
-#[derive(Debug, PartialEq, Eq, Hash)]
-pub struct Program
-{
- program: gl::types::GLuint,
-}
-
-impl Program
-{
- pub fn new() -> Self
- {
- let program = unsafe { gl::CreateProgram() };
-
- Self { program }
- }
-
- pub fn attach(&mut self, shader: &Shader)
- {
- unsafe {
- gl::AttachShader(self.program, shader.shader);
- }
- }
-
- pub fn link(&mut self) -> Result<(), Error>
- {
- unsafe {
- gl::LinkProgram(self.program);
- }
-
- let mut link_success = gl::types::GLint::default();
-
- unsafe {
- gl::GetProgramiv(self.program, gl::LINK_STATUS, &mut link_success);
- }
-
- if link_success == 0 {
- let info_log = self.get_info_log();
-
- return Err(Error::CompileFailed(info_log));
- }
-
- Ok(())
- }
-
- pub fn activate(&self)
- {
- unsafe {
- gl::UseProgram(self.program);
- }
- }
-
- pub fn set_uniform(&mut self, name: &CStr, var: &impl UniformVariable)
- {
- let location = UniformLocation(unsafe {
- gl::GetUniformLocation(self.program, name.as_ptr().cast())
- });
-
- var.set(self, location);
- }
-
- fn get_info_log(&self) -> String
- {
- let mut buf = vec![gl::types::GLchar::default(); 512];
-
- unsafe {
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl::GetProgramInfoLog(
- self.program,
- buf.len() as gl::types::GLsizei,
- null_mut(),
- buf.as_mut_ptr(),
- );
- }
-
- let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
-
- unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
- }
-}
-
-impl Drop for Program
-{
- fn drop(&mut self)
- {
- unsafe {
- gl::DeleteProgram(self.program);
- }
- }
-}
-
-pub trait UniformVariable
-{
- fn set(&self, program: &mut Program, uniform_location: UniformLocation);
-}
-
-impl UniformVariable for f32
-{
- fn set(&self, program: &mut Program, uniform_location: UniformLocation)
- {
- unsafe {
- gl::ProgramUniform1f(program.program, uniform_location.0, *self);
- }
- }
-}
-
-impl UniformVariable for i32
-{
- fn set(&self, program: &mut Program, uniform_location: UniformLocation)
- {
- unsafe {
- gl::ProgramUniform1i(program.program, uniform_location.0, *self);
- }
- }
-}
-
-impl UniformVariable for Vec3<f32>
-{
- fn set(&self, program: &mut Program, uniform_location: UniformLocation)
- {
- unsafe {
- gl::ProgramUniform3f(
- program.program,
- uniform_location.0,
- self.x,
- self.y,
- self.z,
- );
- }
- }
-}
-
-impl UniformVariable for Matrix<f32, 4, 4>
-{
- fn set(&self, program: &mut Program, uniform_location: UniformLocation)
- {
- unsafe {
- gl::ProgramUniformMatrix4fv(
- program.program,
- uniform_location.0,
- 1,
- gl::FALSE,
- self.as_ptr(),
- );
- }
- }
-}
-
-#[derive(Debug)]
-pub struct UniformLocation(gl::types::GLint);
-
-/// Shader error.
-#[derive(Debug, thiserror::Error)]
-pub enum Error
-{
- #[error("All characters in source are not within the ASCII range")]
- SourceNotAscii,
-
- #[error("Failed to compile: {0}")]
- CompileFailed(String),
-}
diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs
deleted file mode 100644
index 80a5f37..0000000
--- a/engine/src/opengl/texture.rs
+++ /dev/null
@@ -1,169 +0,0 @@
-use crate::data_types::dimens::Dimens;
-
-#[derive(Debug)]
-pub struct Texture
-{
- texture: gl::types::GLuint,
-}
-
-impl Texture
-{
- pub fn new() -> Self
- {
- let mut texture = gl::types::GLuint::default();
-
- unsafe {
- gl::CreateTextures(gl::TEXTURE_2D, 1, &mut texture);
- };
-
- Self { texture }
- }
-
- pub fn bind_to_texture_unit(&self, texture_unit: u32)
- {
- unsafe {
- gl::BindTextureUnit(texture_unit, self.texture);
- }
- }
-
- pub fn generate(
- &mut self,
- dimens: Dimens<u32>,
- data: &[u8],
- pixel_data_format: PixelDataFormat,
- )
- {
- self.alloc_image(pixel_data_format, dimens, data);
-
- unsafe {
- gl::GenerateTextureMipmap(self.texture);
- }
- }
-
- pub fn set_wrap(&mut self, wrapping: Wrapping)
- {
- let wrapping_gl = wrapping as gl::types::GLenum;
-
- #[allow(clippy::cast_possible_wrap)]
- unsafe {
- gl::TextureParameteri(self.texture, gl::TEXTURE_WRAP_S, wrapping_gl as i32);
- gl::TextureParameteri(self.texture, gl::TEXTURE_WRAP_T, wrapping_gl as i32);
- }
- }
-
- pub fn set_magnifying_filter(&mut self, filtering: Filtering)
- {
- let filtering_gl = filtering as gl::types::GLenum;
-
- #[allow(clippy::cast_possible_wrap)]
- unsafe {
- gl::TextureParameteri(
- self.texture,
- gl::TEXTURE_MAG_FILTER,
- filtering_gl as i32,
- );
- }
- }
-
- pub fn set_minifying_filter(&mut self, filtering: Filtering)
- {
- let filtering_gl = filtering as gl::types::GLenum;
-
- #[allow(clippy::cast_possible_wrap)]
- unsafe {
- gl::TextureParameteri(
- self.texture,
- gl::TEXTURE_MIN_FILTER,
- filtering_gl as i32,
- );
- }
- }
-
- fn alloc_image(
- &mut self,
- pixel_data_format: PixelDataFormat,
- dimens: Dimens<u32>,
- data: &[u8],
- )
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::TextureStorage2D(
- self.texture,
- 1,
- pixel_data_format.to_sized_internal_format(),
- dimens.width as i32,
- dimens.height as i32,
- );
-
- #[allow(clippy::cast_possible_wrap)]
- gl::TextureSubImage2D(
- self.texture,
- 0,
- 0,
- 0,
- dimens.width as i32,
- dimens.height as i32,
- pixel_data_format.to_format(),
- gl::UNSIGNED_BYTE,
- data.as_ptr().cast(),
- );
- }
- }
-}
-
-impl Drop for Texture
-{
- fn drop(&mut self)
- {
- unsafe {
- gl::DeleteTextures(1, &self.texture);
- }
- }
-}
-
-/// Texture wrapping.
-#[derive(Debug, Clone, Copy)]
-#[repr(u32)]
-pub enum Wrapping
-{
- Repeat = gl::REPEAT,
- MirroredRepeat = gl::MIRRORED_REPEAT,
- ClampToEdge = gl::CLAMP_TO_EDGE,
- ClampToBorder = gl::CLAMP_TO_BORDER,
-}
-
-#[derive(Debug, Clone, Copy)]
-#[repr(u32)]
-pub enum Filtering
-{
- Nearest = gl::NEAREST,
- Linear = gl::LINEAR,
-}
-
-/// Texture pixel data format.
-#[derive(Debug, Clone, Copy)]
-pub enum PixelDataFormat
-{
- Rgb8,
- Rgba8,
-}
-
-impl PixelDataFormat
-{
- fn to_sized_internal_format(self) -> gl::types::GLenum
- {
- match self {
- Self::Rgb8 => gl::RGB8,
- Self::Rgba8 => gl::RGBA8,
- }
- }
-
- fn to_format(self) -> gl::types::GLenum
- {
- match self {
- Self::Rgb8 => gl::RGB,
- Self::Rgba8 => gl::RGBA,
- }
- }
-}
diff --git a/engine/src/opengl/util.rs b/engine/src/opengl/util.rs
deleted file mode 100644
index e60778f..0000000
--- a/engine/src/opengl/util.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-// May only be used when certain crate features are enabled
-#![allow(unused_macros, unused_imports)]
-
-macro_rules! gl_enum {
- (
- $visibility: vis enum $name: ident
- {$(
- $variant: ident = gl::$gl_enum: ident,
- )+}
- ) => {
- #[derive(Debug, Clone, Copy)]
- #[repr(u32)]
- $visibility enum $name
- {$(
- $variant = gl::$gl_enum,
- )+}
-
- impl $name {
- fn from_gl(num: gl::types::GLenum) -> Option<Self>
- {
- match num {
- $(gl::$gl_enum => Some(Self::$variant),)+
- _ => None
- }
- }
- }
- };
-}
-
-pub(crate) use gl_enum;
diff --git a/engine/src/opengl/vertex_array.rs b/engine/src/opengl/vertex_array.rs
deleted file mode 100644
index 1f8a870..0000000
--- a/engine/src/opengl/vertex_array.rs
+++ /dev/null
@@ -1,158 +0,0 @@
-use std::mem::size_of;
-
-use crate::opengl::buffer::Buffer;
-
-#[derive(Debug)]
-pub struct VertexArray
-{
- array: gl::types::GLuint,
-}
-
-impl VertexArray
-{
- pub fn new() -> Self
- {
- let mut array = 0;
-
- unsafe {
- gl::CreateVertexArrays(1, &mut array);
- }
-
- Self { array }
- }
-
- /// Draws the currently bound vertex array.
- pub fn draw_arrays(primitive_kind: PrimitiveKind, start_index: u32, cnt: u32)
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::DrawArrays(
- primitive_kind.into_gl(),
- start_index as gl::types::GLint,
- cnt as gl::types::GLsizei,
- );
- }
- }
-
- /// Draws the currently bound vertex array.
- pub fn draw_elements(primitive_kind: PrimitiveKind, offset: u32, cnt: u32)
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::DrawElements(
- primitive_kind.into_gl(),
- cnt as gl::types::GLsizei,
- gl::UNSIGNED_INT,
- (offset as gl::types::GLint) as *const _,
- );
- }
- }
-
- pub fn bind_element_buffer(&mut self, element_buffer: &Buffer<u32>)
- {
- unsafe {
- gl::VertexArrayElementBuffer(self.array, element_buffer.object());
- }
- }
-
- pub fn bind_vertex_buffer<VertexT>(
- &mut self,
- binding_index: u32,
- vertex_buffer: &Buffer<VertexT>,
- offset: isize,
- )
- {
- unsafe {
- gl::VertexArrayVertexBuffer(
- self.array,
- binding_index,
- vertex_buffer.object(),
- offset,
- size_of::<VertexT>() as i32,
- );
- }
- }
-
- pub fn enable_attrib(&mut self, attrib_index: u32)
- {
- unsafe {
- gl::EnableVertexArrayAttrib(self.array, attrib_index as gl::types::GLuint);
- }
- }
-
- pub fn set_attrib_format(
- &mut self,
- attrib_index: u32,
- data_type: DataType,
- normalized: bool,
- offset: u32,
- )
- {
- unsafe {
- #[allow(clippy::cast_possible_wrap)]
- gl::VertexArrayAttribFormat(
- self.array,
- attrib_index,
- data_type.size() as gl::types::GLint,
- data_type as u32,
- if normalized { gl::TRUE } else { gl::FALSE },
- offset,
- );
- }
- }
-
- /// Associate a vertex attribute and a vertex buffer binding.
- pub fn set_attrib_vertex_buf_binding(
- &mut self,
- attrib_index: u32,
- vertex_buf_binding_index: u32,
- )
- {
- unsafe {
- gl::VertexArrayAttribBinding(
- self.array,
- attrib_index,
- vertex_buf_binding_index,
- );
- }
- }
-
- pub fn bind(&self)
- {
- unsafe { gl::BindVertexArray(self.array) }
- }
-}
-
-#[derive(Debug)]
-pub enum PrimitiveKind
-{
- Triangles,
-}
-
-impl PrimitiveKind
-{
- fn into_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Triangles => gl::TRIANGLES,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-#[repr(u32)]
-pub enum DataType
-{
- Float = gl::FLOAT,
-}
-
-impl DataType
-{
- pub fn size(self) -> u32
- {
- #[allow(clippy::cast_possible_truncation)]
- match self {
- Self::Float => size_of::<gl::types::GLfloat>() as u32,
- }
- }
-}
diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs
index fcc96cc..6d25f6b 100644
--- a/engine/src/renderer.rs
+++ b/engine/src/renderer.rs
@@ -1,6 +1,8 @@
-use ecs::declare_entity;
use ecs::pair::{ChildOf, Pair};
-use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
+use ecs::phase::{Phase, POST_UPDATE as POST_UPDATE_PHASE};
+use ecs::{declare_entity, Component};
+
+use crate::builder;
pub mod opengl;
@@ -10,7 +12,69 @@ declare_entity!(
Phase,
Pair::builder()
.relation::<ChildOf>()
- .target_id(*UPDATE_PHASE)
+ .target_id(*POST_UPDATE_PHASE)
.build()
)
);
+
+builder! {
+/// Window graphics properties.
+#[builder(name=GraphicsPropertiesBuilder, derives=(Debug, Clone))]
+#[derive(Debug, Clone, Component)]
+#[non_exhaustive]
+pub struct GraphicsProperties
+{
+ /// Number of samples for multisampling. `None` means no multisampling.
+ #[builder(skip_generate_fn)]
+ pub multisampling_sample_cnt: Option<u8>,
+
+ /// Whether graphics API debugging is enabled.
+ pub debug: bool,
+
+ /// Whether depth testing is enabled
+ pub depth_test: bool,
+}
+}
+
+impl GraphicsProperties
+{
+ pub fn builder() -> GraphicsPropertiesBuilder
+ {
+ GraphicsPropertiesBuilder::default()
+ }
+}
+
+impl Default for GraphicsProperties
+{
+ fn default() -> Self
+ {
+ Self::builder().build()
+ }
+}
+
+impl GraphicsPropertiesBuilder
+{
+ pub fn multisampling_sample_cnt(mut self, multisampling_sample_cnt: u8) -> Self
+ {
+ self.multisampling_sample_cnt = Some(multisampling_sample_cnt);
+ self
+ }
+
+ pub fn no_multisampling(mut self) -> Self
+ {
+ self.multisampling_sample_cnt = None;
+ self
+ }
+}
+
+impl Default for GraphicsPropertiesBuilder
+{
+ fn default() -> Self
+ {
+ Self {
+ multisampling_sample_cnt: Some(8),
+ debug: false,
+ depth_test: true,
+ }
+ }
+}
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index b48bac5..fb7dfbe 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -1,84 +1,101 @@
//! OpenGL renderer.
+use std::any::type_name;
use std::collections::HashMap;
-use std::ffi::{c_void, CString};
+use std::ffi::CString;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::path::Path;
-use std::process::abort;
use ecs::actions::Actions;
-use ecs::component::local::Local;
use ecs::component::Handle as ComponentHandle;
-use ecs::phase::START as START_PHASE;
+use ecs::entity::obtainer::Obtainer as EntityObtainer;
+use ecs::event::component::{Changed, Removed};
+use ecs::pair::{ChildOf, Pair, Wildcard};
+use ecs::phase::Phase;
use ecs::query::term::Without;
use ecs::sole::Single;
-use ecs::system::initializable::Initializable;
-use ecs::system::Into as _;
-use ecs::{Component, Query};
-
-use crate::asset::{Assets, Id as AssetId};
-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::image::{ColorType as ImageColorType, Image};
-use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
-use crate::material::{Flags as MaterialFlags, Material};
-use crate::matrix::Matrix;
-use crate::mesh::Mesh;
-use crate::model::Model;
-use crate::opengl::buffer::{Buffer, Usage as BufferUsage};
-use crate::opengl::debug::{
- enable_debug_output,
+use ecs::system::observer::Observe;
+use ecs::{declare_entity, Component, Query};
+use glutin::display::GetGlDisplay;
+use glutin::prelude::GlDisplay;
+use glutin::surface::GlSurface;
+use opengl_bindings::debug::{
set_debug_message_callback,
set_debug_message_control,
MessageIdsAction,
MessageSeverity,
MessageSource,
MessageType,
+ SetDebugMessageControlError as GlSetDebugMessageControlError,
};
-use crate::opengl::glsl::{
- preprocess as glsl_preprocess,
- PreprocessingError as GlslPreprocessingError,
+use opengl_bindings::misc::{
+ clear_buffers,
+ enable,
+ set_enabled,
+ BufferClearMask,
+ Capability,
+ SetViewportError as GlSetViewportError,
};
-use crate::opengl::shader::{
+use opengl_bindings::shader::{
Error as GlShaderError,
Kind as ShaderKind,
Program as GlShaderProgram,
Shader as GlShader,
};
-use crate::opengl::texture::{
+use opengl_bindings::texture::{
Filtering as GlTextureFiltering,
+ GenerateError as GlTextureGenerateError,
PixelDataFormat as GlTexturePixelDataFormat,
Texture as GlTexture,
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,
- BufferClearMask,
- Capability,
- ContextFlags,
+use opengl_bindings::{ContextWithFns, CurrentContextWithFns};
+use safer_ffi::layout::ReprC;
+
+use crate::asset::{Assets, Id as AssetId};
+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::image::{ColorType as ImageColorType, Image};
+use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
+use crate::material::{Flags as MaterialFlags, Material};
+use crate::matrix::Matrix;
+use crate::model::Model;
+use crate::opengl::glsl::{
+ preprocess as glsl_preprocess,
+ PreprocessingError as GlslPreprocessingError,
};
use crate::projection::{ClipVolume, Projection};
-use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex};
-use crate::renderer::RENDER_PHASE;
+use crate::renderer::opengl::glutin_compat::{
+ DisplayBuilder,
+ Error as GlutinCompatError,
+};
+use crate::renderer::opengl::graphics_mesh::GraphicsMesh;
+use crate::renderer::{GraphicsProperties, RENDER_PHASE};
use crate::texture::{
Filtering as TextureFiltering,
Properties as TextureProperties,
Wrapping as TextureWrapping,
};
use crate::transform::{Scale, WorldPosition};
-use crate::util::{defer, Defer, RefOrValue};
+use crate::util::MapVec;
use crate::vector::{Vec2, Vec3};
-use crate::window::Window;
+use crate::windowing::window::{
+ Closed as WindowClosed,
+ CreationAttributes as WindowCreationAttributes,
+ CreationReady,
+ Window,
+};
+use crate::windowing::Context as WindowingContext;
+mod glutin_compat;
+mod graphics_mesh;
mod vertex;
const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0;
@@ -91,9 +108,66 @@ type RenderableEntity<'a> = (
Option<&'a WorldPosition>,
Option<&'a Scale>,
Option<&'a DrawFlags>,
- Option<&'a GlObjects>,
+ &'a [Pair<DataInGraphicsContext, Wildcard>],
+);
+
+declare_entity!(
+ pub POST_RENDER_PHASE,
+ (Phase, Pair::builder().relation::<ChildOf>().target_id(*RENDER_PHASE).build())
);
+#[derive(Debug, Component)]
+struct WithGraphicsContext;
+
+#[derive(Debug, Component)]
+struct WindowGlConfig
+{
+ gl_config: glutin::config::Config,
+}
+
+#[derive(Debug, Component)]
+struct WindowGraphicsSurface
+{
+ surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
+}
+
+#[derive(Component)]
+struct GraphicsContext
+{
+ context: ContextWithFns,
+ shader_program: Option<GlShaderProgram>,
+ textures_objs: HashMap<AssetId, GlTexture>,
+ default_1x1_texture_obj: Option<GlTexture>,
+ graphics_mesh_store: GraphicsMeshStore,
+}
+
+#[derive(Debug, Default)]
+struct GraphicsMeshStore
+{
+ graphics_meshes: MapVec<GraphicsMeshId, GraphicsMesh>,
+ next_id: GraphicsMeshId,
+}
+
+impl GraphicsMeshStore
+{
+ fn insert(&mut self, graphics_mesh: GraphicsMesh) -> GraphicsMeshId
+ {
+ let id = self.next_id;
+
+ self.graphics_meshes.insert(id, graphics_mesh);
+
+ self.next_id.inner += 1;
+
+ id
+ }
+}
+
+#[derive(Debug, Component)]
+struct DataInGraphicsContext
+{
+ graphics_mesh_id: GraphicsMeshId,
+}
+
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Extension {}
@@ -103,253 +177,877 @@ impl ecs::extension::Extension for Extension
fn collect(self, mut collector: ecs::extension::Collector<'_>)
{
collector.add_declared_entity(&RENDER_PHASE);
+ collector.add_declared_entity(&POST_RENDER_PHASE);
- collector.add_system(*START_PHASE, initialize);
+ collector.add_system(*RENDER_PHASE, render);
- collector.add_system(
- *RENDER_PHASE,
- render
- .into_system()
- .initialize((GlobalGlObjects::default(),)),
- );
+ collector.add_system(*POST_RENDER_PHASE, prepare_windows);
+ collector.add_system(*POST_RENDER_PHASE, init_window_graphics);
+
+ collector.add_observer(handle_model_removed);
+
+ collector.add_observer(handle_window_changed);
+ collector.add_observer(handle_window_removed);
}
}
-fn initialize(window: Single<Window>)
+#[tracing::instrument(skip_all)]
+fn handle_model_removed(observe: Observe<Pair<Removed, Model>>, mut actions: Actions)
{
- window
- .make_context_current()
- .expect("Failed to make window context current");
+ for evt_match in &observe {
+ let ent_id = evt_match.id();
- 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}",
- );
+ tracing::debug!(entity_id=%ent_id, "Cleaning up after model");
- abort();
- }
- });
+ let ent = evt_match.get_ent_infallible();
- if get_opengl_context_flags().contains(ContextFlags::DEBUG) {
- initialize_debug();
+ for data_in_graphics_ctx_pair in
+ ent.get_wildcard_pair_matches::<DataInGraphicsContext, Wildcard>()
+ {
+ actions.remove_components(ent_id, [data_in_graphics_ctx_pair.id()]);
+
+ let Some(graphics_context_ent) = data_in_graphics_ctx_pair.get_target_ent()
+ else {
+ tracing::trace!(
+ concat!(
+ "Graphics context referenced by pair ({}, {}) does not exist. ",
+ "Skipping cleanup of this model"
+ ),
+ type_name::<DataInGraphicsContext>(),
+ data_in_graphics_ctx_pair.id().target_entity()
+ );
+
+ continue;
+ };
+
+ let Some(data_in_graphics_ctx) =
+ data_in_graphics_ctx_pair.get_data_as_relation()
+ else {
+ unreachable!();
+ };
+
+ let Some(mut graphics_context) =
+ graphics_context_ent.get_mut::<GraphicsContext>()
+ else {
+ tracing::trace!(
+ "Graphics context entity {} does not have a {} component",
+ graphics_context_ent.uid(),
+ type_name::<GraphicsContext>()
+ );
+ continue;
+ };
+
+ graphics_context
+ .graphics_mesh_store
+ .graphics_meshes
+ .remove(data_in_graphics_ctx.graphics_mesh_id);
+ }
}
+}
+
+#[tracing::instrument(skip_all)]
+fn handle_window_changed(
+ observe: Observe<Pair<Changed, Window>>,
+ entity_obtainer: EntityObtainer,
+)
+{
+ for evt_match in &observe {
+ let window_ent = evt_match.get_ent_infallible();
+
+ tracing::trace!(
+ new_state = ?evt_match.get_changed_comp(),
+ "Handling window change"
+ );
+
+ let Some(window_graphics_surface) = window_ent.get::<WindowGraphicsSurface>()
+ else {
+ continue;
+ };
+
+ let Some(graphics_context_ent_id) = window_ent
+ .get_matching_components(
+ Pair::builder()
+ .relation::<WithGraphicsContext>()
+ .target_id(Wildcard::uid())
+ .build()
+ .id(),
+ )
+ .next()
+ .map(|comp_ref| comp_ref.id().target_entity())
+ else {
+ continue;
+ };
- let window_size = window.size().expect("Failed to get window size");
+ let Some(graphics_context_ent) =
+ entity_obtainer.get_entity(graphics_context_ent_id)
+ else {
+ tracing::error!("Graphics context entity does not exist");
+ continue;
+ };
- set_viewport(Vec2 { x: 0, y: 0 }, window_size);
+ let Some(graphics_context) = graphics_context_ent.get::<GraphicsContext>() else {
+ tracing::error!(
+ "Graphics context entity does not have a GraphicsContext component"
+ );
+ continue;
+ };
- window.set_framebuffer_size_callback(|new_window_size| {
- set_viewport(Vec2::ZERO, new_window_size);
- });
+ let Ok(current_graphics_context) = graphics_context
+ .context
+ .make_current(&window_graphics_surface.surface)
+ else {
+ tracing::error!("Failed to make graphics context current");
+ continue;
+ };
- enable(Capability::DepthTest);
- enable(Capability::MultiSample);
+ if let Err(err) = set_viewport(
+ &current_graphics_context,
+ Vec2::default(),
+ evt_match.get_changed_comp().inner_size(),
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
+ }
+ }
}
#[tracing::instrument(skip_all)]
-#[allow(clippy::too_many_arguments)]
-fn render(
- query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
- point_light_query: Query<(&PointLight, &WorldPosition)>,
- directional_lights: Query<(&DirectionalLight,)>,
- camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
- window: Single<Window>,
- global_light: Single<GlobalLight>,
- assets: Single<Assets>,
- mut gl_objects: Local<GlobalGlObjects>,
+fn handle_window_removed(observe: Observe<Pair<Removed, Window>>, mut actions: Actions)
+{
+ for evt_match in &observe {
+ let window_ent_id = evt_match.id();
+
+ let window_ent = evt_match.get_ent_infallible();
+
+ tracing::debug!(
+ entity_id = %window_ent_id,
+ title = %evt_match.get_removed_comp().title,
+ "Handling removal of window"
+ );
+
+ actions.remove_comps::<(WindowGraphicsSurface, WindowGlConfig)>(window_ent_id);
+
+ let Some(with_graphics_ctx_pair_handle) =
+ window_ent.get_first_wildcard_pair_match::<WithGraphicsContext, Wildcard>()
+ else {
+ tracing::warn!("Window entity is missing a (WithGraphicsContext, *) pair");
+ continue;
+ };
+
+ let graphics_context_ent_id = with_graphics_ctx_pair_handle.id().target_entity();
+
+ actions.remove_comps::<(GraphicsContext,)>(graphics_context_ent_id);
+
+ actions.remove_components(window_ent_id, [with_graphics_ctx_pair_handle.id()]);
+ }
+}
+
+#[derive(Debug, Component)]
+struct SetupFailed;
+
+// fn on_window_creation_attrs_added(
+// observe: Observe<Pair<Added, WindowCreationAttributes>>,
+// windowing: Single<Windowing>,
+// window_store: Single<WindowStore>,
+// mut actions: Actions,
+// )
+// {
+// for evt_match in &observe {
+// let Some(ent) = evt_match.get_entity() else {
+// unreachable!();
+// };
+//
+// if ent.has_component(WindowGlConfig::id()) ||
+// ent.has_component(WindowClosed::id()) || ent.has_component() {} }
+// }
+
+fn prepare_windows(
+ window_query: Query<
+ (
+ Option<&Window>,
+ &mut WindowCreationAttributes,
+ Option<&GraphicsProperties>,
+ ),
+ (
+ Without<CreationReady>,
+ Without<WindowGlConfig>,
+ Without<WindowClosed>,
+ Without<SetupFailed>,
+ ),
+ >,
+ windowing_context: Single<WindowingContext>,
mut actions: Actions,
)
{
- let Some((camera, camera_world_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 directional_lights = directional_lights.iter().collect::<Vec<_>>();
+ for (window_ent_id, (window, mut window_creation_attrs, graphics_props)) in
+ window_query.iter_with_euids()
+ {
+ tracing::debug!("Preparing window entity {window_ent_id} for use in rendering");
- let GlobalGlObjects {
- shader_program,
- textures: gl_textures,
- default_1x1_texture: default_1x1_gl_texture,
- } = &mut *gl_objects;
+ let mut glutin_config_template_builder =
+ glutin::config::ConfigTemplateBuilder::new();
- let shader_program =
- shader_program.get_or_insert_with(|| create_default_shader_program().unwrap());
+ let graphics_props = match graphics_props.as_ref() {
+ Some(graphics_props) => &*graphics_props,
+ None => {
+ actions.add_components(window_ent_id, (GraphicsProperties::default(),));
- clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH);
+ &GraphicsProperties::default()
+ }
+ };
- 'subject_loop: for (
- euid,
- (model, material_flags, position, scale, draw_flags, gl_objects),
- ) in query.iter_with_euids()
+ if let Some(multisampling_sample_cnt) = graphics_props.multisampling_sample_cnt {
+ glutin_config_template_builder = glutin_config_template_builder
+ .with_multisampling(multisampling_sample_cnt);
+ }
+
+ let window_handle = match window
+ .as_ref()
+ .map(|window| unsafe {
+ windowing_context.get_window_as_handle(&window.wid())
+ })
+ .flatten()
+ .transpose()
+ {
+ Ok(window_handle) => window_handle,
+ Err(err) => {
+ tracing::error!("Failed to get window handle: {err}");
+ actions.add_components(window_ent_id, (SetupFailed,));
+ continue;
+ }
+ };
+
+ let (new_window_creation_attrs, gl_config) = match DisplayBuilder::new()
+ .with_window_attributes(window_creation_attrs.clone())
+ .build(
+ window_handle,
+ &display_handle,
+ glutin_config_template_builder,
+ |mut cfgs| cfgs.next(),
+ ) {
+ Ok((new_window_creation_attrs, gl_config)) => {
+ (new_window_creation_attrs, gl_config)
+ }
+ Err(GlutinCompatError::WindowRequired) => {
+ actions.add_components(window_ent_id, (CreationReady,));
+ continue;
+ }
+ Err(err) => {
+ tracing::error!("Failed to create platform graphics display: {err}");
+ actions.add_components(window_ent_id, (SetupFailed,));
+ continue;
+ }
+ };
+
+ *window_creation_attrs = new_window_creation_attrs;
+
+ // let gl_config_template = glutin_config_template_builder.build();
+ //
+ // let display = match glutin_winit_compat::create_display(
+ // unsafe { engine_display.as_display_handle() },
+ // glutin_winit_compat::ApiPreference::default(),
+ // None,
+ // ) {
+ // Ok(gl_display) => gl_display,
+ // Err(err) => {
+ // tracing::error!("Failed to create graphics platform display: {err}");
+ // continue;
+ // }
+ // };
+ //
+ // let mut gl_configs = match unsafe { display.find_configs(gl_config_template) }
+ // { Ok(gl_configs) => gl_configs,
+ // Err(err) => {
+ // tracing::error!("Failed to find GL configs: {err:?}");
+ // continue;
+ // }
+ // };
+ //
+ // let Some(first_gl_config) = gl_configs.next() else {
+ // tracing::error!("No matching GL configuration exists");
+ // continue;
+ // };
+ //
+ // *window_creation_attrs = finalize_window_creation_attrs(
+ // window_creation_attrs.clone(),
+ // &first_gl_config,
+ // );
+
+ actions.add_components(window_ent_id, (WindowGlConfig { gl_config },));
+
+ if window.is_none() {
+ actions.add_components(window_ent_id, (CreationReady,));
+ }
+ }
+}
+
+#[tracing::instrument(skip_all)]
+fn init_window_graphics(
+ window_query: Query<
+ (&Window, &WindowGlConfig, &GraphicsProperties),
+ (Without<WindowGraphicsSurface>, Without<SetupFailed>),
+ >,
+ mut actions: Actions,
+ windowing_context: Single<WindowingContext>,
+)
+{
+ for (window_ent_id, (window, window_gl_config, graphics_props)) in
+ window_query.iter_with_euids()
{
- let Some(model_data) = assets.get(&model.asset_handle) else {
- tracing::trace!("Missing model asset");
+ tracing::info!("Initializing graphics for window {window_ent_id}");
+
+ 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"
+ );
+ actions.add_components(window_ent_id, (SetupFailed,));
+ continue;
+ }
+ Err(err) => {
+ tracing::error!("Failed to get window handle: {err}");
+ actions.add_components(window_ent_id, (SetupFailed,));
+ continue;
+ }
+ };
+
+ let Some(window_inner_size) = window.inner_size().try_into_nonzero() else {
+ tracing::error!(
+ "Cannot create a surface for a window with a width/height of 0",
+ );
continue;
};
- let material_flags = material_flags
- .map(|material_flags| material_flags.clone())
- .unwrap_or_default();
+ let surface = match unsafe {
+ display.create_window_surface(
+ &window_gl_config.gl_config,
+ &glutin::surface::SurfaceAttributesBuilder::<
+ glutin::surface::WindowSurface,
+ >::new()
+ .build(
+ window_handle.as_raw(),
+ window_inner_size.width,
+ window_inner_size.height,
+ ),
+ )
+ } {
+ Ok(surface) => surface,
+ Err(err) => {
+ tracing::error!("Failed to create window surface: {err}");
+ continue;
+ }
+ };
- let gl_objs = match gl_objects.as_deref() {
- Some(gl_objs) => RefOrValue::Ref(gl_objs),
- None => RefOrValue::Value(Some(GlObjects::new(&model_data.mesh))),
+ let context = match unsafe {
+ display.create_context(
+ &window_gl_config.gl_config,
+ &glutin::context::ContextAttributesBuilder::new()
+ .with_debug(graphics_props.debug)
+ .build(Some(window_handle.as_raw())),
+ )
+ } {
+ Ok(context) => context,
+ Err(err) => {
+ tracing::error!("Failed to create graphics context: {err}");
+ continue;
+ }
};
- defer!(|gl_objs| {
- if let RefOrValue::Value(opt_gl_objs) = gl_objs {
- actions.add_components(euid, (opt_gl_objs.take().unwrap(),));
- };
- });
+ let context = match ContextWithFns::new(context, &surface) {
+ Ok(context) => context,
+ Err(err) => {
+ tracing::error!("Failed to create graphics context: {err}");
+ continue;
+ }
+ };
- 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_world_pos,
- window.size().expect("Failed to get window size"),
- );
+ let Ok(current_graphics_context) = context.make_current(&surface) else {
+ tracing::error!("Failed to make graphics context current");
+ continue;
+ };
- if model_data.materials.len() > 1 {
- tracing::warn!(concat!(
- "Multiple model materials are not supported ",
- "so only the first material will be used"
- ));
+ if let Err(err) = set_viewport(
+ &current_graphics_context,
+ Vec2 { x: 0, y: 0 },
+ window.inner_size(),
+ ) {
+ tracing::error!("Failed to set viewport: {err}");
}
- let material = match model_data.materials.values().next() {
- Some(material) => material,
- None => {
- tracing::warn!("Model has no materials. Using default material");
+ set_enabled(
+ &current_graphics_context,
+ Capability::DepthTest,
+ graphics_props.depth_test,
+ );
- &Material::default()
+ set_enabled(
+ &current_graphics_context,
+ Capability::MultiSample,
+ graphics_props.multisampling_sample_cnt.is_some(),
+ );
+
+ if graphics_props.debug {
+ enable(&current_graphics_context, Capability::DebugOutput);
+ enable(
+ &current_graphics_context,
+ Capability::DebugOutputSynchronous,
+ );
+
+ set_debug_message_callback(
+ &current_graphics_context,
+ opengl_debug_message_cb,
+ );
+
+ match set_debug_message_control(
+ &current_graphics_context,
+ None,
+ None,
+ None,
+ &[],
+ MessageIdsAction::Disable,
+ ) {
+ Ok(()) => {}
+ Err(GlSetDebugMessageControlError::TooManyIds {
+ id_cnt: _,
+ max_id_cnt: _,
+ }) => {
+ unreachable!() // No ids are given
+ }
}
- };
+ }
- apply_light(
- &material,
- &material_flags,
- &global_light,
- shader_program,
- (point_light_query.iter(), point_light_query.iter().count()),
- directional_lights
- .iter()
- .map(|(dir_light,)| &**dir_light)
- .collect::<Vec<_>>()
- .as_slice(),
- &camera_world_pos,
+ let graphics_context_ent_id = actions.spawn((GraphicsContext {
+ context,
+ shader_program: None,
+ textures_objs: HashMap::new(),
+ default_1x1_texture_obj: None,
+ graphics_mesh_store: GraphicsMeshStore::default(),
+ },));
+
+ actions.add_components(
+ window_ent_id,
+ (
+ WindowGraphicsSurface { surface },
+ Pair::builder()
+ .relation::<WithGraphicsContext>()
+ .target_id(graphics_context_ent_id)
+ .build(),
+ ),
);
+ }
+}
- let material_texture_maps = [
- (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT),
- (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT),
- (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT),
- ];
+#[tracing::instrument(skip_all)]
+#[allow(clippy::too_many_arguments)]
+fn render(
+ query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
+ point_light_query: Query<(&PointLight, &WorldPosition)>,
+ directional_lights: Query<(&DirectionalLight,)>,
+ camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
+ window_query: Query<(
+ &Window,
+ &WindowGraphicsSurface,
+ &GraphicsProperties,
+ Pair<WithGraphicsContext, Wildcard>,
+ )>,
+ global_light: Single<GlobalLight>,
+ assets: Single<Assets>,
+ mut actions: Actions,
+)
+{
+ for (
+ window_ent_id,
+ (window, window_graphics_surface, window_graphics_props, graphics_context_pair),
+ ) in window_query.iter_with_euids()
+ {
+ let Some(graphics_context_ent) = graphics_context_pair.get_target_ent() else {
+ tracing::error!("Window's associated graphics context entity does not exist");
+ actions.remove_components(window_ent_id, [graphics_context_pair.id()]);
+ continue;
+ };
- for (texture, texture_unit) in material_texture_maps {
- let Some(texture) = texture else {
- let gl_texture = default_1x1_gl_texture.get_or_insert_with(|| {
- create_gl_texture(
- &Image::from_color(1, Color::WHITE_U8),
- &TextureProperties::default(),
- )
- });
+ let Some(mut graphics_context) =
+ graphics_context_ent.get_mut::<GraphicsContext>()
+ else {
+ tracing::error!(
+ "Graphics context entity does not have a GraphicsContext component"
+ );
+ return;
+ };
- gl_texture.bind_to_texture_unit(texture_unit);
+ let GraphicsContext {
+ ref context,
+ ref mut shader_program,
+ ref mut textures_objs,
+ ref mut default_1x1_texture_obj,
+ ref mut graphics_mesh_store,
+ } = *graphics_context;
+
+ let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else {
+ tracing::warn!("No current camera. Nothing will be rendered");
+ return;
+ };
+
+ let Ok(current_graphics_context) =
+ context.make_current(&window_graphics_surface.surface)
+ else {
+ tracing::error!("Failed to make graphics context current");
+ continue;
+ };
+
+ let directional_lights = directional_lights.iter().collect::<Vec<_>>();
+
+ let shader_program = shader_program.get_or_insert_with(|| {
+ create_default_shader_program(&current_graphics_context).unwrap()
+ });
+
+ let mut clear_mask = BufferClearMask::COLOR;
+
+ clear_mask.set(BufferClearMask::DEPTH, window_graphics_props.depth_test);
+ clear_buffers(&current_graphics_context, clear_mask);
+
+ for (
+ euid,
+ (
+ model,
+ material_flags,
+ position,
+ scale,
+ draw_flags,
+ data_in_graphics_ctx_pairs,
+ ),
+ ) in query.iter_with_euids()
+ {
+ let Some(model_data) = assets.get(&model.asset_handle) else {
+ tracing::trace!("Missing model asset");
continue;
};
- let texture_image_asset_id = texture.asset_handle.id();
+ let material_flags = material_flags
+ .map(|material_flags| material_flags.clone())
+ .unwrap_or_default();
+
+ let graphics_mesh_id = match data_in_graphics_ctx_pairs
+ .get_with_target_id(graphics_context_ent.uid())
+ {
+ Some(data_in_graphics_ctx_pair) => {
+ let Some(data_in_graphics_ctx) =
+ data_in_graphics_ctx_pair.get_data::<DataInGraphicsContext>()
+ else {
+ tracing::warn!(
+ concat!(
+ "Pair with relation {} ({}) has no data or data with a ",
+ "wrong type. This pair will be removed"
+ ),
+ type_name::<DataInGraphicsContext>(),
+ data_in_graphics_ctx_pair.id()
+ );
+
+ actions.remove_components(euid, [data_in_graphics_ctx_pair.id()]);
+ continue;
+ };
- let gl_texture = match gl_textures.get(&texture_image_asset_id) {
- Some(gl_texture) => gl_texture,
+ data_in_graphics_ctx.graphics_mesh_id
+ }
None => {
- let Some(image) = assets.get::<Image>(&texture.asset_handle) else {
- tracing::trace!("Missing texture asset");
- continue 'subject_loop;
+ let graphics_mesh = match GraphicsMesh::new(
+ &current_graphics_context,
+ &model_data.mesh,
+ ) {
+ Ok(graphics_mesh) => graphics_mesh,
+ Err(err) => {
+ tracing::error!(
+ "Failed to create {}: {err}",
+ type_name::<GraphicsMesh>()
+ );
+
+ // This system should not try again
+ actions.add_components(euid, (NoDraw,));
+
+ continue;
+ }
};
- gl_textures.insert(
- texture_image_asset_id,
- create_gl_texture(image, &texture.properties),
+ let graphics_mesh_id = graphics_mesh_store.insert(graphics_mesh);
+
+ actions.add_components(
+ euid,
+ (Pair::builder()
+ .relation_as_data(DataInGraphicsContext { graphics_mesh_id })
+ .target_id(graphics_context_ent.uid())
+ .build(),),
);
- gl_textures
- .get(&texture.asset_handle.id())
- .expect("Not possible")
+ graphics_mesh_id
}
};
- gl_texture.bind_to_texture_unit(texture_unit);
- }
-
- shader_program.activate();
+ let Some(graphics_mesh) =
+ graphics_mesh_store.graphics_meshes.get(&graphics_mesh_id)
+ else {
+ tracing::error!("Graphics mesh with ID: {graphics_mesh_id:?} not found");
+ 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,
+ apply_transformation_matrices(
+ &current_graphics_context,
+ Transformation {
+ position: position.map(|pos| *pos).unwrap_or_default().position,
+ scale: scale.map(|scale| *scale).unwrap_or_default().scale,
+ },
+ shader_program,
+ &camera,
+ &camera_world_pos,
+ window.inner_size(),
);
- }
- draw_mesh(gl_objs.get().unwrap());
+ if model_data.materials.len() > 1 {
+ tracing::warn!(concat!(
+ "Multiple model materials are not supported ",
+ "so only the first material will be used"
+ ));
+ }
- if draw_flags.is_some() {
- let default_polygon_mode_config = PolygonModeConfig::default();
+ let material = match model_data.materials.values().next() {
+ Some(material) => material,
+ None => {
+ tracing::warn!("Model has no materials. Using default material");
- crate::opengl::set_polygon_mode(
- default_polygon_mode_config.face,
- default_polygon_mode_config.mode,
+ &Material::default()
+ }
+ };
+
+ apply_light(
+ &current_graphics_context,
+ &material,
+ &material_flags,
+ &global_light,
+ shader_program,
+ (point_light_query.iter(), point_light_query.iter().count()),
+ directional_lights
+ .iter()
+ .map(|(dir_light,)| &**dir_light)
+ .collect::<Vec<_>>()
+ .as_slice(),
+ &camera_world_pos,
);
+
+ match create_bind_material_textures(
+ &current_graphics_context,
+ &material,
+ &assets,
+ textures_objs,
+ default_1x1_texture_obj,
+ ) {
+ Ok(()) => {}
+ Err(CreateBindMaterialTexturesError::MissingTextureAsset) => {
+ continue;
+ }
+ Err(
+ err @ CreateBindMaterialTexturesError::CreateTextureFailed { .. },
+ ) => {
+ tracing::error!(
+ "Creating &/ binding material textures failed: {err}"
+ );
+
+ // This system should not try again
+ actions.add_components(euid, (NoDraw,));
+
+ continue;
+ }
+ }
+
+ shader_program.activate(&current_graphics_context);
+
+ if let Some(draw_flags) = &draw_flags {
+ opengl_bindings::misc::set_polygon_mode(
+ &current_graphics_context,
+ draw_flags.polygon_mode_config.face,
+ draw_flags.polygon_mode_config.mode,
+ );
+ }
+
+ if let Err(err) = draw_mesh(&current_graphics_context, &graphics_mesh) {
+ tracing::error!(
+ entity_id = %euid,
+ graphics_context_entity_id = %graphics_context_ent.uid(),
+ "Failed to draw mesh: {err}",
+ );
+
+ // This system should not try again
+ actions.add_components(euid, (NoDraw,));
+
+ continue;
+ };
+
+ if draw_flags.is_some() {
+ let default_polygon_mode_config = PolygonModeConfig::default();
+
+ opengl_bindings::misc::set_polygon_mode(
+ &current_graphics_context,
+ default_polygon_mode_config.face,
+ default_polygon_mode_config.mode,
+ );
+ }
+ }
+
+ if let Err(err) = window_graphics_surface
+ .surface
+ .swap_buffers(context.context())
+ {
+ tracing::error!("Failed to swap buffers: {err}");
}
}
}
-#[derive(Debug, Default, Component)]
-struct GlobalGlObjects
+fn create_default_texture(current_context: &CurrentContextWithFns<'_>) -> GlTexture
{
- shader_program: Option<GlShaderProgram>,
- textures: HashMap<AssetId, GlTexture>,
- default_1x1_texture: Option<GlTexture>,
+ match create_gl_texture(
+ current_context,
+ &Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8),
+ &TextureProperties::default(),
+ ) {
+ Ok(gl_texture) => gl_texture,
+ Err(
+ GlTextureGenerateError::SizeWidthValueTooLarge { value: _, max_value: _ }
+ | GlTextureGenerateError::SizeHeightValueTooLarge { value: _, max_value: _ },
+ ) => unreachable!(),
+ }
}
-fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
+fn create_bind_material_textures(
+ current_context: &CurrentContextWithFns<'_>,
+ material: &Material,
+ assets: &Assets,
+ texture_objs: &mut HashMap<AssetId, GlTexture>,
+ default_1x1_texture_obj: &mut Option<GlTexture>,
+) -> Result<(), CreateBindMaterialTexturesError>
{
- crate::opengl::set_viewport(position, size);
+ let material_texture_maps = [
+ (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT),
+ (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT),
+ (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT),
+ ];
+
+ for (texture, texture_unit) in material_texture_maps {
+ let Some(texture) = texture else {
+ let gl_texture = default_1x1_texture_obj
+ .get_or_insert_with(|| create_default_texture(current_context));
+
+ gl_texture.bind_to_texture_unit(current_context, texture_unit);
+
+ continue;
+ };
+
+ let texture_image_asset_id = texture.asset_handle.id();
+
+ let gl_texture = match texture_objs.get(&texture_image_asset_id) {
+ Some(gl_texture) => gl_texture,
+ None => {
+ let Some(image) = assets.get::<Image>(&texture.asset_handle) else {
+ tracing::trace!(handle=?texture.asset_handle, "Missing texture asset");
+ return Err(CreateBindMaterialTexturesError::MissingTextureAsset);
+ };
+
+ texture_objs.entry(texture_image_asset_id).or_insert(
+ create_gl_texture(current_context, image, &texture.properties)
+ .map_err(|err| {
+ CreateBindMaterialTexturesError::CreateTextureFailed {
+ err,
+ image_asset_id: texture_image_asset_id,
+ }
+ })?,
+ )
+ }
+ };
+
+ gl_texture.bind_to_texture_unit(current_context, texture_unit);
+ }
+
+ Ok(())
}
-fn initialize_debug()
+#[derive(Debug, thiserror::Error)]
+enum CreateBindMaterialTexturesError
{
- enable_debug_output();
+ #[error("Missing texture asset")]
+ MissingTextureAsset,
- set_debug_message_callback(opengl_debug_message_cb);
- set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable);
+ #[error("Failed to create texture from image asset with ID {image_asset_id:?}")]
+ CreateTextureFailed
+ {
+ #[source]
+ err: GlTextureGenerateError,
+ image_asset_id: AssetId,
+ },
}
-fn draw_mesh(gl_objects: &GlObjects)
+fn set_viewport(
+ current_context: &CurrentContextWithFns<'_>,
+ position: Vec2<u32>,
+ size: &Dimens<u32>,
+) -> Result<(), GlSetViewportError>
{
- gl_objects.vertex_arr.bind();
+ let position =
+ opengl_bindings::data_types::Vec2::<u32> { x: position.x, y: position.y };
- if gl_objects.index_buffer.is_some() {
- VertexArray::draw_elements(PrimitiveKind::Triangles, 0, gl_objects.element_cnt);
+ let size = opengl_bindings::data_types::Dimens::<u32> {
+ width: size.width,
+ height: size.height,
+ };
+
+ opengl_bindings::misc::set_viewport(current_context, &position, &size)
+}
+
+fn draw_mesh(
+ current_context: &CurrentContextWithFns<'_>,
+ graphics_mesh: &GraphicsMesh,
+) -> Result<(), GlDrawError>
+{
+ graphics_mesh.vertex_arr.bind(current_context);
+
+ if graphics_mesh.index_buffer.is_some() {
+ VertexArray::draw_elements(
+ current_context,
+ PrimitiveKind::Triangles,
+ 0,
+ graphics_mesh.element_cnt,
+ )?;
} else {
- VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, gl_objects.element_cnt);
+ VertexArray::draw_arrays(
+ current_context,
+ PrimitiveKind::Triangles,
+ 0,
+ graphics_mesh.element_cnt,
+ )?;
}
+
+ Ok(())
}
-fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> GlTexture
+fn create_gl_texture(
+ current_context: &CurrentContextWithFns<'_>,
+ image: &Image,
+ texture_properties: &TextureProperties,
+) -> Result<GlTexture, GlTextureGenerateError>
{
- let mut gl_texture = GlTexture::new();
+ let gl_texture = GlTexture::new(current_context);
gl_texture.generate(
- image.dimensions(),
+ current_context,
+ &image.dimensions().into(),
image.as_bytes(),
match image.color_type() {
ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8,
@@ -358,19 +1056,24 @@ fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> G
unimplemented!();
}
},
- );
+ )?;
- gl_texture.set_wrap(texture_wrapping_to_gl(texture_properties.wrap));
+ gl_texture.set_wrap(
+ current_context,
+ texture_wrapping_to_gl(texture_properties.wrap),
+ );
- gl_texture.set_magnifying_filter(texture_filtering_to_gl(
- texture_properties.magnifying_filter,
- ));
+ gl_texture.set_magnifying_filter(
+ current_context,
+ texture_filtering_to_gl(texture_properties.magnifying_filter),
+ );
- gl_texture.set_minifying_filter(texture_filtering_to_gl(
- texture_properties.minifying_filter,
- ));
+ gl_texture.set_minifying_filter(
+ current_context,
+ texture_filtering_to_gl(texture_properties.minifying_filter),
+ );
- gl_texture
+ Ok(gl_texture)
}
const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl");
@@ -379,32 +1082,34 @@ const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl")
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");
-fn create_default_shader_program() -> Result<GlShaderProgram, CreateShaderError>
+fn create_default_shader_program(
+ current_context: &CurrentContextWithFns<'_>,
+) -> Result<GlShaderProgram, CreateShaderError>
{
- let mut vertex_shader = GlShader::new(ShaderKind::Vertex);
+ let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex);
- vertex_shader.set_source(&*glsl_preprocess(
- VERTEX_GLSL_SHADER_SRC,
- &get_glsl_shader_content,
- )?)?;
+ vertex_shader.set_source(
+ current_context,
+ &*glsl_preprocess(VERTEX_GLSL_SHADER_SRC, &get_glsl_shader_content)?,
+ )?;
- vertex_shader.compile()?;
+ vertex_shader.compile(current_context)?;
- let mut fragment_shader = GlShader::new(ShaderKind::Fragment);
+ let fragment_shader = GlShader::new(current_context, ShaderKind::Fragment);
- fragment_shader.set_source(&*glsl_preprocess(
- FRAGMENT_GLSL_SHADER_SRC,
- &get_glsl_shader_content,
- )?)?;
+ fragment_shader.set_source(
+ current_context,
+ &*glsl_preprocess(FRAGMENT_GLSL_SHADER_SRC, &get_glsl_shader_content)?,
+ )?;
- fragment_shader.compile()?;
+ fragment_shader.compile(current_context)?;
- let mut gl_shader_program = GlShaderProgram::new();
+ let gl_shader_program = GlShaderProgram::new(current_context);
- gl_shader_program.attach(&vertex_shader);
- gl_shader_program.attach(&fragment_shader);
+ gl_shader_program.attach(current_context, &vertex_shader);
+ gl_shader_program.attach(current_context, &fragment_shader);
- gl_shader_program.link()?;
+ gl_shader_program.link(current_context)?;
Ok(gl_shader_program)
}
@@ -435,108 +1140,36 @@ fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error>
))
}
-#[derive(Debug, Component)]
-struct GlObjects
-{
- /// 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,
-
- vertex_arr: VertexArray,
-}
-
-impl GlObjects
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct GraphicsMeshId
{
- #[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_mapped(mesh.vertices(), BufferUsage::Static, |vertex| {
- Vertex {
- pos: vertex.pos,
- texture_coords: vertex.texture_coords,
- normal: vertex.normal,
- }
- });
-
- vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0);
-
- let mut offset = 0u32;
-
- for attrib in Vertex::attrs() {
- vertex_arr.enable_attrib(attrib.index);
-
- vertex_arr.set_attrib_format(
- attrib.index,
- match attrib.component_type {
- AttributeComponentType::Float => VertexArrayDataType::Float,
- },
- false,
- offset,
- );
-
- 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,
- }
- }
+ inner: usize,
}
fn apply_transformation_matrices(
+ current_context: &CurrentContextWithFns<'_>,
transformation: Transformation,
gl_shader_program: &mut GlShaderProgram,
camera: &Camera,
camera_world_pos: &WorldPosition,
- window_size: Dimens<u32>,
+ window_size: &Dimens<u32>,
)
{
- gl_shader_program
- .set_uniform(c"model", &create_transformation_matrix(transformation));
+ gl_shader_program.set_uniform(
+ current_context,
+ c"model",
+ &opengl_bindings::data_types::Matrix {
+ items: create_transformation_matrix(transformation).items,
+ },
+ );
let view_matrix = create_view_matrix(camera, &camera_world_pos.position);
- gl_shader_program.set_uniform(c"view", &view_matrix);
+ gl_shader_program.set_uniform(
+ current_context,
+ c"view",
+ &opengl_bindings::data_types::Matrix { items: view_matrix.items },
+ );
#[allow(clippy::cast_precision_loss)]
let proj_matrix = match &camera.projection {
@@ -548,10 +1181,15 @@ fn apply_transformation_matrices(
.to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne),
};
- gl_shader_program.set_uniform(c"projection", &proj_matrix);
+ gl_shader_program.set_uniform(
+ current_context,
+ c"projection",
+ &opengl_bindings::data_types::Matrix { items: proj_matrix.items },
+ );
}
fn apply_light<'point_light>(
+ current_context: &CurrentContextWithFns<'_>,
material: &Material,
material_flags: &MaterialFlags,
global_light: &GlobalLight,
@@ -580,16 +1218,20 @@ fn apply_light<'point_light>(
);
for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
+ let direction: opengl_bindings::data_types::Vec3<_> = dir_light.direction.into();
+
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(
"directional_lights",
dir_light_index,
"direction",
),
- &dir_light.direction,
+ &direction,
);
set_light_phong_uniforms(
+ current_context,
gl_shader_program,
"directional_lights",
dir_light_index,
@@ -599,18 +1241,26 @@ fn apply_light<'point_light>(
// There probably won't be more than 2147483648 directional lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program
- .set_uniform(c"directional_light_cnt", &(directional_lights.len() as i32));
+ gl_shader_program.set_uniform(
+ current_context,
+ c"directional_light_cnt",
+ &(directional_lights.len() as i32),
+ );
for (point_light_index, (point_light, point_light_world_pos)) in
point_light_iter.enumerate()
{
+ let pos: opengl_bindings::data_types::Vec3<_> =
+ (point_light_world_pos.position + point_light.local_position).into();
+
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name("point_lights", point_light_index, "position"),
- &(point_light_world_pos.position + point_light.local_position),
+ &pos,
);
set_light_phong_uniforms(
+ current_context,
gl_shader_program,
"point_lights",
point_light_index,
@@ -618,6 +1268,7 @@ fn apply_light<'point_light>(
);
set_light_attenuation_uniforms(
+ current_context,
gl_shader_program,
"point_lights",
point_light_index,
@@ -627,44 +1278,67 @@ fn apply_light<'point_light>(
// There probably won't be more than 2147483648 point lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform(c"point_light_cnt", &(point_light_cnt as i32));
-
gl_shader_program.set_uniform(
- c"material.ambient",
- &Vec3::from(if material_flags.use_ambient_color {
+ current_context,
+ c"point_light_cnt",
+ &(point_light_cnt as i32),
+ );
+
+ let ambient: opengl_bindings::data_types::Vec3<_> =
+ Vec3::from(if material_flags.use_ambient_color {
material.ambient.clone()
} else {
global_light.ambient.clone()
- }),
- );
+ })
+ .into();
+
+ gl_shader_program.set_uniform(current_context, c"material.ambient", &ambient);
- gl_shader_program
- .set_uniform(c"material.diffuse", &Vec3::from(material.diffuse.clone()));
+ let diffuse: opengl_bindings::data_types::Vec3<_> =
+ Vec3::from(material.diffuse.clone()).into();
+
+ gl_shader_program.set_uniform(current_context, c"material.diffuse", &diffuse);
+
+ let specular: opengl_bindings::data_types::Vec3<_> =
+ Vec3::from(material.specular.clone()).into();
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program
- .set_uniform(c"material.specular", &Vec3::from(material.specular.clone()));
+ gl_shader_program.set_uniform(current_context, c"material.specular", &specular);
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program
- .set_uniform(c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32));
+ gl_shader_program.set_uniform(
+ current_context,
+ c"material.ambient_map",
+ &(AMBIENT_MAP_TEXTURE_UNIT as i32),
+ );
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program
- .set_uniform(c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32));
+ gl_shader_program.set_uniform(
+ current_context,
+ c"material.diffuse_map",
+ &(DIFFUSE_MAP_TEXTURE_UNIT as i32),
+ );
#[allow(clippy::cast_possible_wrap)]
gl_shader_program.set_uniform(
+ current_context,
c"material.specular_map",
&(SPECULAR_MAP_TEXTURE_UNIT as i32),
);
- gl_shader_program.set_uniform(c"material.shininess", &material.shininess);
+ gl_shader_program.set_uniform(
+ current_context,
+ c"material.shininess",
+ &material.shininess,
+ );
+
+ let view_pos: opengl_bindings::data_types::Vec3<_> = camera_world_pos.position.into();
- gl_shader_program.set_uniform(c"view_pos", &camera_world_pos.position);
+ gl_shader_program.set_uniform(current_context, c"view_pos", &view_pos);
}
fn set_light_attenuation_uniforms(
+ current_context: &CurrentContextWithFns<'_>,
gl_shader_program: &mut GlShaderProgram,
light_array: &str,
light_index: usize,
@@ -672,6 +1346,7 @@ fn set_light_attenuation_uniforms(
)
{
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(
light_array,
light_index,
@@ -681,11 +1356,13 @@ fn set_light_attenuation_uniforms(
);
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
&light.attenuation_params.linear,
);
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(
light_array,
light_index,
@@ -696,6 +1373,7 @@ fn set_light_attenuation_uniforms(
}
fn set_light_phong_uniforms(
+ current_context: &CurrentContextWithFns<'_>,
gl_shader_program: &mut GlShaderProgram,
light_array: &str,
light_index: usize,
@@ -703,13 +1381,23 @@ fn set_light_phong_uniforms(
)
{
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(light_array, light_index, "phong.diffuse"),
- &Vec3::from(light.diffuse().clone()),
+ &opengl_bindings::data_types::Vec3 {
+ x: light.diffuse().red,
+ y: light.diffuse().green,
+ z: light.diffuse().blue,
+ },
);
gl_shader_program.set_uniform(
+ current_context,
&create_light_uniform_name(light_array, light_index, "phong.specular"),
- &Vec3::from(light.specular().clone()),
+ &opengl_bindings::data_types::Vec3 {
+ x: light.specular().red,
+ y: light.specular().green,
+ z: light.specular().blue,
+ },
);
}
@@ -797,7 +1485,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 => {
@@ -845,3 +1534,54 @@ fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFilt
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 + 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,
+ }
+ }
+}
+
+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,
+ }
+ }
+}
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..9b929c0
--- /dev/null
+++ b/engine/src/renderer/opengl/graphics_mesh.rs
@@ -0,0 +1,120 @@
+use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage};
+use opengl_bindings::vertex_array::{
+ DataType as GlVertexArrayDataType,
+ VertexArray as GlVertexArray,
+};
+use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns;
+
+use crate::mesh::Mesh;
+use crate::renderer::opengl::vertex::{
+ AttributeComponentType as VertexAttributeComponentType,
+ Vertex as RendererVertex,
+};
+
+#[derive(Debug)]
+pub struct GraphicsMesh
+{
+ /// Vertex and index buffer has to live as long as the vertex array
+ _vertex_buffer: GlBuffer<RendererVertex>,
+ 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,
+ ) -> Result<Self, Error>
+ {
+ tracing::trace!(
+ "Creating vertex array, vertex buffer{}",
+ if mesh.indices().is_some() {
+ " and index buffer"
+ } else {
+ ""
+ }
+ );
+
+ let vertex_arr = GlVertexArray::new(current_context);
+ let vertex_buffer = GlBuffer::new(current_context);
+
+ vertex_buffer
+ .store_mapped(
+ current_context,
+ mesh.vertices(),
+ GlBufferUsage::Static,
+ |vertex| RendererVertex {
+ pos: vertex.pos.into(),
+ texture_coords: vertex.texture_coords.into(),
+ normal: vertex.normal.into(),
+ },
+ )
+ .map_err(Error::StoreVerticesFailed)?;
+
+ vertex_arr.bind_vertex_buffer(current_context, 0, &vertex_buffer, 0);
+
+ let mut offset = 0u32;
+
+ for attrib in RendererVertex::attrs() {
+ vertex_arr.enable_attrib(current_context, attrib.index);
+
+ vertex_arr.set_attrib_format(
+ current_context,
+ attrib.index,
+ match attrib.component_type {
+ VertexAttributeComponentType::Float => GlVertexArrayDataType::Float,
+ },
+ false,
+ offset,
+ );
+
+ vertex_arr.set_attrib_vertex_buf_binding(current_context, attrib.index, 0);
+
+ offset += attrib.component_size * attrib.component_cnt as u32;
+ }
+
+ if let Some(indices) = mesh.indices() {
+ let index_buffer = GlBuffer::new(current_context);
+
+ index_buffer
+ .store(current_context, indices, GlBufferUsage::Static)
+ .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
+ .vertices()
+ .len()
+ .try_into()
+ .expect("Mesh vertex count does not fit into a 32-bit unsigned int"),
+ vertex_arr,
+ })
+ }
+}
+
+#[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),
+}
diff --git a/engine/src/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs
index 499b94b..5a1593e 100644
--- a/engine/src/renderer/opengl/vertex.rs
+++ b/engine/src/renderer/opengl/vertex.rs
@@ -1,12 +1,13 @@
-use crate::vector::{Vec2, Vec3};
+use safer_ffi::derive_ReprC;
#[derive(Debug, Clone)]
+#[derive_ReprC]
#[repr(C)]
pub struct Vertex
{
- pub pos: Vec3<f32>,
- pub texture_coords: Vec2<f32>,
- pub normal: Vec3<f32>,
+ pub pos: opengl_bindings::data_types::Vec3<f32>,
+ pub texture_coords: opengl_bindings::data_types::Vec2<f32>,
+ pub normal: opengl_bindings::data_types::Vec3<f32>,
}
impl Vertex
diff --git a/engine/src/util.rs b/engine/src/util.rs
index cc4677d..9174734 100644
--- a/engine/src/util.rs
+++ b/engine/src/util.rs
@@ -1,4 +1,72 @@
-use std::marker::PhantomData;
+use ecs::util::VecExt;
+
+#[derive(Debug)]
+pub struct MapVec<Key: Ord, Value>
+{
+ inner: Vec<(Key, Value)>,
+}
+
+impl<Key: Ord, Value> MapVec<Key, Value>
+{
+ pub fn insert(&mut self, key: Key, value: Value)
+ {
+ self.inner
+ .insert_at_part_pt_by_key((key, value), |(a_key, _)| a_key);
+ }
+
+ pub fn remove(&mut self, key: Key) -> Option<Value>
+ {
+ let index = self
+ .inner
+ .binary_search_by_key(&&key, |(a_key, _)| a_key)
+ .ok()?;
+
+ let (_, value) = self.inner.remove(index);
+
+ Some(value)
+ }
+
+ pub fn get(&self, key: &Key) -> Option<&Value>
+ {
+ let index = self
+ .inner
+ .binary_search_by_key(&key, |(a_key, _)| a_key)
+ .ok()?;
+
+ let Some((_, value)) = self.inner.get(index) else {
+ unreachable!(); // Reason: Index from binary search cannot be OOB
+ };
+
+ Some(value)
+ }
+
+ pub fn get_mut(&mut self, key: &Key) -> Option<&mut Value>
+ {
+ let index = self
+ .inner
+ .binary_search_by_key(&key, |(a_key, _)| a_key)
+ .ok()?;
+
+ let Some((_, value)) = self.inner.get_mut(index) else {
+ unreachable!(); // Reason: Index from binary search cannot be OOB
+ };
+
+ Some(value)
+ }
+
+ pub fn values(&self) -> impl Iterator<Item = &Value>
+ {
+ self.inner.iter().map(|(_, value)| value)
+ }
+}
+
+impl<Key: Ord, Value> Default for MapVec<Key, Value>
+{
+ fn default() -> Self
+ {
+ Self { inner: Vec::new() }
+ }
+}
macro_rules! try_option {
($expr: expr) => {
@@ -113,75 +181,3 @@ macro_rules! builder {
}
};
}
-
-pub enum RefOrValue<'a, T>
-{
- Ref(&'a T),
- Value(Option<T>),
-}
-
-impl<'a, T> RefOrValue<'a, T>
-{
- pub fn get(&self) -> Option<&T>
- {
- match self {
- Self::Ref(val_ref) => Some(val_ref),
- Self::Value(val_cell) => val_cell.as_ref(),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Defer<'func, Func, Data>
-where
- Func: FnMut(&mut Data) + 'func,
-{
- func: Func,
- pub data: Data,
- _pd: PhantomData<&'func ()>,
-}
-
-impl<'func, Func, Data> Defer<'func, Func, Data>
-where
- Func: FnMut(&mut Data) + 'func,
-{
- pub fn new(data: Data, func: Func) -> Self
- {
- Self { func, data, _pd: PhantomData }
- }
-}
-
-impl<'func, Func, Data> Drop for Defer<'func, Func, Data>
-where
- Func: FnMut(&mut Data) + 'func,
-{
- fn drop(&mut self)
- {
- (self.func)(&mut self.data)
- }
-}
-
-/// Defines a function that will be called at the end of the current scope.
-///
-/// Only captured variables that are later mutably borrowed needs to specified as
-/// captures.
-macro_rules! defer {
- (|$capture: ident| {$($tt: tt)*}) => {
- // This uses the automatic temporary lifetime extension behaviour introduced
- // in Rust 1.79.0 (https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html) to
- // create a unnamable variable for the Defer struct. The variable should be
- // unnamable so that it cannot be missused and so that this macro can be used
- // multiple times without having to give it a identifier for the Defer struct
- // variable
- let Defer { data: $capture, .. } = if true {
- &Defer::new($capture, |$capture| {
- $($tt)*
- })
- }
- else {
- unreachable!();
- };
- };
-}
-
-pub(crate) use defer;
diff --git a/engine/src/window.rs b/engine/src/window.rs
deleted file mode 100644
index f191c06..0000000
--- a/engine/src/window.rs
+++ /dev/null
@@ -1,755 +0,0 @@
-use std::borrow::Cow;
-use std::ffi::{CStr, CString};
-
-use bitflags::bitflags;
-use ecs::actions::Actions;
-use ecs::extension::Collector as ExtensionCollector;
-use ecs::pair::{ChildOf, Pair};
-use ecs::phase::{Phase, START as START_PHASE};
-use ecs::sole::Single;
-use ecs::{declare_entity, Sole};
-use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue};
-use glfw::WindowSize;
-use util_macros::VariantArr;
-
-use crate::data_types::dimens::Dimens;
-use crate::renderer::RENDER_PHASE;
-use crate::vector::Vec2;
-
-declare_entity!(
- pub UPDATE_PHASE,
- (Phase, Pair::builder().relation::<ChildOf>().target_id(*RENDER_PHASE).build())
-);
-
-#[derive(Debug, Sole)]
-/// Has to be dropped last since it holds the OpenGL context.
-#[sole(drop_last)]
-pub struct Window
-{
- inner: glfw::Window,
-}
-
-impl Window
-{
- /// Returns a new Window builder.
- #[must_use]
- pub fn builder() -> Builder
- {
- Builder::default()
- }
-
- /// Sets the value of a input mode.
- ///
- /// # Errors
- /// Returns `Err` if the input mode is unsupported on the current system.
- pub fn set_input_mode(
- &self,
- input_mode: InputMode,
- enabled: bool,
- ) -> Result<(), Error>
- {
- Ok(self
- .inner
- .set_input_mode(input_mode.to_glfw_input_mode(), enabled)?)
- }
-
- /// Sets the cursor mode.
- ///
- /// # Errors
- /// If a platform error occurs.
- pub fn set_cursor_mode(&self, cursor_mode: CursorMode) -> Result<(), Error>
- {
- Ok(self
- .inner
- .set_cursor_mode(cursor_mode.to_glfw_cursor_mode())?)
- }
-
- /// Returns whether or not the window should close. Will return true when the user has
- /// attempted to close the window.
- #[must_use]
- pub fn should_close(&self) -> bool
- {
- self.inner.should_close()
- }
-
- /// Processes all pending events.
- ///
- /// # Errors
- /// If a platform error occurs.
- pub fn poll_events(&self) -> Result<(), Error>
- {
- Ok(self.inner.poll_events()?)
- }
-
- /// Swaps the front and back buffers of the window.
- ///
- /// # Errors
- /// Will return `Err` if a platform error occurs or if no OpenGL window context
- /// is present.
- pub fn swap_buffers(&self) -> Result<(), Error>
- {
- Ok(self.inner.swap_buffers()?)
- }
-
- /// Returns the size of the window.
- ///
- /// # Errors
- /// Will return `Err` if a platform error occurs.
- pub fn size(&self) -> Result<Dimens<u32>, Error>
- {
- let size = self.inner.size()?;
-
- Ok(Dimens {
- width: size.width,
- height: size.height,
- })
- }
-
- /// Returns the address of the specified OpenGL function, if it is supported by the
- /// current OpenGL context.
- ///
- /// # Errors
- /// Will return `Err` if a platform error occurs or if no current context has
- /// been set.
- ///
- /// # Panics
- /// Will panic if the `proc_name` argument contains a nul byte.
- pub fn get_proc_address(
- &self,
- proc_name: &str,
- ) -> Result<unsafe extern "C" fn(), Error>
- {
- let proc_name_c: Cow<CStr> = CStr::from_bytes_with_nul(proc_name.as_bytes())
- .map(Cow::Borrowed)
- .or_else(|_| CString::new(proc_name).map(Cow::Owned))
- .expect("OpenGL function name contains a nul byte");
-
- Ok(self.inner.get_proc_address(&proc_name_c)?)
- }
-
- /// Makes the OpenGL context of the window current for the calling thread.
- ///
- /// # Errors
- /// Will return `Err` if a platform error occurs or if no OpenGL context is
- /// present.
- pub fn make_context_current(&self) -> Result<(), Error>
- {
- Ok(self.inner.make_context_current()?)
- }
-
- /// Sets the window's framebuffer size callback.
- pub fn set_framebuffer_size_callback(&self, callback: impl Fn(Dimens<u32>) + 'static)
- {
- self.inner.set_framebuffer_size_callback(move |size| {
- callback(Dimens {
- width: size.width,
- height: size.height,
- });
- });
- }
-
- /// Sets the window's key size callback.
- pub fn set_key_callback(
- &self,
- callback: impl Fn(Key, i32, KeyState, KeyModifiers) + 'static,
- )
- {
- self.inner
- .set_key_callback(move |key, scancode, key_state, key_modifiers| {
- let Some(key_state) = KeyState::from_glfw_key_state(key_state) else {
- return;
- };
-
- callback(
- Key::from_glfw_key(key),
- scancode,
- key_state,
- KeyModifiers::from_bits_truncate(key_modifiers.bits()),
- )
- });
- }
-
- /// Sets the window's cursor position callback.
- pub fn set_cursor_pos_callback(&self, callback: impl Fn(Vec2<f64>) + 'static)
- {
- self.inner
- .set_cursor_pos_callback(move |pos| callback(Vec2 { x: pos.x, y: pos.y }));
- }
-
- /// Sets the window's mouse button callback. The given function is called when a mouse
- /// button enters a new state.
- pub fn set_mouse_button_callback(
- &self,
- callback: impl Fn(MouseButton, MouseButtonState, KeyModifiers) + 'static,
- )
- {
- self.inner.set_mouse_button_callback(
- move |mouse_button, mouse_button_state, key_modifiers| {
- callback(
- MouseButton::from_glfw_mouse_button(mouse_button),
- MouseButtonState::from_glfw_mouse_button_state(mouse_button_state),
- KeyModifiers::from_bits_truncate(key_modifiers.bits()),
- )
- },
- );
- }
-
- /// Sets the window's close callback.
- pub fn set_close_callback(&self, callback: impl Fn() + 'static)
- {
- self.inner.set_close_callback(callback);
- }
-
- /// Sets the window's focus callback. The callback is called when the window loses or
- /// gains input focus.
- pub fn set_focus_callback(&self, callback: impl Fn(bool) + 'static)
- {
- self.inner.set_focus_callback(callback);
- }
-}
-
-/// [`Window`] builder.
-#[derive(Debug, Clone, Default)]
-pub struct Builder
-{
- inner: glfw::WindowBuilder,
-}
-
-impl Builder
-{
- /// Sets whether the OpenGL context should be created in debug mode, which may
- /// provide additional error and diagnostic reporting functionality.
- pub fn opengl_debug_context(mut self, enabled: bool) -> Self
- {
- self.inner = self.inner.hint(
- WindowCreationHint::OpenGLDebugContext,
- WindowCreationHintValue::Bool(enabled),
- );
-
- self
- }
-
- /// Set the desired number of samples to use for multisampling. Zero disables
- /// multisampling.
- pub fn multisampling_sample_count(mut self, sample_count: u16) -> Self
- {
- self.inner = self.inner.hint(
- WindowCreationHint::Samples,
- WindowCreationHintValue::Number(sample_count as i32),
- );
-
- self
- }
-
- /// Creates a new window.
- ///
- /// # Errors
- /// Will return `Err` if the title contains a internal nul byte or if a platform error
- /// occurs.
- pub fn create(&self, size: Dimens<u32>, title: &str) -> Result<Window, Error>
- {
- let builder = self.inner.clone().hint(
- WindowCreationHint::OpenGLDebugContext,
- WindowCreationHintValue::Bool(true),
- );
-
- let window = builder.create(
- &WindowSize {
- width: size.width,
- height: size.height,
- },
- title,
- )?;
-
- Ok(Window { inner: window })
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)]
-#[variant_arr(name = KEYS)]
-pub enum Key
-{
- Space,
- Apostrophe,
- Comma,
- Minus,
- Period,
- Slash,
- Digit0,
- Digit1,
- Digit2,
- Digit3,
- Digit4,
- Digit5,
- Digit6,
- Digit7,
- Digit8,
- Digit9,
- Semicolon,
- Equal,
- A,
- B,
- C,
- D,
- E,
- F,
- G,
- H,
- I,
- J,
- K,
- L,
- M,
- N,
- O,
- P,
- Q,
- R,
- S,
- T,
- U,
- V,
- W,
- X,
- Y,
- Z,
- LeftBracket,
- Backslash,
- RightBracket,
- GraveAccent,
- World1,
- World2,
- Escape,
- Enter,
- Tab,
- Backspace,
- Insert,
- Delete,
- Right,
- Left,
- Down,
- Up,
- PageUp,
- PageDown,
- Home,
- End,
- CapsLock,
- ScrollLock,
- NumLock,
- PrintScreen,
- Pause,
- F1,
- F2,
- F3,
- F4,
- F5,
- F6,
- F7,
- F8,
- F9,
- F10,
- F11,
- F12,
- F13,
- F14,
- F15,
- F16,
- F17,
- F18,
- F19,
- F20,
- F21,
- F22,
- F23,
- F24,
- F25,
- Kp0,
- Kp1,
- Kp2,
- Kp3,
- Kp4,
- Kp5,
- Kp6,
- Kp7,
- Kp8,
- Kp9,
- KpDecimal,
- KpDivide,
- KpMultiply,
- KpSubtract,
- KpAdd,
- KpEnter,
- KpEqual,
- LeftShift,
- LeftControl,
- LeftAlt,
- LeftSuper,
- RightShift,
- RightControl,
- RightAlt,
- RightSuper,
- Menu,
-}
-
-impl Key
-{
- fn from_glfw_key(glfw_key: glfw::window::Key) -> Self
- {
- match glfw_key {
- glfw::window::Key::Space => Self::Space,
- glfw::window::Key::Apostrophe => Self::Apostrophe,
- glfw::window::Key::Comma => Self::Comma,
- glfw::window::Key::Minus => Self::Minus,
- glfw::window::Key::Period => Self::Period,
- glfw::window::Key::Slash => Self::Slash,
- glfw::window::Key::Digit0 => Self::Digit0,
- glfw::window::Key::Digit1 => Self::Digit1,
- glfw::window::Key::Digit2 => Self::Digit2,
- glfw::window::Key::Digit3 => Self::Digit3,
- glfw::window::Key::Digit4 => Self::Digit4,
- glfw::window::Key::Digit5 => Self::Digit5,
- glfw::window::Key::Digit6 => Self::Digit6,
- glfw::window::Key::Digit7 => Self::Digit7,
- glfw::window::Key::Digit8 => Self::Digit8,
- glfw::window::Key::Digit9 => Self::Digit9,
- glfw::window::Key::Semicolon => Self::Semicolon,
- glfw::window::Key::Equal => Self::Equal,
- glfw::window::Key::A => Self::A,
- glfw::window::Key::B => Self::B,
- glfw::window::Key::C => Self::C,
- glfw::window::Key::D => Self::D,
- glfw::window::Key::E => Self::E,
- glfw::window::Key::F => Self::F,
- glfw::window::Key::G => Self::G,
- glfw::window::Key::H => Self::H,
- glfw::window::Key::I => Self::I,
- glfw::window::Key::J => Self::J,
- glfw::window::Key::K => Self::K,
- glfw::window::Key::L => Self::L,
- glfw::window::Key::M => Self::M,
- glfw::window::Key::N => Self::N,
- glfw::window::Key::O => Self::O,
- glfw::window::Key::P => Self::P,
- glfw::window::Key::Q => Self::Q,
- glfw::window::Key::R => Self::R,
- glfw::window::Key::S => Self::S,
- glfw::window::Key::T => Self::T,
- glfw::window::Key::U => Self::U,
- glfw::window::Key::V => Self::V,
- glfw::window::Key::W => Self::W,
- glfw::window::Key::X => Self::X,
- glfw::window::Key::Y => Self::Y,
- glfw::window::Key::Z => Self::Z,
- glfw::window::Key::LeftBracket => Self::LeftBracket,
- glfw::window::Key::Backslash => Self::Backslash,
- glfw::window::Key::RightBracket => Self::RightBracket,
- glfw::window::Key::GraveAccent => Self::GraveAccent,
- glfw::window::Key::World1 => Self::World1,
- glfw::window::Key::World2 => Self::World2,
- glfw::window::Key::Escape => Self::Escape,
- glfw::window::Key::Enter => Self::Enter,
- glfw::window::Key::Tab => Self::Tab,
- glfw::window::Key::Backspace => Self::Backspace,
- glfw::window::Key::Insert => Self::Insert,
- glfw::window::Key::Delete => Self::Delete,
- glfw::window::Key::Right => Self::Right,
- glfw::window::Key::Left => Self::Left,
- glfw::window::Key::Down => Self::Down,
- glfw::window::Key::Up => Self::Up,
- glfw::window::Key::PageUp => Self::PageUp,
- glfw::window::Key::PageDown => Self::PageDown,
- glfw::window::Key::Home => Self::Home,
- glfw::window::Key::End => Self::End,
- glfw::window::Key::CapsLock => Self::CapsLock,
- glfw::window::Key::ScrollLock => Self::ScrollLock,
- glfw::window::Key::NumLock => Self::NumLock,
- glfw::window::Key::PrintScreen => Self::PrintScreen,
- glfw::window::Key::Pause => Self::Pause,
- glfw::window::Key::F1 => Self::F1,
- glfw::window::Key::F2 => Self::F2,
- glfw::window::Key::F3 => Self::F3,
- glfw::window::Key::F4 => Self::F4,
- glfw::window::Key::F5 => Self::F5,
- glfw::window::Key::F6 => Self::F6,
- glfw::window::Key::F7 => Self::F7,
- glfw::window::Key::F8 => Self::F8,
- glfw::window::Key::F9 => Self::F9,
- glfw::window::Key::F10 => Self::F10,
- glfw::window::Key::F11 => Self::F11,
- glfw::window::Key::F12 => Self::F12,
- glfw::window::Key::F13 => Self::F13,
- glfw::window::Key::F14 => Self::F14,
- glfw::window::Key::F15 => Self::F15,
- glfw::window::Key::F16 => Self::F16,
- glfw::window::Key::F17 => Self::F17,
- glfw::window::Key::F18 => Self::F18,
- glfw::window::Key::F19 => Self::F19,
- glfw::window::Key::F20 => Self::F20,
- glfw::window::Key::F21 => Self::F21,
- glfw::window::Key::F22 => Self::F22,
- glfw::window::Key::F23 => Self::F23,
- glfw::window::Key::F24 => Self::F24,
- glfw::window::Key::F25 => Self::F25,
- glfw::window::Key::Kp0 => Self::Kp0,
- glfw::window::Key::Kp1 => Self::Kp1,
- glfw::window::Key::Kp2 => Self::Kp2,
- glfw::window::Key::Kp3 => Self::Kp3,
- glfw::window::Key::Kp4 => Self::Kp4,
- glfw::window::Key::Kp5 => Self::Kp5,
- glfw::window::Key::Kp6 => Self::Kp6,
- glfw::window::Key::Kp7 => Self::Kp7,
- glfw::window::Key::Kp8 => Self::Kp8,
- glfw::window::Key::Kp9 => Self::Kp9,
- glfw::window::Key::KpDecimal => Self::KpDecimal,
- glfw::window::Key::KpDivide => Self::KpDivide,
- glfw::window::Key::KpMultiply => Self::KpMultiply,
- glfw::window::Key::KpSubtract => Self::KpSubtract,
- glfw::window::Key::KpAdd => Self::KpAdd,
- glfw::window::Key::KpEnter => Self::KpEnter,
- glfw::window::Key::KpEqual => Self::KpEqual,
- glfw::window::Key::LeftShift => Self::LeftShift,
- glfw::window::Key::LeftControl => Self::LeftControl,
- glfw::window::Key::LeftAlt => Self::LeftAlt,
- glfw::window::Key::LeftSuper => Self::LeftSuper,
- glfw::window::Key::RightShift => Self::RightShift,
- glfw::window::Key::RightControl => Self::RightControl,
- glfw::window::Key::RightAlt => Self::RightAlt,
- glfw::window::Key::RightSuper => Self::RightSuper,
- glfw::window::Key::Menu => Self::Menu,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum KeyState
-{
- Pressed,
- Released,
-}
-
-impl KeyState
-{
- fn from_glfw_key_state(glfw_key_state: glfw::window::KeyState) -> Option<Self>
- {
- match glfw_key_state {
- glfw::window::KeyState::Pressed => Some(Self::Pressed),
- glfw::window::KeyState::Released => Some(Self::Released),
- glfw::window::KeyState::Repeat => None,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum MouseButton
-{
- One,
- Two,
- Three,
- Four,
- Five,
- Six,
- Seven,
- Eight,
-}
-
-impl MouseButton
-{
- pub const LEFT: Self = Self::One;
- pub const MIDDLE: Self = Self::Three;
- pub const RIGHT: Self = Self::Two;
-
- fn from_glfw_mouse_button(mouse_button: glfw::window::MouseButton) -> Self
- {
- match mouse_button {
- glfw::window::MouseButton::One => Self::One,
- glfw::window::MouseButton::Two => Self::Two,
- glfw::window::MouseButton::Three => Self::Three,
- glfw::window::MouseButton::Four => Self::Four,
- glfw::window::MouseButton::Five => Self::Five,
- glfw::window::MouseButton::Six => Self::Six,
- glfw::window::MouseButton::Seven => Self::Seven,
- glfw::window::MouseButton::Eight => Self::Eight,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum MouseButtonState
-{
- Pressed,
- Released,
-}
-
-impl MouseButtonState
-{
- fn from_glfw_mouse_button_state(
- mouse_button_state: glfw::window::MouseButtonState,
- ) -> Self
- {
- match mouse_button_state {
- glfw::window::MouseButtonState::Pressed => Self::Pressed,
- glfw::window::MouseButtonState::Released => Self::Released,
- }
- }
-}
-
-bitflags! {
- #[derive(Debug, Clone, Copy)]
- pub struct KeyModifiers: i32 {
- const SHIFT = glfw::window::KeyModifiers::SHIFT.bits();
- const CONTROL = glfw::window::KeyModifiers::CONTROL.bits();
- const ALT = glfw::window::KeyModifiers::ALT.bits();
- const SUPER = glfw::window::KeyModifiers::SUPER.bits();
- const CAPS_LOCK = glfw::window::KeyModifiers::CAPS_LOCK.bits();
- const NUM_LOCK = glfw::window::KeyModifiers::NUM_LOCK.bits();
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum CursorMode
-{
- /// Hides and grabs the cursor, providing virtual and unlimited cursor movement.
- Disabled,
-
- /// Makes the cursor invisible when it is over the content area of the window but
- /// does not restrict the cursor from leaving.
- Hidden,
-
- /// Makes the cursor visible and behaving normally.
- Normal,
-}
-
-impl CursorMode
-{
- fn to_glfw_cursor_mode(self) -> glfw::window::CursorMode
- {
- match self {
- Self::Disabled => glfw::window::CursorMode::Disabled,
- Self::Hidden => glfw::window::CursorMode::Hidden,
- Self::Normal => glfw::window::CursorMode::Normal,
- }
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum InputMode
-{
- /// When the cursor is disabled, raw (unscaled and unaccelerated) mouse motion can be
- /// enabled if available.
- ///
- /// Raw mouse motion is closer to the actual motion of the mouse across a surface. It
- /// is not affected by the scaling and acceleration applied to the motion of the
- /// desktop cursor. That processing is suitable for a cursor while raw motion is
- /// better for controlling for example a 3D camera. Because of this, raw mouse motion
- /// is only provided when the cursor is disabled.
- RawMouseMotion,
-}
-
-impl InputMode
-{
- fn to_glfw_input_mode(self) -> glfw::window::InputMode
- {
- match self {
- Self::RawMouseMotion => glfw::window::InputMode::RawMouseMotion,
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Extension
-{
- window_builder: Builder,
- window_size: Dimens<u32>,
- window_title: String,
-}
-
-impl Extension
-{
- #[must_use]
- pub fn new(window_builder: Builder) -> Self
- {
- Self { window_builder, ..Default::default() }
- }
-
- #[must_use]
- pub fn window_size(mut self, window_size: Dimens<u32>) -> Self
- {
- self.window_size = window_size;
-
- self
- }
-
- #[must_use]
- pub fn window_title(mut self, window_title: impl Into<String>) -> Self
- {
- self.window_title = window_title.into();
-
- self
- }
-}
-
-impl ecs::extension::Extension for Extension
-{
- fn collect(self, mut collector: ExtensionCollector<'_>)
- {
- collector.add_declared_entity(&self::UPDATE_PHASE);
-
- collector.add_system(*START_PHASE, initialize);
- collector.add_system(*self::UPDATE_PHASE, update);
-
- let window = self
- .window_builder
- .create(self.window_size, &self.window_title)
- .unwrap();
-
- window.set_cursor_mode(CursorMode::Normal).unwrap();
-
- collector.add_sole(window).ok();
- }
-}
-
-impl Default for Extension
-{
- fn default() -> Self
- {
- Self {
- window_builder: Builder::default(),
- window_size: Dimens { width: 1920, height: 1080 },
- window_title: String::new(),
- }
- }
-}
-
-#[derive(Debug, thiserror::Error)]
-#[error(transparent)]
-pub struct Error(glfw::Error);
-
-impl From<glfw::Error> for Error
-{
- fn from(err: glfw::Error) -> Self
- {
- Self(err)
- }
-}
-
-fn initialize(window: Single<Window>, actions: Actions)
-{
- let actions_weak_ref = actions.to_weak_ref();
-
- window.set_close_callback(move || {
- let actions_weak_ref = actions_weak_ref.clone();
-
- let actions_ref = actions_weak_ref.access().expect("No world");
-
- actions_ref.to_actions().stop();
- });
-}
-
-fn update(window: Single<Window>)
-{
- window
- .swap_buffers()
- .expect("Failed to swap window buffers");
-
- window.poll_events().expect("Failed to poll window events");
-}
diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs
new file mode 100644
index 0000000..69adae9
--- /dev/null
+++ b/engine/src/windowing.rs
@@ -0,0 +1,669 @@
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Weak};
+use std::thread::{spawn, JoinHandle as ThreadJoinHandle};
+
+use crossbeam_channel::{
+ bounded as bounded_channel,
+ Receiver as ChannelReceiver,
+ Sender as ChannelSender,
+ TrySendError,
+};
+use ecs::actions::Actions;
+use ecs::component::Component;
+use ecs::entity::obtainer::Obtainer as EntityObtainer;
+use ecs::event::component::{Added, Changed, Removed};
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, UPDATE as UPDATE_PHASE};
+use ecs::sole::Single;
+use ecs::system::observer::Observe;
+use ecs::uid::Uid;
+use ecs::{declare_entity, Query, Sole};
+use raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle, WindowHandle};
+use winit::application::ApplicationHandler;
+use winit::dpi::PhysicalPosition;
+use winit::error::EventLoopError;
+use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
+use winit::event_loop::{
+ ActiveEventLoop,
+ ControlFlow as EventLoopControlFlow,
+ EventLoop,
+ OwnedDisplayHandle,
+};
+use winit::keyboard::PhysicalKey;
+use winit::window::{Window as WinitWindow, WindowId as WinitWindowId};
+
+use crate::data_types::dimens::Dimens;
+use crate::util::MapVec;
+use crate::vector::Vec2;
+use crate::windowing::keyboard::{Key, KeyState, Keyboard, UnknownKeyCodeError};
+use crate::windowing::mouse::{
+ Button as MouseButton,
+ ButtonState as MouseButtonState,
+ Buttons as MouseButtons,
+ Motion as MouseMotion,
+};
+use crate::windowing::window::{
+ Closed as WindowClosed,
+ CreationAttributes as WindowCreationAttributes,
+ CreationReady as WindowCreationReady,
+ CursorGrabMode,
+ Id as WindowId,
+ Window,
+};
+
+pub mod keyboard;
+pub mod mouse;
+pub mod window;
+
+const MESSAGE_FROM_APP_CHANNEL_CAP: usize = 128;
+
+const MESSAGE_TO_APP_CHANNEL_CAP: usize = 16; // Increase if more messages are added
+
+declare_entity!(
+ pub PHASE,
+ (
+ Phase,
+ Pair::builder()
+ .relation::<ChildOf>()
+ .target_id(*UPDATE_PHASE)
+ .build()
+ )
+);
+
+#[derive(Debug, Default)]
+#[non_exhaustive]
+pub struct Extension {}
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ collector.add_sole(Context::default()).ok();
+ collector.add_sole(Keyboard::default()).ok();
+ collector.add_sole(MouseMotion::default()).ok();
+ collector.add_sole(MouseButtons::default()).ok();
+
+ collector.add_declared_entity(&PHASE);
+
+ collector.add_system(*PHASE, update_stuff);
+
+ collector.add_observer(handle_window_changed);
+ collector.add_observer(handle_window_removed);
+ collector.add_observer(handle_window_creation_ready);
+ }
+}
+
+fn handle_window_creation_ready(
+ observe: Observe<Pair<Added, WindowCreationReady>>,
+ context: Single<Context>,
+)
+{
+ for evt_match in &observe {
+ let Some(ent) = evt_match.get_entity() else {
+ unreachable!();
+ };
+
+ if ent.has_component(Window::id()) || ent.has_component(WindowClosed::id()) {
+ continue;
+ }
+
+ let Some(window_creation_attrs) = ent.get::<WindowCreationAttributes>() else {
+ unreachable!();
+ };
+
+ context.try_send_message_to_app(MessageToApp::CreateWindow(
+ ent.uid(),
+ window_creation_attrs.clone(),
+ ));
+ }
+}
+
+#[tracing::instrument(skip_all)]
+fn update_stuff(
+ mut context: Single<Context>,
+ mut keyboard: Single<Keyboard>,
+ mut mouse_motion: Single<MouseMotion>,
+ mut mouse_buttons: Single<MouseButtons>,
+ mut actions: Actions,
+ entity_obtainer: EntityObtainer,
+)
+{
+ keyboard.make_key_states_previous();
+ mouse_buttons.make_states_previous();
+ mouse_motion.position_delta = Vec2::default();
+
+ let Context {
+ ref message_from_app_receiver,
+ ref mut display_handle,
+ ref mut windows,
+ ..
+ } = *context;
+
+ for message in message_from_app_receiver.try_iter() {
+ match message {
+ MessageFromApp::Init(new_display_handle) => {
+ *display_handle = Some(new_display_handle);
+ }
+ MessageFromApp::WindowCreated(
+ window_ent_id,
+ winit_window,
+ window_creation_attrs,
+ ) => {
+ actions.add_components(
+ window_ent_id,
+ (Window::new(&winit_window, &window_creation_attrs),),
+ );
+
+ actions.remove_comps::<(WindowCreationReady,)>(window_ent_id);
+
+ tracing::debug!("Added window component to window entity");
+
+ windows.insert(
+ WindowId::from_inner(winit_window.id()),
+ (winit_window, window_ent_id),
+ );
+ }
+ MessageFromApp::WindowResized(window_id, new_window_size) => {
+ let Some(window_ent_id) =
+ windows.get(&window_id).map(|(_, ent_id)| ent_id)
+ else {
+ continue;
+ };
+
+ let Some(window_ent) = entity_obtainer.get_entity(*window_ent_id) else {
+ continue;
+ };
+
+ let Some(mut window) = window_ent.get_mut::<Window>() else {
+ continue;
+ };
+
+ window.set_inner_size(new_window_size);
+
+ window.set_changed();
+ }
+ MessageFromApp::WindowCloseRequested(window_id) => {
+ let Some(window_ent_id) =
+ windows.get(&window_id).map(|(_, ent_id)| ent_id)
+ else {
+ tracing::error!(
+ wid = ?window_id,
+ "Window does not exist in windowing context"
+ );
+ continue;
+ };
+
+ actions.remove_comps::<(Window,)>(*window_ent_id);
+ }
+ MessageFromApp::KeyboardKeyStateChanged(key, key_state) => {
+ keyboard.set_key_state(key, key_state);
+ }
+ MessageFromApp::MouseMoved { position_delta } => {
+ mouse_motion.position_delta += position_delta;
+ }
+ MessageFromApp::MouseButtonStateChanged(mouse_button, mouse_button_state) => {
+ mouse_buttons.set(mouse_button, mouse_button_state);
+ }
+ }
+ }
+}
+
+fn handle_window_changed(
+ observe: Observe<'_, Pair<Changed, Window>>,
+ context: Single<Context>,
+)
+{
+ for evt_match in &observe {
+ let window_ent_id = evt_match.id();
+
+ let window = evt_match.get_changed_comp();
+
+ let Some((winit_window, _)) = context.windows.get(&window.wid()) else {
+ tracing::error!(
+ wid = ?window.wid(),
+ entity_id = %window_ent_id,
+ "Window does not exist in windowing context",
+ );
+ continue;
+ };
+
+ window.apply(winit_window);
+
+ context.try_send_message_to_app(MessageToApp::SetWindowCursorGrabMode(
+ window.wid(),
+ window.cursor_grab_mode,
+ ));
+ }
+}
+
+fn handle_window_removed(
+ observe: Observe<Pair<Removed, Window>>,
+ window_query: Query<(&Window,)>,
+ mut context: Single<Context>,
+ mut actions: Actions,
+)
+{
+ for evt_match in &observe {
+ let window = evt_match.get_removed_comp();
+
+ context.windows.remove(window.wid());
+
+ actions.add_components(evt_match.id(), (WindowClosed,));
+ }
+
+ if window_query.iter().count() == 1 {
+ actions.stop();
+ }
+}
+
+#[derive(Debug, Sole)]
+pub struct Context
+{
+ _thread: ThreadJoinHandle<()>,
+ is_dropped: Arc<AtomicBool>,
+ message_from_app_receiver: ChannelReceiver<MessageFromApp>,
+ message_to_app_sender: ChannelSender<MessageToApp>,
+ display_handle: Option<OwnedDisplayHandle>,
+ windows: MapVec<WindowId, (Arc<winit::window::Window>, Uid)>,
+}
+
+impl Context
+{
+ pub fn display_handle(&self) -> Option<DisplayHandle<'_>>
+ {
+ let display_handle = self.display_handle.as_ref()?;
+
+ display_handle.display_handle().ok()
+ }
+
+ /// Returns the specified window as a window handle, if it exists.
+ ///
+ /// # Safety
+ /// The Window handle must only be used with thread safe APIs.
+ pub unsafe fn get_window_as_handle(
+ &self,
+ window_id: &WindowId,
+ ) -> Option<Result<WindowHandle<'_>, HandleError>>
+ {
+ self.windows.get(window_id).map(|(winit_window, _)| {
+ #[cfg(windows)]
+ {
+ use winit::platform::windows::WindowExtWindows;
+
+ // SAFETY: I don't care
+ unsafe { winit_window.window_handle_any_thread() }
+ }
+
+ #[cfg(not(windows))]
+ {
+ use raw_window_handle::HasWindowHandle;
+
+ winit_window.window_handle()
+ }
+ })
+ }
+
+ fn try_send_message_to_app(&self, message: MessageToApp)
+ {
+ if let Err(err) = self.message_to_app_sender.try_send(message) {
+ let error = match &err {
+ TrySendError::Full(_) => TrySendError::Full(()),
+ TrySendError::Disconnected(_) => TrySendError::Disconnected(()),
+ };
+
+ let message = err.into_inner();
+
+ tracing::error!("Failed to send message {error}: {message:?}");
+ }
+ }
+}
+
+impl Default for Context
+{
+ fn default() -> Self
+ {
+ let is_dropped = Arc::new(AtomicBool::new(false));
+
+ let is_dropped_b = is_dropped.clone();
+
+ let (message_from_app_sender, message_from_app_receiver) =
+ bounded_channel::<MessageFromApp>(MESSAGE_FROM_APP_CHANNEL_CAP);
+
+ let message_from_app_receiver_b = message_from_app_receiver.clone();
+
+ let (message_to_app_sender, message_to_app_receiver) =
+ bounded_channel::<MessageToApp>(MESSAGE_TO_APP_CHANNEL_CAP);
+
+ Self {
+ _thread: spawn(move || {
+ let mut app = App {
+ message_from_app_sender,
+ message_from_app_receiver: message_from_app_receiver_b,
+ message_to_app_receiver,
+ is_dropped: is_dropped_b,
+ windows: MapVec::default(),
+ focused_window_id: None,
+ };
+
+ let event_loop = match create_event_loop() {
+ Ok(event_loop) => event_loop,
+ Err(err) => {
+ tracing::error!("Failed to create event loop: {err}");
+ return;
+ }
+ };
+
+ event_loop.set_control_flow(EventLoopControlFlow::Poll);
+
+ if let Err(err) = event_loop.run_app(&mut app) {
+ tracing::error!("Event loop error occurred: {err}");
+ }
+ }),
+ is_dropped,
+ message_from_app_receiver,
+ message_to_app_sender,
+ display_handle: None,
+ windows: MapVec::default(),
+ }
+ }
+}
+
+impl Drop for Context
+{
+ fn drop(&mut self)
+ {
+ self.is_dropped.store(true, Ordering::Relaxed);
+ }
+}
+
+fn create_event_loop() -> Result<EventLoop<()>, EventLoopError>
+{
+ let mut event_loop_builder = EventLoop::builder();
+
+ #[cfg(any(x11_platform, wayland_platform))]
+ winit::platform::x11::EventLoopBuilderExtX11::with_any_thread(
+ &mut event_loop_builder,
+ true,
+ );
+
+ #[cfg(windows)]
+ winit::platform::windows::EventLoopBuilderExtWindows::with_any_thread(
+ &mut event_loop_builder,
+ true,
+ );
+
+ #[cfg(not(any(x11_platform, wayland_platform, windows)))]
+ compile_error!("Unsupported platform");
+
+ event_loop_builder.build()
+}
+
+#[derive(Debug)]
+enum MessageFromApp
+{
+ Init(OwnedDisplayHandle),
+ WindowCreated(Uid, Arc<WinitWindow>, WindowCreationAttributes),
+ WindowResized(WindowId, Dimens<u32>),
+ WindowCloseRequested(WindowId),
+ KeyboardKeyStateChanged(Key, KeyState),
+ MouseMoved
+ {
+ position_delta: Vec2<f64>,
+ },
+ MouseButtonStateChanged(MouseButton, MouseButtonState),
+}
+
+#[derive(Debug)]
+enum MessageToApp
+{
+ CreateWindow(Uid, WindowCreationAttributes),
+ SetWindowCursorGrabMode(WindowId, CursorGrabMode),
+}
+
+#[derive(Debug)]
+struct App
+{
+ message_from_app_sender: ChannelSender<MessageFromApp>,
+ message_from_app_receiver: ChannelReceiver<MessageFromApp>,
+ message_to_app_receiver: ChannelReceiver<MessageToApp>,
+ is_dropped: Arc<AtomicBool>,
+ windows: MapVec<WindowId, (Weak<WinitWindow>, WindowSettings)>,
+ focused_window_id: Option<WindowId>,
+}
+
+impl App
+{
+ fn handle_received_messages(&mut self, event_loop: &ActiveEventLoop)
+ {
+ for message in self.message_to_app_receiver.try_iter() {
+ match message {
+ MessageToApp::CreateWindow(window_ent_id, window_creation_attrs) => {
+ tracing::info!(
+ "Creating window with title {}",
+ window_creation_attrs.title()
+ );
+
+ let winit_window = Arc::new(
+ match event_loop
+ .create_window(window_creation_attrs.clone().into_inner())
+ {
+ Ok(window) => window,
+ Err(err) => {
+ tracing::error!("Failed to create window: {err}");
+ continue;
+ }
+ },
+ );
+
+ tracing::info!("Created window has title {}", winit_window.title());
+
+ self.windows.insert(
+ WindowId::from_inner(winit_window.id()),
+ (Arc::downgrade(&winit_window), WindowSettings::default()),
+ );
+
+ self.send_message(MessageFromApp::WindowCreated(
+ window_ent_id,
+ winit_window,
+ window_creation_attrs,
+ ));
+ }
+ MessageToApp::SetWindowCursorGrabMode(window_id, cursor_grab_mode) => {
+ let Some((_, window_settings)) = self.windows.get_mut(&window_id)
+ else {
+ tracing::warn!(
+ window_id=?window_id,
+ "Cannot set window cursor grab mode. Window not found"
+ );
+
+ continue;
+ };
+
+ window_settings.cursor_grab_mode = cursor_grab_mode;
+ }
+ }
+ }
+ }
+
+ fn send_message(&self, message: MessageFromApp)
+ {
+ if self.message_from_app_sender.is_full() {
+ tracing::warn!(
+ "Message channel is full! Dropping oldest message from channel"
+ );
+
+ self.message_from_app_receiver.try_recv().ok();
+ }
+
+ if let Err(err) = self.message_from_app_sender.try_send(message) {
+ let error = match &err {
+ TrySendError::Full(_) => TrySendError::Full(()),
+ TrySendError::Disconnected(_) => TrySendError::Disconnected(()),
+ };
+
+ let message = err.into_inner();
+
+ tracing::error!("Failed to send message {error}: {message:?}");
+ }
+ }
+}
+
+impl ApplicationHandler for App
+{
+ fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause)
+ {
+ match cause {
+ StartCause::Init => {
+ self.send_message(MessageFromApp::Init(
+ event_loop.owned_display_handle(),
+ ));
+ }
+ StartCause::Poll => {
+ if self.is_dropped.load(Ordering::Relaxed) {
+ event_loop.exit();
+ return;
+ }
+
+ self.handle_received_messages(event_loop);
+ }
+ _ => {}
+ }
+ }
+
+ fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop)
+ {
+ for (window, _) in self.windows.values() {
+ let Some(window) = window.upgrade() else {
+ continue;
+ };
+
+ window.request_redraw();
+ }
+ }
+
+ fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
+
+ fn window_event(
+ &mut self,
+ _event_loop: &ActiveEventLoop,
+ window_id: WinitWindowId,
+ event: WindowEvent,
+ )
+ {
+ match event {
+ WindowEvent::Resized(new_window_size) => {
+ self.send_message(MessageFromApp::WindowResized(
+ WindowId::from_inner(window_id),
+ new_window_size.into(),
+ ));
+ }
+ WindowEvent::CloseRequested => {
+ self.send_message(MessageFromApp::WindowCloseRequested(
+ WindowId::from_inner(window_id),
+ ));
+ }
+ WindowEvent::KeyboardInput {
+ device_id: _,
+ event: keyboard_event,
+ is_synthetic: _,
+ } => {
+ if keyboard_event.repeat {
+ return;
+ }
+
+ let key_code = match keyboard_event.physical_key {
+ PhysicalKey::Code(key_code) => key_code,
+ PhysicalKey::Unidentified(native_key) => {
+ tracing::warn!("Ignoring unidentified key: {native_key:?}");
+ return;
+ }
+ };
+
+ let key: Key = match key_code.try_into() {
+ Ok(key) => key,
+ Err(UnknownKeyCodeError) => {
+ tracing::warn!("Ignoring key with unknown key code {key_code:?}");
+ return;
+ }
+ };
+
+ self.send_message(MessageFromApp::KeyboardKeyStateChanged(
+ key,
+ keyboard_event.state.into(),
+ ));
+ }
+ WindowEvent::MouseInput { device_id: _, state, button } => {
+ self.send_message(MessageFromApp::MouseButtonStateChanged(
+ button.into(),
+ state.into(),
+ ));
+ }
+ WindowEvent::Focused(is_focused) => {
+ if is_focused {
+ self.focused_window_id = Some(WindowId::from_inner(window_id));
+ } else {
+ self.focused_window_id = None;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn device_event(
+ &mut self,
+ _event_loop: &ActiveEventLoop,
+ _device_id: DeviceId,
+ device_event: DeviceEvent,
+ )
+ {
+ match device_event {
+ DeviceEvent::MouseMotion { delta } => {
+ self.send_message(MessageFromApp::MouseMoved {
+ position_delta: Vec2 { x: delta.0, y: delta.1 },
+ });
+
+ let Some(focused_window_id) = self.focused_window_id else {
+ return;
+ };
+
+ let Some((focused_window, focused_window_settings)) =
+ self.windows.get(&focused_window_id)
+ else {
+ tracing::error!(
+ window_id=?focused_window_id,
+ "Focused window not found"
+ );
+ return;
+ };
+
+ if focused_window_settings.cursor_grab_mode != CursorGrabMode::Locked {
+ return;
+ }
+
+ // TODO: This might need to be optimized
+ let Some(focused_window) = focused_window.upgrade() else {
+ return;
+ };
+
+ let focused_window_size = focused_window.inner_size();
+
+ if let Err(err) = focused_window.set_cursor_position(PhysicalPosition {
+ x: focused_window_size.width / 2,
+ y: focused_window_size.height / 2,
+ }) {
+ tracing::error!(
+ window_id=?focused_window_id,
+ "Failed to set cursor position in focused window: {err}"
+ );
+ };
+ }
+ _ => {}
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct WindowSettings
+{
+ cursor_grab_mode: CursorGrabMode,
+}
diff --git a/engine/src/windowing/keyboard.rs b/engine/src/windowing/keyboard.rs
new file mode 100644
index 0000000..e4fffe5
--- /dev/null
+++ b/engine/src/windowing/keyboard.rs
@@ -0,0 +1,763 @@
+use std::collections::HashMap;
+
+use ecs::Sole;
+
+#[derive(Debug, Default, Sole)]
+pub struct Keyboard
+{
+ map: HashMap<Key, KeyData>,
+}
+
+impl Keyboard
+{
+ #[must_use]
+ pub fn get_key_state(&self, key: Key) -> KeyState
+ {
+ let Some(key_data) = self.map.get(&key) else {
+ return KeyState::Released;
+ };
+
+ key_data.curr_state
+ }
+
+ #[must_use]
+ pub fn get_prev_key_state(&self, key: Key) -> KeyState
+ {
+ let Some(key_data) = self.map.get(&key) else {
+ return KeyState::Released;
+ };
+
+ key_data.previous_state
+ }
+
+ pub fn set_key_state(&mut self, key: Key, key_state: KeyState)
+ {
+ let key_data = self.map.entry(key).or_default();
+
+ key_data.curr_state = key_state;
+ }
+
+ pub fn make_key_states_previous(&mut self)
+ {
+ for key_data in self.map.values_mut() {
+ key_data.previous_state = key_data.curr_state;
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Key
+{
+ /// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
+ /// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
+ /// (hankaku/zenkaku/kanji) key on Japanese keyboards
+ Backquote,
+ /// Used for both the US <kbd>\\</kbd> (on the 101-key layout) and also for the key
+ /// located between the <kbd>"</kbd> and <kbd>Enter</kbd> keys on row C of the 102-,
+ /// 104- and 106-key layouts.
+ /// Labeled <kbd>#</kbd> on a UK (102) keyboard.
+ Backslash,
+ /// <kbd>[</kbd> on a US keyboard.
+ BracketLeft,
+ /// <kbd>]</kbd> on a US keyboard.
+ BracketRight,
+ /// <kbd>,</kbd> on a US keyboard.
+ Comma,
+ /// <kbd>0</kbd> on a US keyboard.
+ Digit0,
+ /// <kbd>1</kbd> on a US keyboard.
+ Digit1,
+ /// <kbd>2</kbd> on a US keyboard.
+ Digit2,
+ /// <kbd>3</kbd> on a US keyboard.
+ Digit3,
+ /// <kbd>4</kbd> on a US keyboard.
+ Digit4,
+ /// <kbd>5</kbd> on a US keyboard.
+ Digit5,
+ /// <kbd>6</kbd> on a US keyboard.
+ Digit6,
+ /// <kbd>7</kbd> on a US keyboard.
+ Digit7,
+ /// <kbd>8</kbd> on a US keyboard.
+ Digit8,
+ /// <kbd>9</kbd> on a US keyboard.
+ Digit9,
+ /// <kbd>=</kbd> on a US keyboard.
+ Equal,
+ /// Located between the left <kbd>Shift</kbd> and <kbd>Z</kbd> keys.
+ /// Labeled <kbd>\\</kbd> on a UK keyboard.
+ IntlBackslash,
+ /// Located between the <kbd>/</kbd> and right <kbd>Shift</kbd> keys.
+ /// Labeled <kbd>\\</kbd> (ro) on a Japanese keyboard.
+ IntlRo,
+ /// Located between the <kbd>=</kbd> and <kbd>Backspace</kbd> keys.
+ /// Labeled <kbd>¥</kbd> (yen) on a Japanese keyboard. <kbd>\\</kbd> on a
+ /// Russian keyboard.
+ IntlYen,
+ /// <kbd>a</kbd> on a US keyboard.
+ /// Labeled <kbd>q</kbd> on an AZERTY (e.g., French) keyboard.
+ A,
+ /// <kbd>b</kbd> on a US keyboard.
+ B,
+ /// <kbd>c</kbd> on a US keyboard.
+ C,
+ /// <kbd>d</kbd> on a US keyboard.
+ D,
+ /// <kbd>e</kbd> on a US keyboard.
+ E,
+ /// <kbd>f</kbd> on a US keyboard.
+ F,
+ /// <kbd>g</kbd> on a US keyboard.
+ G,
+ /// <kbd>h</kbd> on a US keyboard.
+ H,
+ /// <kbd>i</kbd> on a US keyboard.
+ I,
+ /// <kbd>j</kbd> on a US keyboard.
+ J,
+ /// <kbd>k</kbd> on a US keyboard.
+ K,
+ /// <kbd>l</kbd> on a US keyboard.
+ L,
+ /// <kbd>m</kbd> on a US keyboard.
+ M,
+ /// <kbd>n</kbd> on a US keyboard.
+ N,
+ /// <kbd>o</kbd> on a US keyboard.
+ O,
+ /// <kbd>p</kbd> on a US keyboard.
+ P,
+ /// <kbd>q</kbd> on a US keyboard.
+ /// Labeled <kbd>a</kbd> on an AZERTY (e.g., French) keyboard.
+ Q,
+ /// <kbd>r</kbd> on a US keyboard.
+ R,
+ /// <kbd>s</kbd> on a US keyboard.
+ S,
+ /// <kbd>t</kbd> on a US keyboard.
+ T,
+ /// <kbd>u</kbd> on a US keyboard.
+ U,
+ /// <kbd>v</kbd> on a US keyboard.
+ V,
+ /// <kbd>w</kbd> on a US keyboard.
+ /// Labeled <kbd>z</kbd> on an AZERTY (e.g., French) keyboard.
+ W,
+ /// <kbd>x</kbd> on a US keyboard.
+ X,
+ /// <kbd>y</kbd> on a US keyboard.
+ /// Labeled <kbd>z</kbd> on a QWERTZ (e.g., German) keyboard.
+ Y,
+ /// <kbd>z</kbd> on a US keyboard.
+ /// Labeled <kbd>w</kbd> on an AZERTY (e.g., French) keyboard, and <kbd>y</kbd> on a
+ /// QWERTZ (e.g., German) keyboard.
+ Z,
+ /// <kbd>-</kbd> on a US keyboard.
+ Minus,
+ /// <kbd>.</kbd> on a US keyboard.
+ Period,
+ /// <kbd>'</kbd> on a US keyboard.
+ Quote,
+ /// <kbd>;</kbd> on a US keyboard.
+ Semicolon,
+ /// <kbd>/</kbd> on a US keyboard.
+ Slash,
+ /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
+ AltLeft,
+ /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
+ /// This is labeled <kbd>AltGr</kbd> on many keyboard layouts.
+ AltRight,
+ /// <kbd>Backspace</kbd> or <kbd>⌫</kbd>.
+ /// Labeled <kbd>Delete</kbd> on Apple keyboards.
+ Backspace,
+ /// <kbd>CapsLock</kbd> or <kbd>⇪</kbd>
+ CapsLock,
+ /// The application context menu key, which is typically found between the right
+ /// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key.
+ ContextMenu,
+ /// <kbd>Control</kbd> or <kbd>⌃</kbd>
+ ControlLeft,
+ /// <kbd>Control</kbd> or <kbd>⌃</kbd>
+ ControlRight,
+ /// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards.
+ Enter,
+ /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
+ SuperLeft,
+ /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
+ SuperRight,
+ /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
+ ShiftLeft,
+ /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
+ ShiftRight,
+ /// <kbd> </kbd> (space)
+ Space,
+ /// <kbd>Tab</kbd> or <kbd>⇥</kbd>
+ Tab,
+ /// Japanese: <kbd>変</kbd> (henkan)
+ Convert,
+ /// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd>
+ /// (katakana/hiragana/romaji)
+ KanaMode,
+ /// Korean: HangulMode <kbd>한/영</kbd> (han/yeong)
+ ///
+ /// Japanese (Mac keyboard): <kbd>か</kbd> (kana)
+ Lang1,
+ /// Korean: Hanja <kbd>한</kbd> (hanja)
+ ///
+ /// Japanese (Mac keyboard): <kbd>英</kbd> (eisu)
+ Lang2,
+ /// Japanese (word-processing keyboard): Katakana
+ Lang3,
+ /// Japanese (word-processing keyboard): Hiragana
+ Lang4,
+ /// Japanese (word-processing keyboard): Zenkaku/Hankaku
+ Lang5,
+ /// Japanese: <kbd>無変換</kbd> (muhenkan)
+ NonConvert,
+ /// <kbd>⌦</kbd>. The forward delete key.
+ /// Note that on Apple keyboards, the key labelled <kbd>Delete</kbd> on the main part
+ /// of the keyboard is encoded as [`Backspace`].
+ ///
+ /// [`Backspace`]: Self::Backspace
+ Delete,
+ /// <kbd>Page Down</kbd>, <kbd>End</kbd>, or <kbd>↘</kbd>
+ End,
+ /// <kbd>Help</kbd>. Not present on standard PC keyboards.
+ Help,
+ /// <kbd>Home</kbd> or <kbd>↖</kbd>
+ Home,
+ /// <kbd>Insert</kbd> or <kbd>Ins</kbd>. Not present on Apple keyboards.
+ Insert,
+ /// <kbd>Page Down</kbd>, <kbd>PgDn</kbd>, or <kbd>⇟</kbd>
+ PageDown,
+ /// <kbd>Page Up</kbd>, <kbd>PgUp</kbd>, or <kbd>⇞</kbd>
+ PageUp,
+ /// <kbd>↓</kbd>
+ ArrowDown,
+ /// <kbd>←</kbd>
+ ArrowLeft,
+ /// <kbd>→</kbd>
+ ArrowRight,
+ /// <kbd>↑</kbd>
+ ArrowUp,
+ /// On the Mac, this is used for the numpad <kbd>Clear</kbd> key.
+ NumLock,
+ /// <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control
+ Numpad0,
+ /// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or
+ /// remote control
+ Numpad1,
+ /// <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control
+ Numpad2,
+ /// <kbd>3 PgDn</kbd> on a keyboard. <kbd>3 DEF</kbd> on a phone or remote control
+ Numpad3,
+ /// <kbd>4 ←</kbd> on a keyboard. <kbd>4 GHI</kbd> on a phone or remote control
+ Numpad4,
+ /// <kbd>5</kbd> on a keyboard. <kbd>5 JKL</kbd> on a phone or remote control
+ Numpad5,
+ /// <kbd>6 →</kbd> on a keyboard. <kbd>6 MNO</kbd> on a phone or remote control
+ Numpad6,
+ /// <kbd>7 Home</kbd> on a keyboard. <kbd>7 PQRS</kbd> or <kbd>7 PRS</kbd> on a phone
+ /// or remote control
+ Numpad7,
+ /// <kbd>8 ↑</kbd> on a keyboard. <kbd>8 TUV</kbd> on a phone or remote control
+ Numpad8,
+ /// <kbd>9 PgUp</kbd> on a keyboard. <kbd>9 WXYZ</kbd> or <kbd>9 WXY</kbd> on a phone
+ /// or remote control
+ Numpad9,
+ /// <kbd>+</kbd>
+ NumpadAdd,
+ /// Found on the Microsoft Natural Keyboard.
+ NumpadBackspace,
+ /// <kbd>C</kbd> or <kbd>A</kbd> (All Clear). Also for use with numpads that have a
+ /// <kbd>Clear</kbd> key that is separate from the <kbd>NumLock</kbd> key. On the
+ /// Mac, the numpad <kbd>Clear</kbd> key is encoded as [`NumLock`].
+ ///
+ /// [`NumLock`]: Self::NumLock
+ NumpadClear,
+ /// <kbd>C</kbd> (Clear Entry)
+ NumpadClearEntry,
+ /// <kbd>,</kbd> (thousands separator). For locales where the thousands separator
+ /// is a "." (e.g., Brazil), this key may generate a <kbd>.</kbd>.
+ NumpadComma,
+ /// <kbd>. Del</kbd>. For locales where the decimal separator is "," (e.g.,
+ /// Brazil), this key may generate a <kbd>,</kbd>.
+ NumpadDecimal,
+ /// <kbd>/</kbd>
+ NumpadDivide,
+ NumpadEnter,
+ /// <kbd>=</kbd>
+ NumpadEqual,
+ /// <kbd>#</kbd> on a phone or remote control device. This key is typically found
+ /// below the <kbd>9</kbd> key and to the right of the <kbd>0</kbd> key.
+ NumpadHash,
+ /// <kbd>M</kbd> Add current entry to the value stored in memory.
+ NumpadMemoryAdd,
+ /// <kbd>M</kbd> Clear the value stored in memory.
+ NumpadMemoryClear,
+ /// <kbd>M</kbd> Replace the current entry with the value stored in memory.
+ NumpadMemoryRecall,
+ /// <kbd>M</kbd> Replace the value stored in memory with the current entry.
+ NumpadMemoryStore,
+ /// <kbd>M</kbd> Subtract current entry from the value stored in memory.
+ NumpadMemorySubtract,
+ /// <kbd>*</kbd> on a keyboard. For use with numpads that provide mathematical
+ /// operations (<kbd>+</kbd>, <kbd>-</kbd> <kbd>*</kbd> and <kbd>/</kbd>).
+ ///
+ /// Use `NumpadStar` for the <kbd>*</kbd> key on phones and remote controls.
+ NumpadMultiply,
+ /// <kbd>(</kbd> Found on the Microsoft Natural Keyboard.
+ NumpadParenLeft,
+ /// <kbd>)</kbd> Found on the Microsoft Natural Keyboard.
+ NumpadParenRight,
+ /// <kbd>*</kbd> on a phone or remote control device.
+ ///
+ /// This key is typically found below the <kbd>7</kbd> key and to the left of
+ /// the <kbd>0</kbd> key.
+ ///
+ /// Use <kbd>"NumpadMultiply"</kbd> for the <kbd>*</kbd> key on
+ /// numeric keypads.
+ NumpadStar,
+ /// <kbd>-</kbd>
+ NumpadSubtract,
+ /// <kbd>Esc</kbd> or <kbd>⎋</kbd>
+ Escape,
+ /// <kbd>Fn</kbd> This is typically a hardware key that does not generate a separate
+ /// code.
+ Fn,
+ /// <kbd>FLock</kbd> or <kbd>FnLock</kbd>. Function Lock key. Found on the Microsoft
+ /// Natural Keyboard.
+ FnLock,
+ /// <kbd>PrtScr SysRq</kbd> or <kbd>Print Screen</kbd>
+ PrintScreen,
+ /// <kbd>Scroll Lock</kbd>
+ ScrollLock,
+ /// <kbd>Pause Break</kbd>
+ Pause,
+ /// Some laptops place this key to the left of the <kbd>↑</kbd> key.
+ ///
+ /// This also the "back" button (triangle) on Android.
+ BrowserBack,
+ BrowserFavorites,
+ /// Some laptops place this key to the right of the <kbd>↑</kbd> key.
+ BrowserForward,
+ /// The "home" button on Android.
+ BrowserHome,
+ BrowserRefresh,
+ BrowserSearch,
+ BrowserStop,
+ /// <kbd>Eject</kbd> or <kbd>⏏</kbd>. This key is placed in the function section on
+ /// some Apple keyboards.
+ Eject,
+ /// Sometimes labelled <kbd>My Computer</kbd> on the keyboard
+ LaunchApp1,
+ /// Sometimes labelled <kbd>Calculator</kbd> on the keyboard
+ LaunchApp2,
+ LaunchMail,
+ MediaPlayPause,
+ MediaSelect,
+ MediaStop,
+ MediaTrackNext,
+ MediaTrackPrevious,
+ /// This key is placed in the function section on some Apple keyboards, replacing the
+ /// <kbd>Eject</kbd> key.
+ Power,
+ Sleep,
+ AudioVolumeDown,
+ AudioVolumeMute,
+ AudioVolumeUp,
+ WakeUp,
+ // Legacy modifier key. Also called "Super" in certain places.
+ Meta,
+ // Legacy modifier key.
+ Hyper,
+ Turbo,
+ Abort,
+ Resume,
+ Suspend,
+ /// Found on Sun’s USB keyboard.
+ Again,
+ /// Found on Sun’s USB keyboard.
+ Copy,
+ /// Found on Sun’s USB keyboard.
+ Cut,
+ /// Found on Sun’s USB keyboard.
+ Find,
+ /// Found on Sun’s USB keyboard.
+ Open,
+ /// Found on Sun’s USB keyboard.
+ Paste,
+ /// Found on Sun’s USB keyboard.
+ Props,
+ /// Found on Sun’s USB keyboard.
+ Select,
+ /// Found on Sun’s USB keyboard.
+ Undo,
+ /// Use for dedicated <kbd>ひらがな</kbd> key found on some Japanese word processing
+ /// keyboards.
+ Hiragana,
+ /// Use for dedicated <kbd>カタカナ</kbd> key found on some Japanese word processing
+ /// keyboards.
+ Katakana,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F1,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F2,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F3,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F4,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F5,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F6,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F7,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F8,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F9,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F10,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F11,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F12,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F13,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F14,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F15,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F16,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F17,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F18,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F19,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F20,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F21,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F22,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F23,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F24,
+ /// General-purpose function key.
+ F25,
+ /// General-purpose function key.
+ F26,
+ /// General-purpose function key.
+ F27,
+ /// General-purpose function key.
+ F28,
+ /// General-purpose function key.
+ F29,
+ /// General-purpose function key.
+ F30,
+ /// General-purpose function key.
+ F31,
+ /// General-purpose function key.
+ F32,
+ /// General-purpose function key.
+ F33,
+ /// General-purpose function key.
+ F34,
+ /// General-purpose function key.
+ F35,
+}
+
+impl TryFrom<winit::keyboard::KeyCode> for Key
+{
+ type Error = UnknownKeyCodeError;
+
+ fn try_from(key_code: winit::keyboard::KeyCode) -> Result<Self, Self::Error>
+ {
+ match key_code {
+ winit::keyboard::KeyCode::Backquote => Ok(Self::Backquote),
+ winit::keyboard::KeyCode::Backslash => Ok(Self::Backslash),
+ winit::keyboard::KeyCode::BracketLeft => Ok(Self::BracketLeft),
+ winit::keyboard::KeyCode::BracketRight => Ok(Self::BracketRight),
+ winit::keyboard::KeyCode::Comma => Ok(Self::Comma),
+ winit::keyboard::KeyCode::Digit0 => Ok(Self::Digit0),
+ winit::keyboard::KeyCode::Digit1 => Ok(Self::Digit1),
+ winit::keyboard::KeyCode::Digit2 => Ok(Self::Digit2),
+ winit::keyboard::KeyCode::Digit3 => Ok(Self::Digit3),
+ winit::keyboard::KeyCode::Digit4 => Ok(Self::Digit4),
+ winit::keyboard::KeyCode::Digit5 => Ok(Self::Digit5),
+ winit::keyboard::KeyCode::Digit6 => Ok(Self::Digit6),
+ winit::keyboard::KeyCode::Digit7 => Ok(Self::Digit7),
+ winit::keyboard::KeyCode::Digit8 => Ok(Self::Digit8),
+ winit::keyboard::KeyCode::Digit9 => Ok(Self::Digit9),
+ winit::keyboard::KeyCode::Equal => Ok(Self::Equal),
+ winit::keyboard::KeyCode::IntlBackslash => Ok(Self::IntlBackslash),
+ winit::keyboard::KeyCode::IntlRo => Ok(Self::IntlRo),
+ winit::keyboard::KeyCode::IntlYen => Ok(Self::IntlYen),
+ winit::keyboard::KeyCode::KeyA => Ok(Self::A),
+ winit::keyboard::KeyCode::KeyB => Ok(Self::B),
+ winit::keyboard::KeyCode::KeyC => Ok(Self::C),
+ winit::keyboard::KeyCode::KeyD => Ok(Self::D),
+ winit::keyboard::KeyCode::KeyE => Ok(Self::E),
+ winit::keyboard::KeyCode::KeyF => Ok(Self::F),
+ winit::keyboard::KeyCode::KeyG => Ok(Self::G),
+ winit::keyboard::KeyCode::KeyH => Ok(Self::H),
+ winit::keyboard::KeyCode::KeyI => Ok(Self::I),
+ winit::keyboard::KeyCode::KeyJ => Ok(Self::J),
+ winit::keyboard::KeyCode::KeyK => Ok(Self::K),
+ winit::keyboard::KeyCode::KeyL => Ok(Self::L),
+ winit::keyboard::KeyCode::KeyM => Ok(Self::M),
+ winit::keyboard::KeyCode::KeyN => Ok(Self::N),
+ winit::keyboard::KeyCode::KeyO => Ok(Self::O),
+ winit::keyboard::KeyCode::KeyP => Ok(Self::P),
+ winit::keyboard::KeyCode::KeyQ => Ok(Self::Q),
+ winit::keyboard::KeyCode::KeyR => Ok(Self::R),
+ winit::keyboard::KeyCode::KeyS => Ok(Self::S),
+ winit::keyboard::KeyCode::KeyT => Ok(Self::T),
+ winit::keyboard::KeyCode::KeyU => Ok(Self::U),
+ winit::keyboard::KeyCode::KeyV => Ok(Self::V),
+ winit::keyboard::KeyCode::KeyW => Ok(Self::W),
+ winit::keyboard::KeyCode::KeyX => Ok(Self::X),
+ winit::keyboard::KeyCode::KeyY => Ok(Self::Y),
+ winit::keyboard::KeyCode::KeyZ => Ok(Self::Z),
+ winit::keyboard::KeyCode::Minus => Ok(Self::Minus),
+ winit::keyboard::KeyCode::Period => Ok(Self::Period),
+ winit::keyboard::KeyCode::Quote => Ok(Self::Quote),
+ winit::keyboard::KeyCode::Semicolon => Ok(Self::Semicolon),
+ winit::keyboard::KeyCode::Slash => Ok(Self::Slash),
+ winit::keyboard::KeyCode::AltLeft => Ok(Self::AltLeft),
+ winit::keyboard::KeyCode::AltRight => Ok(Self::AltRight),
+ winit::keyboard::KeyCode::Backspace => Ok(Self::Backspace),
+ winit::keyboard::KeyCode::CapsLock => Ok(Self::CapsLock),
+ winit::keyboard::KeyCode::ContextMenu => Ok(Self::ContextMenu),
+ winit::keyboard::KeyCode::ControlLeft => Ok(Self::ControlLeft),
+ winit::keyboard::KeyCode::ControlRight => Ok(Self::ControlRight),
+ winit::keyboard::KeyCode::Enter => Ok(Self::Enter),
+ winit::keyboard::KeyCode::SuperLeft => Ok(Self::SuperLeft),
+ winit::keyboard::KeyCode::SuperRight => Ok(Self::SuperRight),
+ winit::keyboard::KeyCode::ShiftLeft => Ok(Self::ShiftLeft),
+ winit::keyboard::KeyCode::ShiftRight => Ok(Self::ShiftRight),
+ winit::keyboard::KeyCode::Space => Ok(Self::Space),
+ winit::keyboard::KeyCode::Tab => Ok(Self::Tab),
+ winit::keyboard::KeyCode::Convert => Ok(Self::Convert),
+ winit::keyboard::KeyCode::KanaMode => Ok(Self::KanaMode),
+ winit::keyboard::KeyCode::Lang1 => Ok(Self::Lang1),
+ winit::keyboard::KeyCode::Lang2 => Ok(Self::Lang2),
+ winit::keyboard::KeyCode::Lang3 => Ok(Self::Lang3),
+ winit::keyboard::KeyCode::Lang4 => Ok(Self::Lang4),
+ winit::keyboard::KeyCode::Lang5 => Ok(Self::Lang5),
+ winit::keyboard::KeyCode::NonConvert => Ok(Self::NonConvert),
+ winit::keyboard::KeyCode::Delete => Ok(Self::Delete),
+ winit::keyboard::KeyCode::End => Ok(Self::End),
+ winit::keyboard::KeyCode::Help => Ok(Self::Help),
+ winit::keyboard::KeyCode::Home => Ok(Self::Home),
+ winit::keyboard::KeyCode::Insert => Ok(Self::Insert),
+ winit::keyboard::KeyCode::PageDown => Ok(Self::PageDown),
+ winit::keyboard::KeyCode::PageUp => Ok(Self::PageUp),
+ winit::keyboard::KeyCode::ArrowDown => Ok(Self::ArrowDown),
+ winit::keyboard::KeyCode::ArrowLeft => Ok(Self::ArrowLeft),
+ winit::keyboard::KeyCode::ArrowRight => Ok(Self::ArrowRight),
+ winit::keyboard::KeyCode::ArrowUp => Ok(Self::ArrowUp),
+ winit::keyboard::KeyCode::NumLock => Ok(Self::NumLock),
+ winit::keyboard::KeyCode::Numpad0 => Ok(Self::Numpad0),
+ winit::keyboard::KeyCode::Numpad1 => Ok(Self::Numpad1),
+ winit::keyboard::KeyCode::Numpad2 => Ok(Self::Numpad2),
+ winit::keyboard::KeyCode::Numpad3 => Ok(Self::Numpad3),
+ winit::keyboard::KeyCode::Numpad4 => Ok(Self::Numpad4),
+ winit::keyboard::KeyCode::Numpad5 => Ok(Self::Numpad5),
+ winit::keyboard::KeyCode::Numpad6 => Ok(Self::Numpad6),
+ winit::keyboard::KeyCode::Numpad7 => Ok(Self::Numpad7),
+ winit::keyboard::KeyCode::Numpad8 => Ok(Self::Numpad8),
+ winit::keyboard::KeyCode::Numpad9 => Ok(Self::Numpad9),
+ winit::keyboard::KeyCode::NumpadAdd => Ok(Self::NumpadAdd),
+ winit::keyboard::KeyCode::NumpadBackspace => Ok(Self::NumpadBackspace),
+ winit::keyboard::KeyCode::NumpadClear => Ok(Self::NumpadClear),
+ winit::keyboard::KeyCode::NumpadClearEntry => Ok(Self::NumpadClearEntry),
+ winit::keyboard::KeyCode::NumpadComma => Ok(Self::NumpadComma),
+ winit::keyboard::KeyCode::NumpadDecimal => Ok(Self::NumpadDecimal),
+ winit::keyboard::KeyCode::NumpadDivide => Ok(Self::NumpadDivide),
+ winit::keyboard::KeyCode::NumpadEnter => Ok(Self::NumpadEnter),
+ winit::keyboard::KeyCode::NumpadEqual => Ok(Self::NumpadEqual),
+ winit::keyboard::KeyCode::NumpadHash => Ok(Self::NumpadHash),
+ winit::keyboard::KeyCode::NumpadMemoryAdd => Ok(Self::NumpadMemoryAdd),
+ winit::keyboard::KeyCode::NumpadMemoryClear => Ok(Self::NumpadMemoryClear),
+ winit::keyboard::KeyCode::NumpadMemoryRecall => Ok(Self::NumpadMemoryRecall),
+ winit::keyboard::KeyCode::NumpadMemoryStore => Ok(Self::NumpadMemoryStore),
+ winit::keyboard::KeyCode::NumpadMemorySubtract => {
+ Ok(Self::NumpadMemorySubtract)
+ }
+ winit::keyboard::KeyCode::NumpadMultiply => Ok(Self::NumpadMultiply),
+ winit::keyboard::KeyCode::NumpadParenLeft => Ok(Self::NumpadParenLeft),
+ winit::keyboard::KeyCode::NumpadParenRight => Ok(Self::NumpadParenRight),
+ winit::keyboard::KeyCode::NumpadStar => Ok(Self::NumpadStar),
+ winit::keyboard::KeyCode::NumpadSubtract => Ok(Self::NumpadSubtract),
+ winit::keyboard::KeyCode::Escape => Ok(Self::Escape),
+ winit::keyboard::KeyCode::Fn => Ok(Self::Fn),
+ winit::keyboard::KeyCode::FnLock => Ok(Self::FnLock),
+ winit::keyboard::KeyCode::PrintScreen => Ok(Self::PrintScreen),
+ winit::keyboard::KeyCode::ScrollLock => Ok(Self::ScrollLock),
+ winit::keyboard::KeyCode::Pause => Ok(Self::Pause),
+ winit::keyboard::KeyCode::BrowserBack => Ok(Self::BrowserBack),
+ winit::keyboard::KeyCode::BrowserFavorites => Ok(Self::BrowserFavorites),
+ winit::keyboard::KeyCode::BrowserForward => Ok(Self::BrowserForward),
+ winit::keyboard::KeyCode::BrowserHome => Ok(Self::BrowserHome),
+ winit::keyboard::KeyCode::BrowserRefresh => Ok(Self::BrowserRefresh),
+ winit::keyboard::KeyCode::BrowserSearch => Ok(Self::BrowserSearch),
+ winit::keyboard::KeyCode::BrowserStop => Ok(Self::BrowserStop),
+ winit::keyboard::KeyCode::Eject => Ok(Self::Eject),
+ winit::keyboard::KeyCode::LaunchApp1 => Ok(Self::LaunchApp1),
+ winit::keyboard::KeyCode::LaunchApp2 => Ok(Self::LaunchApp2),
+ winit::keyboard::KeyCode::LaunchMail => Ok(Self::LaunchMail),
+ winit::keyboard::KeyCode::MediaPlayPause => Ok(Self::MediaPlayPause),
+ winit::keyboard::KeyCode::MediaSelect => Ok(Self::MediaSelect),
+ winit::keyboard::KeyCode::MediaStop => Ok(Self::MediaStop),
+ winit::keyboard::KeyCode::MediaTrackNext => Ok(Self::MediaTrackNext),
+ winit::keyboard::KeyCode::MediaTrackPrevious => Ok(Self::MediaTrackPrevious),
+ winit::keyboard::KeyCode::Power => Ok(Self::Power),
+ winit::keyboard::KeyCode::Sleep => Ok(Self::Sleep),
+ winit::keyboard::KeyCode::AudioVolumeDown => Ok(Self::AudioVolumeDown),
+ winit::keyboard::KeyCode::AudioVolumeMute => Ok(Self::AudioVolumeMute),
+ winit::keyboard::KeyCode::AudioVolumeUp => Ok(Self::AudioVolumeUp),
+ winit::keyboard::KeyCode::WakeUp => Ok(Self::WakeUp),
+ winit::keyboard::KeyCode::Meta => Ok(Self::Meta),
+ winit::keyboard::KeyCode::Hyper => Ok(Self::Hyper),
+ winit::keyboard::KeyCode::Turbo => Ok(Self::Turbo),
+ winit::keyboard::KeyCode::Abort => Ok(Self::Abort),
+ winit::keyboard::KeyCode::Resume => Ok(Self::Resume),
+ winit::keyboard::KeyCode::Suspend => Ok(Self::Suspend),
+ winit::keyboard::KeyCode::Again => Ok(Self::Again),
+ winit::keyboard::KeyCode::Copy => Ok(Self::Copy),
+ winit::keyboard::KeyCode::Cut => Ok(Self::Cut),
+ winit::keyboard::KeyCode::Find => Ok(Self::Find),
+ winit::keyboard::KeyCode::Open => Ok(Self::Open),
+ winit::keyboard::KeyCode::Paste => Ok(Self::Paste),
+ winit::keyboard::KeyCode::Props => Ok(Self::Props),
+ winit::keyboard::KeyCode::Select => Ok(Self::Select),
+ winit::keyboard::KeyCode::Undo => Ok(Self::Undo),
+ winit::keyboard::KeyCode::Hiragana => Ok(Self::Hiragana),
+ winit::keyboard::KeyCode::Katakana => Ok(Self::Katakana),
+ winit::keyboard::KeyCode::F1 => Ok(Self::F1),
+ winit::keyboard::KeyCode::F2 => Ok(Self::F2),
+ winit::keyboard::KeyCode::F3 => Ok(Self::F3),
+ winit::keyboard::KeyCode::F4 => Ok(Self::F4),
+ winit::keyboard::KeyCode::F5 => Ok(Self::F5),
+ winit::keyboard::KeyCode::F6 => Ok(Self::F6),
+ winit::keyboard::KeyCode::F7 => Ok(Self::F7),
+ winit::keyboard::KeyCode::F8 => Ok(Self::F8),
+ winit::keyboard::KeyCode::F9 => Ok(Self::F9),
+ winit::keyboard::KeyCode::F10 => Ok(Self::F10),
+ winit::keyboard::KeyCode::F11 => Ok(Self::F11),
+ winit::keyboard::KeyCode::F12 => Ok(Self::F12),
+ winit::keyboard::KeyCode::F13 => Ok(Self::F13),
+ winit::keyboard::KeyCode::F14 => Ok(Self::F14),
+ winit::keyboard::KeyCode::F15 => Ok(Self::F15),
+ winit::keyboard::KeyCode::F16 => Ok(Self::F16),
+ winit::keyboard::KeyCode::F17 => Ok(Self::F17),
+ winit::keyboard::KeyCode::F18 => Ok(Self::F18),
+ winit::keyboard::KeyCode::F19 => Ok(Self::F19),
+ winit::keyboard::KeyCode::F20 => Ok(Self::F20),
+ winit::keyboard::KeyCode::F21 => Ok(Self::F21),
+ winit::keyboard::KeyCode::F22 => Ok(Self::F22),
+ winit::keyboard::KeyCode::F23 => Ok(Self::F23),
+ winit::keyboard::KeyCode::F24 => Ok(Self::F24),
+ winit::keyboard::KeyCode::F25 => Ok(Self::F25),
+ winit::keyboard::KeyCode::F26 => Ok(Self::F26),
+ winit::keyboard::KeyCode::F27 => Ok(Self::F27),
+ winit::keyboard::KeyCode::F28 => Ok(Self::F28),
+ winit::keyboard::KeyCode::F29 => Ok(Self::F29),
+ winit::keyboard::KeyCode::F30 => Ok(Self::F30),
+ winit::keyboard::KeyCode::F31 => Ok(Self::F31),
+ winit::keyboard::KeyCode::F32 => Ok(Self::F32),
+ winit::keyboard::KeyCode::F33 => Ok(Self::F33),
+ winit::keyboard::KeyCode::F34 => Ok(Self::F34),
+ winit::keyboard::KeyCode::F35 => Ok(Self::F35),
+ _ => Err(UnknownKeyCodeError),
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("Unknown key code")]
+pub struct UnknownKeyCodeError;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum KeyState
+{
+ Pressed,
+ Released,
+}
+
+impl KeyState
+{
+ #[must_use]
+ #[inline]
+ pub fn is_pressed(&self) -> bool
+ {
+ matches!(self, Self::Pressed)
+ }
+
+ #[must_use]
+ #[inline]
+ pub fn is_released(&self) -> bool
+ {
+ matches!(self, Self::Released)
+ }
+}
+
+impl From<winit::event::ElementState> for KeyState
+{
+ fn from(element_state: winit::event::ElementState) -> Self
+ {
+ match element_state {
+ winit::event::ElementState::Pressed => Self::Pressed,
+ winit::event::ElementState::Released => Self::Released,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct KeyData
+{
+ curr_state: KeyState,
+ previous_state: KeyState,
+}
+
+impl Default for KeyData
+{
+ fn default() -> Self
+ {
+ KeyData {
+ curr_state: KeyState::Released,
+ previous_state: KeyState::Released,
+ }
+ }
+}
diff --git a/engine/src/windowing/mouse.rs b/engine/src/windowing/mouse.rs
new file mode 100644
index 0000000..1afe594
--- /dev/null
+++ b/engine/src/windowing/mouse.rs
@@ -0,0 +1,136 @@
+use std::collections::HashMap;
+
+use ecs::Sole;
+
+use crate::vector::Vec2;
+
+#[derive(Debug, Default, Clone, Sole)]
+#[non_exhaustive]
+pub struct Motion
+{
+ pub position_delta: Vec2<f64>,
+}
+
+/// Mouse buttons.
+#[derive(Debug, Default, Sole)]
+pub struct Buttons
+{
+ map: HashMap<Button, ButtonData>,
+}
+
+impl Buttons
+{
+ pub fn get(&self, button: Button) -> ButtonState
+ {
+ let Some(button_data) = self.map.get(&button) else {
+ return ButtonState::Released;
+ };
+
+ button_data.current_state
+ }
+
+ pub fn get_previous(&self, button: Button) -> ButtonState
+ {
+ let Some(button_data) = self.map.get(&button) else {
+ return ButtonState::Released;
+ };
+
+ button_data.previous_state
+ }
+
+ pub fn set(&mut self, button: Button, button_state: ButtonState)
+ {
+ let button_data = self.map.entry(button).or_default();
+
+ button_data.current_state = button_state;
+ }
+
+ pub(crate) fn make_states_previous(&mut self)
+ {
+ for button_data in self.map.values_mut() {
+ button_data.previous_state = button_data.current_state;
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Button
+{
+ Left,
+ Right,
+ Middle,
+ Back,
+ Forward,
+ Other(u16),
+}
+
+impl From<winit::event::MouseButton> for Button
+{
+ fn from(mouse_button: winit::event::MouseButton) -> Self
+ {
+ match mouse_button {
+ winit::event::MouseButton::Left => Self::Left,
+ winit::event::MouseButton::Right => Self::Right,
+ winit::event::MouseButton::Middle => Self::Middle,
+ winit::event::MouseButton::Back => Self::Back,
+ winit::event::MouseButton::Forward => Self::Forward,
+ winit::event::MouseButton::Other(other_mouse_button) => {
+ Self::Other(other_mouse_button)
+ }
+ }
+ }
+}
+
+/// Mouse button state.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum ButtonState
+{
+ Pressed,
+ Released,
+}
+
+impl ButtonState
+{
+ #[must_use]
+ #[inline]
+ pub fn is_pressed(&self) -> bool
+ {
+ matches!(self, Self::Pressed)
+ }
+
+ #[must_use]
+ #[inline]
+ pub fn is_released(&self) -> bool
+ {
+ matches!(self, Self::Released)
+ }
+}
+
+impl From<winit::event::ElementState> for ButtonState
+{
+ fn from(element_state: winit::event::ElementState) -> Self
+ {
+ match element_state {
+ winit::event::ElementState::Pressed => Self::Pressed,
+ winit::event::ElementState::Released => Self::Released,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ButtonData
+{
+ current_state: ButtonState,
+ previous_state: ButtonState,
+}
+
+impl Default for ButtonData
+{
+ fn default() -> Self
+ {
+ Self {
+ current_state: ButtonState::Released,
+ previous_state: ButtonState::Released,
+ }
+ }
+}
diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs
new file mode 100644
index 0000000..79b2102
--- /dev/null
+++ b/engine/src/windowing/window.rs
@@ -0,0 +1,171 @@
+use std::borrow::Cow;
+
+use ecs::Component;
+
+use crate::data_types::dimens::Dimens;
+
+pub mod platform;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Id
+{
+ inner: winit::window::WindowId,
+}
+
+impl Id
+{
+ pub(crate) fn from_inner(inner: winit::window::WindowId) -> Self
+ {
+ Self { inner }
+ }
+}
+
+macro_rules! impl_creation_attributes_field_fns {
+ ($field: ident, ($($getter_ret_pre: tt)?), $getter_ret_type: ty, $field_type: ty) => {
+ impl CreationAttributes
+ {
+ pub fn $field(&self) -> $getter_ret_type
+ {
+ $($getter_ret_pre)? self.attrs.$field
+ }
+
+ paste::paste! {
+ pub fn [<with_ $field>](mut self, $field: impl Into<$field_type>) -> Self {
+ self.attrs.$field = $field.into();
+ self
+ }
+ }
+ }
+ };
+}
+
+#[derive(Debug, Component, Clone)]
+#[non_exhaustive]
+pub struct CreationAttributes
+{
+ attrs: winit::window::WindowAttributes,
+}
+
+impl_creation_attributes_field_fns!(title, (&), &str, String);
+impl_creation_attributes_field_fns!(transparent, (), bool, bool);
+
+impl CreationAttributes
+{
+ #[cfg(target_os = "linux")]
+ pub fn with_x11_visual(mut self, visual_id: XVisualID) -> Self
+ {
+ use winit::platform::x11::WindowAttributesExtX11;
+
+ self.attrs = self.attrs.with_x11_visual(visual_id);
+
+ self
+ }
+}
+
+impl CreationAttributes
+{
+ pub(crate) fn into_inner(self) -> winit::window::WindowAttributes
+ {
+ self.attrs
+ }
+}
+
+impl Default for CreationAttributes
+{
+ fn default() -> Self
+ {
+ CreationAttributes {
+ attrs: winit::window::WindowAttributes::default().with_title("Application"),
+ }
+ }
+}
+
+#[derive(Debug, Component, Clone, Copy)]
+pub struct CreationReady;
+
+#[derive(Debug, Component)]
+#[non_exhaustive]
+pub struct Window
+{
+ wid: Id,
+ pub title: Cow<'static, str>,
+ pub cursor_visible: bool,
+ pub cursor_grab_mode: CursorGrabMode,
+ inner_size: Dimens<u32>,
+}
+
+impl Window
+{
+ pub fn wid(&self) -> Id
+ {
+ self.wid
+ }
+
+ pub fn inner_size(&self) -> &Dimens<u32>
+ {
+ &self.inner_size
+ }
+
+ pub(crate) fn new(
+ winit_window: &winit::window::Window,
+ creation_attrs: &CreationAttributes,
+ ) -> Self
+ {
+ Self {
+ wid: Id::from_inner(winit_window.id()),
+ title: creation_attrs.title().to_string().into(),
+ cursor_visible: true,
+ cursor_grab_mode: CursorGrabMode::None,
+ inner_size: winit_window.inner_size().into(),
+ }
+ }
+
+ pub(crate) fn apply(&self, winit_window: &winit::window::Window)
+ {
+ winit_window.set_title(&self.title);
+ winit_window.set_cursor_visible(self.cursor_visible);
+ }
+
+ pub(crate) fn set_inner_size(&mut self, inner_size: Dimens<u32>)
+ {
+ self.inner_size = inner_size;
+ }
+}
+
+#[derive(Debug, Component)]
+pub struct Closed;
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum CursorGrabMode
+{
+ #[default]
+ None,
+
+ /// The cursor is locked to a specific position in the window.
+ Locked,
+}
+
+/// A unique identifier for an X11 visual.
+pub type XVisualID = u32;
+
+impl<P> From<winit::dpi::PhysicalSize<P>> for Dimens<P>
+{
+ fn from(size: winit::dpi::PhysicalSize<P>) -> Self
+ {
+ Self {
+ width: size.width,
+ height: size.height,
+ }
+ }
+}
+
+impl<P> From<Dimens<P>> for winit::dpi::PhysicalSize<P>
+{
+ fn from(dimens: Dimens<P>) -> Self
+ {
+ Self {
+ width: dimens.width,
+ height: dimens.height,
+ }
+ }
+}
diff --git a/engine/src/windowing/window/platform.rs b/engine/src/windowing/window/platform.rs
new file mode 100644
index 0000000..f3908a2
--- /dev/null
+++ b/engine/src/windowing/window/platform.rs
@@ -0,0 +1,12 @@
+#[cfg(x11_platform)]
+pub mod x11
+{
+ use std::ffi::c_void;
+
+ pub type XlibErrorHook = Box<dyn Fn(*mut c_void, *mut c_void) -> bool + Send + Sync>;
+
+ pub fn register_xlib_error_hook(hook: XlibErrorHook)
+ {
+ winit::platform::x11::register_xlib_error_hook(hook);
+ }
+}