#![allow(irrefutable_let_patterns)] use blade_graphics as gpu; mod particle; struct Example { command_encoder: gpu::CommandEncoder, prev_sync_point: Option, context: gpu::Context, gui_painter: blade_egui::GuiPainter, particle_system: particle::System, } impl Example { fn new(window: &winit::window::Window) -> Self { let window_size = window.inner_size(); let context = unsafe { gpu::Context::init_windowed( window, gpu::ContextDesc { validation: cfg!(debug_assertions), capture: false, overlay: false, }, ) .unwrap() }; let surface_format = context.resize(gpu::SurfaceConfig { size: gpu::Extent { width: window_size.width, height: window_size.height, depth: 1, }, usage: gpu::TextureUsage::TARGET, display_sync: gpu::DisplaySync::Block, color_space: gpu::ColorSpace::Linear, }); let gui_painter = blade_egui::GuiPainter::new(surface_format, &context); let particle_system = particle::System::new( &context, particle::SystemDesc { name: "particle system", capacity: 100_000, draw_format: surface_format, }, ); let mut command_encoder = context.create_command_encoder(gpu::CommandEncoderDesc { name: "main", buffer_count: 2, }); command_encoder.start(); particle_system.reset(&mut command_encoder); let sync_point = context.submit(&mut command_encoder); Self { command_encoder, prev_sync_point: Some(sync_point), context, gui_painter, particle_system, } } fn destroy(&mut self) { if let Some(sp) = self.prev_sync_point.take() { self.context.wait_for(&sp, !0); } self.context .destroy_command_encoder(&mut self.command_encoder); self.gui_painter.destroy(&self.context); self.particle_system.destroy(&self.context); } fn render( &mut self, gui_primitives: &[egui::ClippedPrimitive], gui_textures: &egui::TexturesDelta, screen_desc: &blade_egui::ScreenDescriptor, ) { let frame = self.context.acquire_frame(); self.command_encoder.start(); self.command_encoder.init_texture(frame.texture()); self.gui_painter .update_textures(&mut self.command_encoder, gui_textures, &self.context); self.particle_system.update(&mut self.command_encoder); if let mut pass = self.command_encoder.render(gpu::RenderTargetSet { colors: &[gpu::RenderTarget { view: frame.texture_view(), init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), finish_op: gpu::FinishOp::Store, }], depth_stencil: None, }) { self.particle_system.draw(&mut pass); self.gui_painter .paint(&mut pass, gui_primitives, screen_desc, &self.context); } self.command_encoder.present(frame); let sync_point = self.context.submit(&mut self.command_encoder); self.gui_painter.after_submit(&sync_point); if let Some(sp) = self.prev_sync_point.take() { self.context.wait_for(&sp, !0); } self.prev_sync_point = Some(sync_point); } } fn main() { env_logger::init(); let event_loop = winit::event_loop::EventLoop::new().unwrap(); let window = winit::window::WindowBuilder::new() .with_title("blade-particle") .build(&event_loop) .unwrap(); let egui_ctx = egui::Context::default(); let viewport_id = egui_ctx.viewport_id(); let mut egui_winit = egui_winit::State::new(egui_ctx, viewport_id, &window, None, None); let mut example = Example::new(&window); event_loop .run(|event, target| { target.set_control_flow(winit::event_loop::ControlFlow::Poll); match event { winit::event::Event::AboutToWait => { window.request_redraw(); } winit::event::Event::WindowEvent { event, .. } => { let response = egui_winit.on_window_event(&window, &event); if response.consumed { return; } if response.repaint { window.request_redraw(); } match event { winit::event::WindowEvent::KeyboardInput { event: winit::event::KeyEvent { physical_key: winit::keyboard::PhysicalKey::Code(key_code), state: winit::event::ElementState::Pressed, .. }, .. } => match key_code { winit::keyboard::KeyCode::Escape => { target.exit(); } _ => {} }, winit::event::WindowEvent::CloseRequested => { target.exit(); } winit::event::WindowEvent::RedrawRequested => { let raw_input = egui_winit.take_egui_input(&window); let egui_output = egui_winit.egui_ctx().run(raw_input, |egui_ctx| { egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { ui.heading("Particle System"); example.particle_system.add_gui(ui); if ui.button("Quit").clicked() { target.exit(); } }); }); egui_winit.handle_platform_output(&window, egui_output.platform_output); let repaint_delay = egui_output.viewport_output[&viewport_id].repaint_delay; let pixels_per_point = egui_winit::pixels_per_point(egui_winit.egui_ctx(), &window); let primitives = egui_winit .egui_ctx() .tessellate(egui_output.shapes, pixels_per_point); let control_flow = if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_delay) { winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) } else { winit::event_loop::ControlFlow::Wait }; target.set_control_flow(control_flow); //Note: this will probably look different with proper support for resizing let window_size = window.inner_size(); let screen_desc = blade_egui::ScreenDescriptor { physical_size: (window_size.width, window_size.height), scale_factor: pixels_per_point, }; example.render(&primitives, &egui_output.textures_delta, &screen_desc); } _ => {} } } _ => {} } }) .unwrap(); example.destroy(); }