Created
November 1, 2025 09:03
-
-
Save ruccho/7c83dd811d9de6abdf2f0ac3fad7d873 to your computer and use it in GitHub Desktop.
Running egui on user-initialized wgpu and window
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::sync::{Arc, RwLock}; | |
| use anyhow::Result; | |
| use egui_wgpu::ScreenDescriptor; | |
| use tokio::runtime::Handle; | |
| use winit::{ | |
| application::ApplicationHandler, | |
| event::WindowEvent, | |
| event_loop::{ControlFlow, EventLoop}, | |
| window::Window, | |
| }; | |
| fn main() { | |
| env_logger::init(); | |
| let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); | |
| let _guard = runtime.enter(); | |
| entry().unwrap(); | |
| } | |
| pub fn entry() -> Result<()> { | |
| let event_loop = EventLoop::with_user_event().build()?; | |
| event_loop.set_control_flow(ControlFlow::Poll); | |
| let gfx_device = Handle::current().block_on(GfxDevice::new())?; | |
| let mut handler = AppHandler { | |
| gfx_device, | |
| app: Arc::new(RwLock::new(None)), | |
| window: None, | |
| }; | |
| event_loop.run_app(&mut handler)?; | |
| Ok(()) | |
| } | |
| struct AppHandler { | |
| gfx_device: GfxDevice, | |
| app: Arc<RwLock<Option<App>>>, | |
| window: Option<Arc<Window>>, | |
| } | |
| struct App { | |
| window: Arc<Window>, | |
| surface_format: wgpu::TextureFormat, | |
| size: winit::dpi::PhysicalSize<u32>, | |
| _instance: wgpu::Instance, | |
| device: wgpu::Device, | |
| queue: wgpu::Queue, | |
| surface: wgpu::Surface<'static>, | |
| frame_count: u64, | |
| egui_renderer: egui_wgpu::Renderer, | |
| egui_state: egui_winit::State, | |
| } | |
| #[derive(Debug, Clone)] | |
| struct GfxDevice { | |
| instance: wgpu::Instance, | |
| device: wgpu::Device, | |
| queue: wgpu::Queue, | |
| adapter: wgpu::Adapter, | |
| } | |
| impl GfxDevice { | |
| async fn new() -> Result<Self> { | |
| let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { | |
| backends: wgpu::Backends::from_env() | |
| .unwrap_or(wgpu::Backends::DX12 | wgpu::Backends::GL), // Vulkan has latency issue: https://github.com/emilk/egui/issues/5037 | |
| flags: wgpu::InstanceFlags::from_build_config().with_env(), | |
| backend_options: wgpu::BackendOptions::from_env_or_default(), | |
| }); | |
| let adapter = instance | |
| .request_adapter(&wgpu::RequestAdapterOptions { | |
| power_preference: wgpu::PowerPreference::HighPerformance, | |
| ..Default::default() | |
| }) | |
| .await?; | |
| let (device, queue) = adapter | |
| .request_device(&wgpu::DeviceDescriptor::default()) | |
| .await?; | |
| Ok(Self { | |
| instance, | |
| device, | |
| queue, | |
| adapter, | |
| }) | |
| } | |
| } | |
| impl App { | |
| async fn new( | |
| gfx_device: GfxDevice, | |
| surface: wgpu::Surface<'static>, | |
| window: Arc<Window>, | |
| ) -> Result<Self> { | |
| let size = window.inner_size(); | |
| let cap = surface.get_capabilities(&gfx_device.adapter); | |
| let surface_format = cap.formats[0]; | |
| let egui_renderer = | |
| egui_wgpu::Renderer::new(&gfx_device.device, surface_format, None, 1, false); | |
| let egui_ctx = egui::Context::default(); | |
| let egui_state = egui_winit::State::new( | |
| egui_ctx, | |
| egui::viewport::ViewportId::ROOT, | |
| &window, | |
| Some(window.scale_factor() as f32), | |
| None, | |
| None, | |
| ); | |
| let app = Self { | |
| window, | |
| _instance: gfx_device.instance, | |
| surface_format, | |
| size, | |
| device: gfx_device.device, | |
| queue: gfx_device.queue, | |
| surface, | |
| frame_count: 0, | |
| egui_renderer, | |
| egui_state, | |
| }; | |
| app.configure_surface(); | |
| Ok(app) | |
| } | |
| fn configure_surface(&self) { | |
| let surface_config = wgpu::SurfaceConfiguration { | |
| usage: wgpu::TextureUsages::RENDER_ATTACHMENT, | |
| format: self.surface_format, | |
| width: self.size.width, | |
| height: self.size.height, | |
| present_mode: wgpu::PresentMode::AutoVsync, | |
| desired_maximum_frame_latency: 2, | |
| alpha_mode: wgpu::CompositeAlphaMode::Auto, | |
| view_formats: vec![self.surface_format.add_srgb_suffix()], | |
| }; | |
| self.surface.configure(&self.device, &surface_config); | |
| } | |
| fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { | |
| self.size = new_size; | |
| // reconfigure the surface | |
| self.configure_surface(); | |
| } | |
| fn render(&mut self, egui_output: egui::FullOutput) { | |
| self.window.pre_present_notify(); | |
| let desc = ScreenDescriptor { | |
| size_in_pixels: [self.size.width, self.size.height], | |
| pixels_per_point: egui_output.pixels_per_point, | |
| }; | |
| self.egui_state | |
| .handle_platform_output(&self.window, egui_output.platform_output); | |
| let clipped_primitives = self.egui_state.egui_ctx().tessellate( | |
| egui_output.shapes, | |
| self.egui_state.egui_ctx().pixels_per_point(), | |
| ); | |
| for (id, image_delta) in egui_output.textures_delta.set { | |
| self.egui_renderer | |
| .update_texture(&self.device, &self.queue, id, &image_delta); | |
| } | |
| let surface_texture = self | |
| .surface | |
| .get_current_texture() | |
| .expect("failed to acquire next swapchain texture"); | |
| let texture_view = surface_texture | |
| .texture | |
| .create_view(&wgpu::TextureViewDescriptor { | |
| format: Some(self.surface_format.add_srgb_suffix()), | |
| ..Default::default() | |
| }); | |
| let mut encoder = self.device.create_command_encoder(&Default::default()); | |
| self.egui_renderer.update_buffers( | |
| &self.device, | |
| &self.queue, | |
| &mut encoder, | |
| &clipped_primitives, | |
| &desc, | |
| ); | |
| { | |
| let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { | |
| label: None, | |
| color_attachments: &[Some(wgpu::RenderPassColorAttachment { | |
| view: &texture_view, | |
| // depth_slice: None, | |
| resolve_target: None, | |
| ops: wgpu::Operations { | |
| load: wgpu::LoadOp::Clear(if self.frame_count.is_multiple_of(2) { | |
| wgpu::Color::RED | |
| } else { | |
| wgpu::Color::GREEN | |
| }), | |
| store: wgpu::StoreOp::Store, | |
| }, | |
| })], | |
| depth_stencil_attachment: None, | |
| timestamp_writes: None, | |
| occlusion_query_set: None, | |
| }); | |
| self.frame_count += 1; | |
| let mut render_pass = render_pass.forget_lifetime(); | |
| self.egui_renderer | |
| .render(&mut render_pass, &clipped_primitives, &desc); | |
| drop(render_pass); | |
| } | |
| self.queue.submit([encoder.finish()]); | |
| for x in &egui_output.textures_delta.free { | |
| self.egui_renderer.free_texture(x); | |
| } | |
| surface_texture.present(); | |
| } | |
| fn update_egui(&mut self) -> egui::FullOutput { | |
| let raw_input = self.egui_state.take_egui_input(&self.window); | |
| self.egui_state.egui_ctx().run(raw_input, |ctx| { | |
| egui::CentralPanel::default().show(ctx, |ui| { | |
| ui.label("Hello world!"); | |
| if ui.button("Click me").clicked() { | |
| println!("Clicked!"); | |
| } | |
| }); | |
| }) | |
| } | |
| } | |
| impl ApplicationHandler for AppHandler { | |
| fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { | |
| let mut attrs = Window::default_attributes(); | |
| attrs.title = String::from("Xukuro"); | |
| let window = event_loop.create_window(attrs).unwrap(); | |
| let window = Arc::new(window); | |
| let surface = self | |
| .gfx_device | |
| .instance | |
| .create_surface(window.clone()) | |
| .unwrap(); | |
| let gfx_device = self.gfx_device.clone(); | |
| let cell = Arc::downgrade(&self.app); | |
| self.window = window.clone().into(); | |
| tokio::spawn(async move { | |
| let app = App::new(gfx_device, surface, window).await.unwrap(); | |
| let Some(cell) = cell.upgrade() else { | |
| return; | |
| }; | |
| let mut cell = cell.write().unwrap(); | |
| cell.replace(app); | |
| }); | |
| } | |
| fn window_event( | |
| &mut self, | |
| event_loop: &winit::event_loop::ActiveEventLoop, | |
| _window_id: winit::window::WindowId, | |
| event: winit::event::WindowEvent, | |
| ) { | |
| if let Some(app) = &mut *self.app.write().unwrap() { | |
| let response = app.egui_state.on_window_event(&app.window, &event); | |
| if response.consumed { | |
| return; | |
| } | |
| match event { | |
| WindowEvent::CloseRequested => { | |
| event_loop.exit(); | |
| } | |
| WindowEvent::RedrawRequested => { | |
| let egui = app.update_egui(); | |
| app.render(egui); | |
| self.window.as_ref().unwrap().request_redraw(); | |
| } | |
| WindowEvent::Resized(size) => { | |
| app.resize(size); | |
| self.window.as_ref().unwrap().request_redraw(); | |
| } | |
| _ => (), | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment