Skip to content

Instantly share code, notes, and snippets.

@ruccho
Created November 1, 2025 09:03
Show Gist options
  • Select an option

  • Save ruccho/7c83dd811d9de6abdf2f0ac3fad7d873 to your computer and use it in GitHub Desktop.

Select an option

Save ruccho/7c83dd811d9de6abdf2f0ac3fad7d873 to your computer and use it in GitHub Desktop.
Running egui on user-initialized wgpu and window
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