Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active April 23, 2024 20:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save matthewjberger/db1c707c868f14be5bd8c3ed8c7094a2 to your computer and use it in GitHub Desktop.
Save matthewjberger/db1c707c868f14be5bd8c3ed8c7094a2 to your computer and use it in GitHub Desktop.
Rust window - winit 0.29.11, wgpu 0.19.1, egui 0.27.2
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
egui = "0.27.2"
egui-wgpu = { version = "0.27.2", features = ["winit"] }
egui-winit = "0.27.2"
pollster = "0.3.0"
wgpu = "0.19.1"
winit = "0.29.11"
fn main() {
App::new("Standalone Winit/Wgpu Example", 800, 600).run();
}
pub struct App<'window> {
event_loop: winit::event_loop::EventLoop<()>,
window: std::sync::Arc<winit::window::Window>,
renderer: Renderer<'window>,
gui_state: egui_winit::State,
}
impl<'window> App<'window> {
pub fn new(title: &str, width: u32, height: u32) -> Self {
let event_loop = winit::event_loop::EventLoop::new().unwrap();
let window = winit::window::WindowBuilder::new()
.with_title(title)
.with_inner_size(winit::dpi::PhysicalSize::new(width, height))
.with_transparent(true)
.build(&event_loop)
.unwrap();
let window = std::sync::Arc::new(window);
let renderer = pollster::block_on(Renderer::new(window.clone(), width, height));
let gui_context = egui::Context::default();
gui_context.set_pixels_per_point(window.scale_factor() as f32);
let viewport_id = gui_context.viewport_id();
let gui_state = egui_winit::State::new(
gui_context,
viewport_id,
&window,
Some(window.scale_factor() as _),
None,
);
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
Self {
event_loop,
window,
renderer,
gui_state,
}
}
pub fn run(self) {
let Self {
event_loop,
window,
mut renderer,
mut gui_state,
} = self;
event_loop
.run(move |event, elwt| {
match event {
winit::event::Event::WindowEvent { ref event, .. } => {
// Receive gui window event
if gui_state.on_window_event(&window, event).consumed {
return;
}
// If the gui didn't consume the event, handle it
match event {
winit::event::WindowEvent::KeyboardInput {
event:
winit::event::KeyEvent {
physical_key: winit::keyboard::PhysicalKey::Code(key_code),
..
},
..
} => {
// Exit by pressing the escape key
if matches!(key_code, winit::keyboard::KeyCode::Escape) {
elwt.exit();
}
}
// Close button handler
winit::event::WindowEvent::CloseRequested => {
println!("The close button was pressed; stopping");
elwt.exit();
}
winit::event::WindowEvent::Resized(winit::dpi::PhysicalSize {
width,
height,
}) => {
let (width, height) = ((*width).max(1), (*height).max(1));
println!("Resizing renderer surface to: ({width}, {height})");
renderer.resize(width, height);
}
_ => {}
}
}
winit::event::Event::AboutToWait => {
let gui_input = gui_state.take_egui_input(&window);
gui_state.egui_ctx().begin_frame(gui_input);
egui::Window::new("wgpu")
.resizable(false)
.fixed_pos((10.0, 10.0))
.show(gui_state.egui_ctx(), |ui| {
ui.heading("Hello, world!");
});
let egui::FullOutput {
textures_delta,
shapes,
pixels_per_point,
..
} = gui_state.egui_ctx().end_frame();
let paint_jobs = gui_state.egui_ctx().tessellate(shapes, pixels_per_point);
let screen_descriptor = {
let window_size = window.inner_size();
egui_wgpu::ScreenDescriptor {
size_in_pixels: [window_size.width, window_size.height],
pixels_per_point: window.scale_factor() as f32,
}
};
renderer.render_frame(screen_descriptor, paint_jobs, textures_delta);
}
_ => {}
}
})
.unwrap();
}
}
pub struct Renderer<'window> {
gpu: Gpu<'window>,
depth_texture_view: wgpu::TextureView,
egui_renderer: egui_wgpu::Renderer,
}
impl<'window> Renderer<'window> {
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub async fn new(
window: impl Into<wgpu::SurfaceTarget<'window>>,
width: u32,
height: u32,
) -> Self {
let gpu = Gpu::new_async(window, width, height).await;
let depth_texture_view = gpu.create_depth_texture(width, height);
let egui_renderer = egui_wgpu::Renderer::new(
&gpu.device,
gpu.surface_config.format,
Some(Self::DEPTH_FORMAT),
1,
);
Self {
gpu,
depth_texture_view,
egui_renderer,
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.gpu.resize(width, height);
self.depth_texture_view = self.gpu.create_depth_texture(width, height);
}
pub fn render_frame(
&mut self,
screen_descriptor: egui_wgpu::ScreenDescriptor,
paint_jobs: Vec<egui::ClippedPrimitive>,
textures_delta: egui::TexturesDelta,
) {
for (id, image_delta) in &textures_delta.set {
self.egui_renderer
.update_texture(&self.gpu.device, &self.gpu.queue, *id, image_delta);
}
for id in &textures_delta.free {
self.egui_renderer.free_texture(id);
}
let mut encoder = self
.gpu
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
self.egui_renderer.update_buffers(
&self.gpu.device,
&self.gpu.queue,
&mut encoder,
&paint_jobs,
&screen_descriptor,
);
let surface_texture = self
.gpu
.surface
.get_current_texture()
.expect("Failed to get surface texture!");
let surface_texture_view =
surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor {
label: wgpu::Label::default(),
aspect: wgpu::TextureAspect::default(),
format: Some(self.gpu.surface_format),
dimension: None,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
});
encoder.insert_debug_marker("Render scene");
// This scope around the crate::render_pass prevents the
// crate::render_pass from holding a borrow to the encoder,
// which would prevent calling `.finish()` in
// preparation for queue submission.
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &surface_texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.19,
g: 0.24,
b: 0.42,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
self.egui_renderer
.render(&mut render_pass, &paint_jobs, &screen_descriptor);
}
self.gpu.queue.submit(std::iter::once(encoder.finish()));
surface_texture.present();
}
}
pub struct Gpu<'window> {
pub surface: wgpu::Surface<'window>,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface_config: wgpu::SurfaceConfiguration,
pub surface_format: wgpu::TextureFormat,
}
impl<'window> Gpu<'window> {
pub fn alignment(&self) -> u64 {
self.device.limits().min_uniform_buffer_offset_alignment as wgpu::BufferAddress
}
pub fn aspect_ratio(&self) -> f32 {
self.surface_config.width as f32 / self.surface_config.height.max(1) as f32
}
pub fn resize(&mut self, width: u32, height: u32) {
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
}
pub fn create_depth_texture(&self, width: u32, height: u32) -> wgpu::TextureView {
let texture = self.device.create_texture(
&(wgpu::TextureDescriptor {
label: Some("Depth Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
}),
);
texture.create_view(&wgpu::TextureViewDescriptor {
label: None,
format: Some(wgpu::TextureFormat::Depth32Float),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
base_array_layer: 0,
array_layer_count: None,
mip_level_count: None,
})
}
pub async fn new_async(
window: impl Into<wgpu::SurfaceTarget<'window>>,
width: u32,
height: u32,
) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
..Default::default()
});
let surface = instance.create_surface(window).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect("Failed to request adapter!");
let (device, queue) = {
println!("WGPU Adapter Features: {:#?}", adapter.features());
adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("WGPU Device"),
required_features: wgpu::Features::all_webgpu_mask(),
required_limits: wgpu::Limits::default(),
},
None,
)
.await
.expect("Failed to request a device!")
};
let surface_capabilities = surface.get_capabilities(&adapter);
// This assumes an sRGB surface texture
let surface_format = surface_capabilities
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_capabilities.formats[0]);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width,
height,
present_mode: surface_capabilities.present_modes[0],
alpha_mode: surface_capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
Self {
surface,
device,
queue,
surface_config,
surface_format,
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment