,
}
@@ -28,8 +28,8 @@ impl GliumAssetManager {
impl ModelManager for GliumAssetManager {
fn load_gltf(&mut self, id: ModelID, path: &Path) {
- let model = load_gltf(path, self.facade.as_ref()).unwrap();
- self.models.insert(id, model);
+ let models = load_gltf(path, self.facade.as_ref()).unwrap();
+ self.models.insert(id, models);
}
fn unload_model(&mut self, id: ModelID) {
@@ -48,6 +48,6 @@ impl ModelManager for GliumAssetManager {
// }
fn get_model(&self, id: &ModelID) -> Option<&dyn Any> {
- self.models.get(id).map(|model| model as &dyn Any)
+ self.models.get(id).map(|models| models as &dyn Any)
}
}
diff --git a/glium_platform/src/gltf_loader.rs b/glium_platform/src/gltf_loader.rs
index e41bbfb..fe4ec37 100644
--- a/glium_platform/src/gltf_loader.rs
+++ b/glium_platform/src/gltf_loader.rs
@@ -1,4 +1,4 @@
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use glium::{backend::Facade, IndexBuffer, VertexBuffer};
use glium::index::PrimitiveType;
use std::{fmt::Debug, path::Path};
@@ -8,31 +8,16 @@ use glium::uniforms::{SamplerWrapFunction, MinifySamplerFilter, MagnifySamplerFi
use gltf::image::Format as GltfFormat;
use glam::Vec2;
-/// Load a glTF 2.0 file from disk and upload the first primitive to the GPU.
-pub fn load_gltf(path: P, facade: &dyn Facade) -> Result
+/// Load a glTF 2.0 file from disk and upload all primitives to the GPU.
+///
+/// Returns one [`Model`] per glTF primitive (across all meshes).
+pub fn load_gltf(path: P, facade: &dyn Facade) -> Result>
where
P: AsRef + Debug,
{
// -- parse the asset & bring buffer blobs into memory --
let (doc, buffers, images) = gltf::import(path.as_ref()).context("failed to import glTF file")?;
- // -- grab the very first mesh / primitive --
- let mesh = doc.meshes().next().context("glTF has no meshes")?;
- let primitive = mesh.primitives().next().context("mesh has no primitives")?;
-
- // ---------- MATERIAL ----------
- let mut mat = Material::default();
-
- let mat_idx = primitive.material().index().context("primitive has no material")?;
- let material = doc.materials().nth(mat_idx).unwrap();
- let pbr = material.pbr_metallic_roughness();
-
- // Factors --------------------------------------------------
- mat.base_color_factor = pbr.base_color_factor();
- mat.metal_factor = pbr.metallic_factor();
- mat.roughness_factor = pbr.roughness_factor();
- mat.emissive_factor = material.emissive_factor();
-
// Helper to update sampler settings from glTF sampler
fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) {
let sampler_info = t.sampler();
@@ -64,64 +49,92 @@ where
}
}
- // Base-color texture (sRGB)
- if let Some(info) = pbr.base_color_texture() {
- update_sampler(&mut mat, &info.texture());
- let view = info.texture().source().index();
- mat.base_color = Some(glium_srgb_texture(facade, &images[view])?);
- }
+ let mut out: Vec = Vec::new();
- // Metallic-Roughness (linear)
- if let Some(info) = pbr.metallic_roughness_texture() {
- update_sampler(&mut mat, &info.texture());
- let view = info.texture().source().index();
- mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?);
- }
+ for mesh in doc.meshes() {
+ for primitive in mesh.primitives() {
+ // ---------- MATERIAL ----------
+ let mut mat = Material::default();
+ let material = primitive.material();
+ let pbr = material.pbr_metallic_roughness();
- // Normal map (linear)
- if let Some(info) = primitive.material().normal_texture() {
- update_sampler(&mut mat, &info.texture());
- let view = info.texture().source().index();
- mat.normal = Some(glium_linear_texture(facade, &images[view])?);
- }
+ // Factors --------------------------------------------------
+ mat.base_color_factor = pbr.base_color_factor();
+ mat.metal_factor = pbr.metallic_factor();
+ mat.roughness_factor = pbr.roughness_factor();
+ mat.emissive_factor = material.emissive_factor();
- // Occlusion (linear)
- if let Some(info) = primitive.material().occlusion_texture() {
- update_sampler(&mut mat, &info.texture());
- let view = info.texture().source().index();
- mat.occlusion = Some(glium_linear_texture(facade, &images[view])?);
- }
+ // Base-color texture (sRGB)
+ if let Some(info) = pbr.base_color_texture() {
+ update_sampler(&mut mat, &info.texture());
+ let view = info.texture().source().index();
+ mat.base_color = Some(glium_srgb_texture(facade, &images[view])?);
+ }
- // Emissive (sRGB)
- if let Some(info) = primitive.material().emissive_texture() {
- update_sampler(&mut mat, &info.texture());
- let view = info.texture().source().index();
- mat.emissive = Some(glium_srgb_texture(facade, &images[view])?);
- }
+ // Metallic-Roughness (linear)
+ if let Some(info) = pbr.metallic_roughness_texture() {
+ update_sampler(&mut mat, &info.texture());
+ let view = info.texture().source().index();
+ mat.metallic_roughness = Some(glium_linear_texture(facade, &images[view])?);
+ }
- // KHR_texture_transform
- if let Some(tex) = pbr.base_color_texture() {
- if let Some(xform) = tex.texture_transform() {
- mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]);
- mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]);
+ // Normal map (linear)
+ if let Some(info) = material.normal_texture() {
+ update_sampler(&mut mat, &info.texture());
+ let view = info.texture().source().index();
+ mat.normal = Some(glium_linear_texture(facade, &images[view])?);
+ }
+
+ // Occlusion (linear)
+ if let Some(info) = material.occlusion_texture() {
+ update_sampler(&mut mat, &info.texture());
+ let view = info.texture().source().index();
+ mat.occlusion = Some(glium_linear_texture(facade, &images[view])?);
+ }
+
+ // Emissive (sRGB)
+ if let Some(info) = material.emissive_texture() {
+ update_sampler(&mut mat, &info.texture());
+ let view = info.texture().source().index();
+ mat.emissive = Some(glium_srgb_texture(facade, &images[view])?);
+ }
+
+ // KHR_texture_transform (base color only, for now)
+ if let Some(tex) = pbr.base_color_texture() {
+ if let Some(xform) = tex.texture_transform() {
+ mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]);
+ mat.uv_scale = Vec2::new(xform.scale()[0], xform.scale()[1]);
+ }
+ }
+
+ // ---- Vertex/index data ----
+ let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0));
+
+ let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect();
+ let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect();
+ let tex_coords: Vec<[f32; 2]> = reader
+ .read_tex_coords(0)
+ .map(|tc| tc.into_f32().collect())
+ .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
+ let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect();
+
+ // Interleave
+ let vertices: Vec = (0..positions.len())
+ .map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] })
+ .collect();
+
+ let vbuf = VertexBuffer::immutable(facade, &vertices)?;
+ let ibuf = IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &indices)?;
+
+ out.push(Model { mesh: Mesh { vbuf, ibuf }, material: mat });
}
}
- // ---- Vertex/index data ----
- let reader = primitive.reader(|buf| Some(&buffers[buf.index()].0));
+ if out.is_empty() {
+ bail!("glTF has no mesh primitives");
+ }
- let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect();
- let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect();
- let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0).map(|tc| tc.into_f32().collect()).unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
- let indices: Vec = reader.read_indices().context("missing indices")?.into_u32().collect();
-
- // Interleave
- let vertices: Vec = (0..positions.len()).map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }).collect();
-
- let vbuf = VertexBuffer::immutable(facade, &vertices)?;
- let ibuf = IndexBuffer ::immutable(facade, PrimitiveType::TrianglesList, &indices)?;
-
- Ok(Model { mesh: Mesh { vbuf, ibuf }, material: mat })
+ Ok(out)
}
/// Linear-space texture (RGBA8) from glTF image data.
diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs
index db8e56e..8f00d92 100644
--- a/glium_platform/src/platform.rs
+++ b/glium_platform/src/platform.rs
@@ -1,7 +1,7 @@
-use std::cell::RefCell;
+use std::cell::{RefCell, Cell};
use std::rc::Rc;
-use std::sync::{Arc, Mutex};
-use raidillon_platform::{Platform, PlatformContext, TimeContext};
+use std::sync::{Arc, Mutex, RwLock};
+use raidillon_platform::{Platform, PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef};
use glium::backend::glutin::Display;
use glium::backend::glutin::SimpleWindowBuilder;
use glium::glutin::surface::WindowSurface;
@@ -14,11 +14,12 @@ use raidillon_assets::ModelManagerRef;
use raidillon_core::engine::EngineTrait;
use raidillon_core::time;
use raidillon_core::time::Time;
-use crate::render::debug_ui::ImguiBridge;
-use crate::render::{BasicMeshRenderingSystem, SkyboxRenderingSystem};
+use crate::render::{BasicMeshRenderingSystem, DebugWireframeRenderingSystem, EguiRenderer, SkyboxRenderingSystem};
use crate::GliumAssetManager;
use glam::Vec3;
use winit::event::DeviceEvent::MouseMotion;
+use raidillon_core::EguiQueue;
+use raidillon_platform::settings::{Settings, default_config_path};
pub struct GliumPlatform> {
event_loop: EventLoop<()>,
@@ -28,6 +29,10 @@ pub struct GliumPlatform> {
asset_manager: ModelManagerRef,
engine: E,
time: time::Time,
+ egui_queue: Rc>,
+ settings: Arc>,
+ debug_wireframes: DebugWireframesRef,
+ should_egui_receive_input_events: Rc>,
}
impl> Platform for GliumPlatform {
@@ -50,9 +55,20 @@ impl> Platform for GliumPlatfor
let window = Arc::new(Mutex::new(window));
// Install rendering systems in order
- rendering_system_manager.add::(&display, window.clone());
- rendering_system_manager.add::(&display, window.clone());
- rendering_system_manager.add::(&display, window.clone());
+ rendering_system_manager.add::(&display, window.clone(), &event_loop);
+ rendering_system_manager.add::(&display, window.clone(), &event_loop);
+ rendering_system_manager.add::(&display, window.clone(), &event_loop);
+ rendering_system_manager.add::(&display, window.clone(), &event_loop);
+
+ let egui_queue = Rc::new(RefCell::new(EguiQueue::new()));
+
+ let settings = Arc::new(
+ RwLock::new(
+ Settings::load_or_default(default_config_path()).unwrap()
+ )
+ );
+ let debug_wireframes = Rc::new(RefCell::new(DebugWireframes::new()));
+ let should_egui_receive_input_events = Rc::new(Cell::new(false));
Self {
event_loop,
@@ -62,6 +78,10 @@ impl> Platform for GliumPlatfor
asset_manager,
engine,
time,
+ egui_queue,
+ settings,
+ debug_wireframes,
+ should_egui_receive_input_events,
}
}
@@ -77,9 +97,21 @@ impl> Platform for GliumPlatfor
frame_height: h as f32,
time_ctx: self.construct_time_ctx(),
window: self.window.clone(),
+ egui_queue: self.egui_queue.clone(),
+ settings: self.settings.clone(),
+ debug_wireframes: self.debug_wireframes.clone(),
+ should_egui_receive_input_events: self.should_egui_receive_input_events.clone(),
};
self.engine.initialize(ctx.clone());
+ self.settings.read().unwrap().display_settings.apply(&*self.window.lock().unwrap());
+
let _ = &self.event_loop.run(move |event, el| {
+ let settings_handle = self.settings.read().unwrap();
+ if settings_handle.display_settings.dirty {
+ settings_handle.display_settings.apply(&*self.window.lock().unwrap());
+ }
+ drop(settings_handle);
+
self.rendering_system_manager
.systems
.values_mut()
@@ -91,24 +123,30 @@ impl> Platform for GliumPlatfor
match event {
Event::WindowEvent { event, .. } => match event {
+ WindowEvent::Resized(size) => {
+ if size.width > 0 && size.height > 0 {
+ self.display.resize((size.width, size.height));
+ }
+ },
WindowEvent::CloseRequested => {
// TODO: Run uninitialize on renderer and engine
+ self.settings.read().unwrap().save_to_file(default_config_path());
el.exit();
},
WindowEvent::RedrawRequested => {
let mut target = self.display.draw();
target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0);
- let (scene, debug_ui_buffer) = (
- self.engine.current_scene(),
- self.engine.get_debug_ui_buffer(),
- );
+ let scene = self.engine.current_scene();
let mut context = RenderingContext {
scene,
target: &mut target,
+ display: &self.display,
asset_manager: self.asset_manager.clone(),
window: self.window.clone(),
- debug_ui_buffer,
+ egui_queue: self.egui_queue.clone(),
+ debug_wireframes: self.debug_wireframes.clone(),
env_light_dir: Vec3::new(0.0, -1.0, 0.0),
+ should_egui_receive_input_events: self.should_egui_receive_input_events.clone(),
};
self.rendering_system_manager
@@ -116,6 +154,9 @@ impl> Platform for GliumPlatfor
.values_mut()
.for_each(|system| system.render(&mut context));
+ // clear debug wireframes after rendering
+ self.debug_wireframes.borrow_mut().clear();
+
target.finish().unwrap();
}
_ => {},
diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs
index 663b2dd..6553cc0 100644
--- a/glium_platform/src/render/basic.rs
+++ b/glium_platform/src/render/basic.rs
@@ -9,6 +9,8 @@ use raidillon_assets::include_shader;
pub use raidillon_platform::Camera;
use glam::Vec3;
use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction};
+use winit::event::Event;
+use winit::event_loop::EventLoop;
use raidillon_ecs::{Transform, ModelID};
use raidillon_ecs::components::ModelHandle;
use crate::model::Model;
@@ -21,7 +23,7 @@ pub struct BasicMeshRenderingSystem {
}
impl RenderingSystem for BasicMeshRenderingSystem {
- fn initialize(display: &Display, _window: Arc>) -> Self {
+ fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> Self {
const VERT_SRC: &str = include_shader!("gl_textured.vert");
const FRAG_SRC: &str = include_shader!("gl_textured.frag");
@@ -57,7 +59,11 @@ impl RenderingSystem for BasicMeshRenderingSystem {
};
// Use HDR-derived environment light direction if provided, otherwise default to downward
- let light_dir: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) };
+ let light_dir_world: Vec3 = if ctx.env_light_dir.length_squared() > 0.0 { ctx.env_light_dir.normalize() } else { Vec3::new(0.0, -1.0, 0.0) };
+
+ // Transform light direction to view space (normals/positions are in view space)
+ let view_mat3 = glam::Mat3::from_mat4(cam.view());
+ let light_dir_view = (view_mat3 * light_dir_world).normalize();
let asset_manager = ctx.asset_manager.borrow();
@@ -67,41 +73,43 @@ impl RenderingSystem for BasicMeshRenderingSystem {
_ => continue,
};
- let model = match model.downcast_ref::() {
- Some(model) => model,
+ let models = match model.downcast_ref::>() {
+ Some(models) => models,
None => continue,
};
- let mesh = &model.mesh;
- let mat = &model.material;
+ for model in models {
+ let mesh = &model.mesh;
+ let mat = &model.material;
- let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex);
+ let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex);
- let mut sampler = tex_ref.sampled();
- sampler = sampler.wrap_function(SamplerWrapFunction::Repeat);
- sampler = sampler.minify_filter(MinifySamplerFilter::Linear);
- sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear);
+ let mut sampler = tex_ref.sampled();
+ sampler = sampler.wrap_function(SamplerWrapFunction::Repeat);
+ sampler = sampler.minify_filter(MinifySamplerFilter::Linear);
+ sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear);
- let c = mat.base_color_factor;
+ let c = mat.base_color_factor;
- let uniforms = uniform! {
- model: tr.matrix().to_cols_array_2d(),
- view: cam.view().to_cols_array_2d(),
- projection: cam.projection().to_cols_array_2d(),
- u_light: [light_dir.x, light_dir.y, light_dir.z],
- tex: sampler,
- color: [c[0], c[1], c[2]],
- uv_offset: [mat.uv_offset.x, mat.uv_offset.y],
- uv_scale: [mat.uv_scale.x, mat.uv_scale.y],
- };
+ let uniforms = uniform! {
+ model: tr.matrix().to_cols_array_2d(),
+ view: cam.view().to_cols_array_2d(),
+ projection: cam.projection().to_cols_array_2d(),
+ u_light: [light_dir_view.x, light_dir_view.y, light_dir_view.z],
+ tex: sampler,
+ color: [c[0], c[1], c[2]],
+ uv_offset: [mat.uv_offset.x, mat.uv_offset.y],
+ uv_scale: [mat.uv_scale.x, mat.uv_scale.y],
+ };
- ctx.target.draw(
- &mesh.vbuf,
- &mesh.ibuf,
- &self.program,
- &uniforms,
- &self.params,
- ).unwrap();
+ ctx.target.draw(
+ &mesh.vbuf,
+ &mesh.ibuf,
+ &self.program,
+ &uniforms,
+ &self.params,
+ ).unwrap();
+ }
}
}
}
diff --git a/glium_platform/src/render/debug_ui.rs b/glium_platform/src/render/debug_ui.rs
deleted file mode 100644
index ac4a06e..0000000
--- a/glium_platform/src/render/debug_ui.rs
+++ /dev/null
@@ -1,75 +0,0 @@
-use std::sync::{Arc, Mutex};
-use std::time::Instant;
-use glium::Display;
-use glium::glutin::surface::WindowSurface;
-use imgui::{Context as ImguiContext};
-use imgui_winit_support::{HiDpiMode, WinitPlatform};
-use imgui_glium_renderer::Renderer as ImguiGliumRenderer;
-use winit::window::Window;
-use winit::event::Event;
-use glium::Frame;
-use crate::RenderingSystem;
-use crate::system::RenderingContext;
-
-pub struct ImguiBridge {
- imgui: ImguiContext,
- platform: WinitPlatform,
- renderer: ImguiGliumRenderer,
- last_frame: Instant,
- rendered_this_frame: bool,
-}
-
-impl RenderingSystem for ImguiBridge {
- fn handle_event(&mut self, window: Arc>, event: Event<()>) {
- let window = window.lock().unwrap();
- self.platform.handle_event(self.imgui.io_mut(), &*window, &event);
- }
-
- fn prepare_frame(&mut self, window: Arc>) {
- self.rendered_this_frame = false;
- let now = Instant::now();
- self.imgui.io_mut().update_delta_time(now - self.last_frame);
- self.last_frame = now;
- let window = window.lock().unwrap();
- self.platform
- .prepare_frame(self.imgui.io_mut(), &*window)
- .expect("Failed to prepare frame");
- }
-
- fn render(&mut self, ctx: &mut RenderingContext) {
- if self.rendered_this_frame { return; }
- self.rendered_this_frame = true;
-
- let ui = self.imgui.frame();
- ctx.debug_ui_buffer.write_buffer(&ui);
-
- {
- let window = ctx.window.lock().unwrap();
- self.platform.prepare_render(&ui, &*window);
- }
- let draw_data = self.imgui.render();
- if draw_data.total_vtx_count == 0 && draw_data.total_idx_count == 0 {
- return;
- }
-
- self.renderer.render(ctx.target, draw_data).expect("imgui rendering failed");
- }
-
- fn initialize(display: &Display, window: Arc>) -> Self {
- let mut imgui = ImguiContext::create();
- imgui.set_ini_filename(None);
- let mut platform = WinitPlatform::new(&mut imgui);
- let window = window.lock().unwrap();
- platform.attach_window(imgui.io_mut(), &*window, HiDpiMode::Default);
- imgui.fonts().add_font(&[imgui::FontSource::DefaultFontData { config: None }]);
- let renderer = ImguiGliumRenderer::new(&mut imgui, display).unwrap();
-
- Self {
- imgui,
- platform,
- renderer,
- last_frame: Instant::now(),
- rendered_this_frame: false,
- }
- }
-}
diff --git a/glium_platform/src/render/debug_wireframe.rs b/glium_platform/src/render/debug_wireframe.rs
new file mode 100644
index 0000000..2f259e9
--- /dev/null
+++ b/glium_platform/src/render/debug_wireframe.rs
@@ -0,0 +1,80 @@
+use std::sync::{Arc, Mutex};
+use glium::{Display, Program, Surface, VertexBuffer, implement_vertex};
+use glium::glutin::surface::WindowSurface;
+use glium::index::PrimitiveType;
+use glium::uniform;
+use winit::event_loop::EventLoop;
+use raidillon_assets::include_shader;
+use crate::system::RenderingContext;
+use crate::RenderingSystem;
+pub use raidillon_platform::Camera;
+
+#[derive(Copy, Clone)]
+struct DebugVertex {
+ position: [f32; 3],
+ color: [f32; 4],
+}
+
+implement_vertex!(DebugVertex, position, color);
+
+/// renders debug wireframes from the shared buffer
+pub struct DebugWireframeRenderingSystem {
+ program: Program,
+ params: glium::DrawParameters<'static>,
+}
+
+impl RenderingSystem for DebugWireframeRenderingSystem {
+ fn initialize(display: &Display, _window: Arc>, _event_loop: &EventLoop<()>) -> Self {
+ const VERT_SRC: &str = include_shader!("debug_wireframe.vert");
+ const FRAG_SRC: &str = include_shader!("debug_wireframe.frag");
+
+ let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap();
+
+ let params = glium::DrawParameters {
+ depth: glium::Depth {
+ test: glium::draw_parameters::DepthTest::IfLess,
+ write: false,
+ ..Default::default()
+ },
+ line_width: Some(1.0),
+ ..Default::default()
+ };
+
+ Self { program, params }
+ }
+
+ fn render(&mut self, ctx: &mut RenderingContext) {
+ let debug_wireframes = ctx.debug_wireframes.borrow();
+
+ if !debug_wireframes.enabled || debug_wireframes.vertices.is_empty() {
+ return;
+ }
+
+ let cam = match ctx.scene.world.query::<&Camera>().iter().next() {
+ Some((_, cam)) => *cam,
+ None => return,
+ };
+
+ let vertices: Vec = debug_wireframes.vertices.iter()
+ .map(|v| DebugVertex { position: v.position, color: v.color })
+ .collect();
+
+ let vbuf = match VertexBuffer::new(ctx.display, &vertices) {
+ Ok(vb) => vb,
+ Err(_) => return,
+ };
+
+ let uniforms = uniform! {
+ view: cam.view().to_cols_array_2d(),
+ projection: cam.projection().to_cols_array_2d(),
+ };
+
+ ctx.target.draw(
+ &vbuf,
+ glium::index::NoIndices(PrimitiveType::LinesList),
+ &self.program,
+ &uniforms,
+ &self.params,
+ ).ok();
+ }
+}
diff --git a/glium_platform/src/render/egui.rs b/glium_platform/src/render/egui.rs
new file mode 100644
index 0000000..3cd0583
--- /dev/null
+++ b/glium_platform/src/render/egui.rs
@@ -0,0 +1,74 @@
+use std::sync::{Arc, Mutex};
+use egui::ViewportId;
+use glium::{Display, Frame};
+use glium::glutin::surface::WindowSurface;
+use winit::window::Window;
+use crate::RenderingSystem;
+use crate::system::RenderingContext;
+use egui_glium::EguiGlium;
+use winit::event::{Event, WindowEvent};
+use winit::event_loop::EventLoop;
+use std::cell::Cell;
+use std::rc::Rc;
+
+pub struct EguiRenderer {
+ egui_glium: EguiGlium,
+ should_egui_receive_input_events: Option>>,
+}
+
+impl RenderingSystem for EguiRenderer {
+ fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self
+ where
+ Self: Sized,
+ {
+ let window = window.lock().unwrap();
+ let egui_glium = EguiGlium::new(ViewportId::ROOT, &display, &window, &event_loop);
+
+ Self { egui_glium: egui_glium, should_egui_receive_input_events: None }
+ }
+
+ fn render(&mut self, ctx: &mut RenderingContext) {
+ if self.should_egui_receive_input_events.is_none() {
+ self.should_egui_receive_input_events = Some(ctx.should_egui_receive_input_events.clone());
+ }
+
+ let window = ctx.window.lock().unwrap();
+
+ self.egui_glium.run(&window, |egui_ctx| {
+ ctx.egui_queue.borrow_mut().run(egui_ctx);
+ });
+
+ self.egui_glium.paint(ctx.display, ctx.target);
+ }
+
+ fn handle_event(&mut self, window: Arc>, event: Event<()>) {
+ let window = window.lock().unwrap();
+ match event {
+ Event::WindowEvent { event, .. } => {
+ let should_egui_receive_input_events = match self.should_egui_receive_input_events.as_ref() {
+ Some(v) => v.get(),
+ None => true,
+ };
+
+ let should_send_event = if should_egui_receive_input_events {
+ true
+ } else {
+ !matches!(event,
+ WindowEvent::KeyboardInput { .. } |
+ WindowEvent::ModifiersChanged(_) |
+ WindowEvent::CursorMoved { .. } |
+ WindowEvent::MouseInput { .. } |
+ WindowEvent::MouseWheel { .. } |
+ WindowEvent::Touch(_) |
+ WindowEvent::Ime(_)
+ )
+ };
+
+ if should_send_event {
+ let _ = self.egui_glium.on_event(&window, &event);
+ }
+ }
+ _ => {},
+ }
+ }
+}
diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs
index f49e308..22aa047 100644
--- a/glium_platform/src/render/mod.rs
+++ b/glium_platform/src/render/mod.rs
@@ -1,6 +1,9 @@
mod basic;
-pub mod debug_ui;
mod skybox;
+mod egui;
+mod debug_wireframe;
pub use basic::BasicMeshRenderingSystem;
pub use skybox::SkyboxRenderingSystem;
+pub use egui::EguiRenderer;
+pub use debug_wireframe::DebugWireframeRenderingSystem;
diff --git a/glium_platform/src/render/skybox.rs b/glium_platform/src/render/skybox.rs
index ac22210..d11683b 100644
--- a/glium_platform/src/render/skybox.rs
+++ b/glium_platform/src/render/skybox.rs
@@ -7,7 +7,9 @@ use glium::glutin::surface::WindowSurface;
use glium::index::PrimitiveType;
use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d};
use glium::uniform;
+use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction};
use glam::{Mat4, Vec2, Vec3};
+use winit::event_loop::EventLoop;
use raidillon_assets::include_shader;
use crate::system::RenderingContext;
use crate::RenderingSystem;
@@ -109,7 +111,7 @@ impl SkyboxRenderingSystem {
}
impl RenderingSystem for SkyboxRenderingSystem {
- fn initialize(display: &Display, _window: Arc>) -> Self {
+ fn initialize(display: &Display, _window: Arc>, event_loop: &EventLoop<()>) -> Self {
const VERT_SRC: &str = include_shader!("skybox.vert");
const FRAG_SRC: &str = include_shader!("skybox.frag");
let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap();
@@ -117,7 +119,7 @@ impl RenderingSystem for SkyboxRenderingSystem {
// Load EXR from assets/exr
let manifest_dir = env!("CARGO_MANIFEST_DIR");
- let path = std::path::Path::new(manifest_dir).join("../assets/exr/qwantani_sunset_puresky_2k.exr");
+ let path = std::path::Path::new(manifest_dir).join("../assets/exr/citrus_orchard_road_puresky_4k.exr");
let (equirect_srgb, light_dir) = Self::load_hdr_equirect_and_analyze(display, &path);
Self { program, quad_vb, quad_ib, equirect_srgb, light_dir }
}
@@ -131,10 +133,15 @@ impl RenderingSystem for SkyboxRenderingSystem {
let mut view = cam.view();
// remove translation from view matrix (only orientation)
view.col_mut(3).x = 0.0; view.col_mut(3).y = 0.0; view.col_mut(3).z = 0.0;
+ let mut sampler = self.equirect_srgb.sampled();
+ sampler = sampler.wrap_function(SamplerWrapFunction::Repeat);
+ sampler = sampler.minify_filter(MinifySamplerFilter::Linear);
+ sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear);
+
let uniforms = uniform! {
view: view.to_cols_array_2d(),
projection: cam.projection().to_cols_array_2d(),
- equirect: &self.equirect_srgb,
+ equirect: sampler,
};
let params = glium::DrawParameters { depth: glium::Depth { test: glium::draw_parameters::DepthTest::IfLessOrEqual, write: false, ..Default::default() }, ..Default::default() };
ctx.target.draw(&self.quad_vb, &self.quad_ib, &self.program, &uniforms, ¶ms).ok();
diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs
index 51a292f..cf5f945 100644
--- a/glium_platform/src/system.rs
+++ b/glium_platform/src/system.rs
@@ -6,17 +6,23 @@ use indexmap::IndexMap;
use glium::{Display, Frame};
use glium::glutin::surface::WindowSurface;
use raidillon_assets::ModelManagerRef;
-use raidillon_core::{define_typemap, DebugUIBuffer};
+use raidillon_core::{define_typemap, EguiQueue};
use raidillon_core::scene::Scene;
+use raidillon_platform::DebugWireframesRef;
use glam::Vec3;
+use winit::event_loop::EventLoop;
+use std::cell::Cell;
pub struct RenderingContext<'a> {
pub scene: &'a Scene,
pub target: &'a mut Frame,
pub window: Arc>,
+ pub display: &'a Display,
pub asset_manager: ModelManagerRef,
- pub debug_ui_buffer: &'a DebugUIBuffer,
+ pub egui_queue: Rc>,
+ pub debug_wireframes: DebugWireframesRef,
pub env_light_dir: Vec3,
+ pub should_egui_receive_input_events: Rc>
}
/// The internal "rendering system" trait of glium_platform.
@@ -30,7 +36,7 @@ pub trait RenderingSystem {
}
fn prepare_frame(&mut self, _window: Arc>) {}
fn render(&mut self, ctx: &mut RenderingContext);
- fn initialize(display: &Display, window: Arc>) -> Self
+ fn initialize(display: &Display, window: Arc>, event_loop: &EventLoop<()>) -> Self
where
Self: Sized;
}
@@ -48,11 +54,11 @@ impl RenderingSystemManager {
}
}
- pub fn add(&mut self, display: &Display, window: Arc>)
+ pub fn add(&mut self, display: &Display, window: Arc>, event_loop: &EventLoop<()>)
where
R: RenderingSystem + 'static,
{
- let system = R::initialize(display, window);
+ let system = R::initialize(display, window, event_loop);
self.systems.insert(TypeId::of::(), Box::new(system));
}
diff --git a/platform/Cargo.toml b/platform/Cargo.toml
index 3b42bac..16fec56 100644
--- a/platform/Cargo.toml
+++ b/platform/Cargo.toml
@@ -8,3 +8,5 @@ winit = "0.30.12"
raidillon_core = { path = "../core" }
raidillon_assets = { path = "../asset" }
glam = "0.30.5"
+serde = { version = "1.0.228", features = ["derive"] }
+toml = "0.9.8"
diff --git a/platform/src/context.rs b/platform/src/context.rs
index 892b69e..31bd6cb 100644
--- a/platform/src/context.rs
+++ b/platform/src/context.rs
@@ -1,6 +1,66 @@
-use std::sync::{Arc, Mutex};
+use std::cell::Cell;
+use std::{cell::RefCell, rc::Rc};
+use std::sync::{Arc, Mutex, RwLock};
use winit::event::Event;
use raidillon_assets::ModelManagerRef;
+use raidillon_core::EguiQueue;
+use crate::settings::Settings;
+
+/// a single debug wireframe vertex with position and color
+#[derive(Clone, Copy)]
+pub struct DebugWireframeVertex {
+ pub position: [f32; 3],
+ pub color: [f32; 4],
+}
+
+/// shared buffer for debug wireframe rendering
+#[derive(Clone, Default)]
+pub struct DebugWireframes {
+ pub vertices: Vec,
+ pub enabled: bool,
+}
+
+impl DebugWireframes {
+ pub fn new() -> Self {
+ Self { vertices: Vec::new(), enabled: true }
+ }
+
+ pub fn clear(&mut self) {
+ self.vertices.clear();
+ }
+
+ /// add a single line segment
+ pub fn add_line(&mut self, start: [f32; 3], end: [f32; 3], color: [f32; 4]) {
+ self.vertices.push(DebugWireframeVertex { position: start, color });
+ self.vertices.push(DebugWireframeVertex { position: end, color });
+ }
+
+ /// add a wireframe box from min/max corners
+ pub fn add_box(&mut self, min: [f32; 3], max: [f32; 3], color: [f32; 4]) {
+ let [x0, y0, z0] = min;
+ let [x1, y1, z1] = max;
+
+ // bottom face edges
+ self.add_line([x0, y0, z0], [x1, y0, z0], color);
+ self.add_line([x1, y0, z0], [x1, y0, z1], color);
+ self.add_line([x1, y0, z1], [x0, y0, z1], color);
+ self.add_line([x0, y0, z1], [x0, y0, z0], color);
+
+ // top face edges
+ self.add_line([x0, y1, z0], [x1, y1, z0], color);
+ self.add_line([x1, y1, z0], [x1, y1, z1], color);
+ self.add_line([x1, y1, z1], [x0, y1, z1], color);
+ self.add_line([x0, y1, z1], [x0, y1, z0], color);
+
+ // vertical edges
+ self.add_line([x0, y0, z0], [x0, y1, z0], color);
+ self.add_line([x1, y0, z0], [x1, y1, z0], color);
+ self.add_line([x1, y0, z1], [x1, y1, z1], color);
+ self.add_line([x0, y0, z1], [x0, y1, z1], color);
+ }
+}
+
+pub type DebugWireframesRef = Rc>;
#[derive(Clone)]
pub struct PlatformContext {
@@ -10,6 +70,12 @@ pub struct PlatformContext {
pub frame_height: f32,
pub time_ctx: TimeContext,
pub window: Arc>,
+ pub egui_queue: Rc>,
+ pub settings: Arc>,
+ /// shared debug wireframe buffer
+ pub debug_wireframes: DebugWireframesRef,
+ /// sets whether or not egui will receive input events
+ pub should_egui_receive_input_events: Rc| >,
}
#[derive(Clone)]
diff --git a/platform/src/lib.rs b/platform/src/lib.rs
index 8875d35..ef88e08 100644
--- a/platform/src/lib.rs
+++ b/platform/src/lib.rs
@@ -2,7 +2,8 @@ pub mod platform;
mod camera;
mod event;
pub mod context;
+pub mod settings;
pub use platform::Platform;
pub use camera::Camera;
-pub use context::{PlatformContext, TimeContext};
+pub use context::{PlatformContext, TimeContext, DebugWireframes, DebugWireframesRef, DebugWireframeVertex};
diff --git a/platform/src/settings.rs b/platform/src/settings.rs
new file mode 100644
index 0000000..a901137
--- /dev/null
+++ b/platform/src/settings.rs
@@ -0,0 +1,166 @@
+use winit::monitor::{MonitorHandle, VideoModeHandle};
+use winit::window::{Fullscreen, Window};
+use serde::{Serialize, Deserialize};
+use std::error::Error;
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+
+pub fn default_config_path() -> PathBuf {
+ let exe_path = std::env::current_exe().unwrap();
+ let exe_dir = exe_path
+ .parent()
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "executable has no parent")).unwrap();
+
+ exe_dir.join("settings.toml")
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
+#[serde(rename_all = "snake_case")]
+pub enum WindowMode {
+ BorderlessFullscreen,
+ ExclusiveFullscreen,
+ #[default]
+ Windowed,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+pub struct Settings {
+ pub display_settings: DisplaySettings,
+}
+
+impl Settings {
+ pub fn load_from_file(path: impl AsRef) -> Result> {
+ let path = path.as_ref();
+ let text = fs::read_to_string(path)?;
+ let settings: Settings = toml::from_str(&text)?;
+ Ok(settings)
+ }
+
+ pub fn save_to_file(&self, path: impl AsRef) -> Result<(), Box> {
+ let path = path.as_ref();
+ if let Some(parent) = path.parent() {
+ fs::create_dir_all(parent)?;
+ }
+ let toml_str = toml::to_string_pretty(self)?;
+ fs::write(path, toml_str)?;
+ Ok(())
+ }
+
+ pub fn load_or_default(path: impl AsRef) -> Result> {
+ let path = path.as_ref();
+
+ match fs::read_to_string(path) {
+ Ok(text) => {
+ let settings: Settings = toml::from_str(&text)?;
+ Ok(settings)
+ }
+ Err(err) if err.kind() == io::ErrorKind::NotFound => {
+ let settings = Settings::default();
+ if let Some(parent) = path.parent() {
+ fs::create_dir_all(parent)?;
+ }
+ let toml_str = toml::to_string_pretty(&settings)?;
+ fs::write(path, toml_str)?;
+ Ok(settings)
+ }
+ Err(err) => Err(Box::new(err)),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(default)]
+pub struct DisplaySettings {
+ pub fullscreen_mode: WindowMode,
+ #[serde(skip)]
+ pub dirty: bool,
+}
+
+impl Default for DisplaySettings {
+ fn default() -> Self {
+ Self {
+ fullscreen_mode: WindowMode::Windowed,
+ dirty: false,
+ }
+ }
+}
+
+impl DisplaySettings {
+ pub fn apply(&self, window: &Window) {
+ // apply fullscreen mode
+ match self.fullscreen_mode {
+ WindowMode::BorderlessFullscreen => {
+ let monitor = window.current_monitor().or_else(|| window.primary_monitor());
+ window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
+ }
+ WindowMode::ExclusiveFullscreen => {
+ let monitor = window.current_monitor().or_else(|| window.primary_monitor());
+ match monitor {
+ Some(monitor) => {
+ if let Some(video_mode) = pick_best_video_mode(&monitor) {
+ window.set_fullscreen(Some(Fullscreen::Exclusive(video_mode)));
+ } else {
+ // fallback to borderless
+ window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor))));
+ }
+ }
+ None => {
+ // no monitor info, fallback to windowed
+ window.set_fullscreen(None);
+ }
+ }
+ }
+ WindowMode::Windowed => {
+ window.set_fullscreen(None);
+ },
+ }
+ }
+}
+
+fn pick_best_video_mode(monitor: &MonitorHandle) -> Option {
+ let target_size = monitor.size();
+
+ let mut best_native: Option = None;
+ let mut best_any: Option = None;
+
+ for mode in monitor.video_modes() {
+ if mode.size() == target_size {
+ let replace = match best_native.as_ref() {
+ None => true,
+ Some(best) => {
+ (mode.refresh_rate_millihertz(), mode.bit_depth())
+ > (best.refresh_rate_millihertz(), best.bit_depth())
+ }
+ };
+ if replace {
+ best_native = Some(mode.clone());
+ }
+ }
+
+ let replace = match best_any.as_ref() {
+ None => true,
+ Some(best) => is_better_video_mode(&mode, best),
+ };
+ if replace {
+ best_any = Some(mode);
+ }
+ }
+
+ best_native.or(best_any)
+}
+
+fn is_better_video_mode(a: &VideoModeHandle, b: &VideoModeHandle) -> bool {
+ let a_size = a.size();
+ let b_size = b.size();
+ let a_area = u64::from(a_size.width) * u64::from(a_size.height);
+ let b_area = u64::from(b_size.width) * u64::from(b_size.height);
+
+ match a_area.cmp(&b_area) {
+ std::cmp::Ordering::Greater => true,
+ std::cmp::Ordering::Less => false,
+ std::cmp::Ordering::Equal => {
+ (a.refresh_rate_millihertz(), a.bit_depth()) > (b.refresh_rate_millihertz(), b.bit_depth())
+ }
+ }
+}
| | |