use egui_wgpu_backend::wgpu; use egui_wgpu_backend::RenderPass as EguiRenderPass; use egui_winit::winit::{ self, dpi::PhysicalSize, event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; use legion::EntityStore; use parking_lot::RwLock; use std::sync::Arc; mod import; mod model; mod render; mod script; mod ui; struct Application { window: winit::window::Window, device: Arc, queue: Arc, size: winit::dpi::PhysicalSize, surface: wgpu::Surface, config: wgpu::SurfaceConfiguration, egui_state: egui_winit::State, egui_ctx: egui::Context, egui_rp: EguiRenderPass, ui: ui::UserInterface, viewport: ui::ViewportWidget, render_state: Arc>, file_receiver: crossbeam_channel::Receiver, objects: Vec, } impl Application { pub async fn new(window: winit::window::Window) -> Self { let size = window.inner_size(); let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); let surface = unsafe { instance.create_surface(&window) }; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::LowPower, compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .unwrap(); let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { features: wgpu::Features::empty(), limits: wgpu::Limits::default(), label: None, }, None, ) .await .unwrap(); let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: *surface.get_supported_formats(&adapter).first().unwrap(), width: size.width, height: size.height, present_mode: wgpu::PresentMode::Fifo, }; surface.configure(&device, &config); let device = Arc::new(device); let queue = Arc::new(queue); let egui_state = egui_winit::State::new(4096, &window); let egui_ctx = egui::Context::default(); let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1); let render_state = render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp); let viewport_texture = render_state .resources .get::() .unwrap() .viewport .egui_texture; let (file_sender, file_receiver) = crossbeam_channel::unbounded(); Self { window, device, queue, size, surface, config, egui_state, egui_ctx, egui_rp, ui: ui::UserInterface::new(file_sender), viewport: ui::ViewportWidget::new(viewport_texture), render_state: Arc::new(RwLock::new(render_state)), file_receiver, objects: vec![], } } pub fn update(&mut self) { self.window.request_redraw(); loop { match self.file_receiver.try_recv() { Ok(ui::FileEvent::Save) => println!("Saving!"), Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path), Ok(ui::FileEvent::Import(kind, path)) => { let model = match kind { ui::ImportKind::Stl => import::load_stl(path), ui::ImportKind::Gltf => import::load_gltf(path), }; let mut render_state = self.render_state.write(); let loaded = render_state.load_model(&model); let transform = glam::Mat4::IDENTITY; let widgets = render_state.spawn_model(&loaded, &transform); self.objects.extend(widgets.into_iter()); }, Ok(ui::FileEvent::LoadScript(path)) => { script::Script::new(&path, self.render_state.to_owned()); } Err(crossbeam_channel::TryRecvError::Empty) => break, Err(crossbeam_channel::TryRecvError::Disconnected) => { panic!("File event sender hung up!"); } } } let mut render_state = self.render_state.write(); for object in self.objects.iter_mut() { object.flush_dirty(|object| { let transform = object.transform.to_mat4(); for entity in object.entities.iter() { render_state .world .entry_mut(*entity) .unwrap() .get_component_mut::() .unwrap() .transform = transform; } }); } } pub fn on_resize(&mut self, new_size: PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); } } pub fn render(&mut self) { match self.surface.get_current_texture() { Err(wgpu::SurfaceError::Lost) => self.on_resize(self.size), Err(e) => panic!("Surface error: {:?}", e), Ok(surface_texture) => { puffin::GlobalProfiler::lock().new_frame(); let output = { puffin::profile_scope!("Draw egui"); let raw_input = self.egui_state.take_egui_input(&self.window); self.egui_ctx.run(raw_input, |ctx| { self.ui.run(ctx, &mut self.viewport, &mut self.objects) }) }; { puffin::profile_scope!("Main render"); let mut render_state = self.render_state.write(); render_state.update_viewport(&mut self.egui_rp, &mut self.viewport); render_state.render(); } puffin::profile_scope!("Render egui"); self.egui_state.handle_platform_output( &self.window, &self.egui_ctx, output.platform_output, ); let meshes = self.egui_ctx.tessellate(output.shapes); let screen_desc = egui_wgpu_backend::ScreenDescriptor { physical_width: self.config.width, physical_height: self.config.height, scale_factor: self.egui_ctx.pixels_per_point(), }; self.egui_rp .update_buffers(&self.device, &self.queue, &meshes, &screen_desc); self.egui_rp .add_textures(&self.device, &self.queue, &output.textures_delta) .unwrap(); let output_view = surface_texture.texture.create_view(&Default::default()); let mut cmds = self.device.create_command_encoder(&Default::default()); self.egui_rp .execute(&mut cmds, &output_view, &meshes, &screen_desc, None) .unwrap(); self.queue.submit(std::iter::once(cmds.finish())); surface_texture.present(); self.egui_rp.remove_textures(output.textures_delta).unwrap(); } } } } fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_title("Cyborg Editor") .build(&event_loop) .unwrap(); let mut app = pollster::block_on(Application::new(window)); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { Event::RedrawRequested(window_id) if window_id == app.window.id() => { app.render(); } Event::MainEventsCleared => { app.update(); if app.ui.should_quit() { *control_flow = ControlFlow::Exit; } } Event::WindowEvent { event, window_id } if window_id == app.window.id() => { match &event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Resized(physical_size) => { app.on_resize(*physical_size); } WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { app.on_resize(**new_inner_size); } _ => {} } app.egui_state.on_event(&app.egui_ctx, &event); } _ => (), } }); }