Last active
April 23, 2024 20:32
-
-
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
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
[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" |
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
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