wgpu_examples/hello_windows/
mod.rs1#![cfg_attr(target_arch = "wasm32", allow(dead_code, unused_imports))]
2
3use std::{collections::HashMap, sync::Arc};
4use wgpu::CurrentSurfaceTexture;
5use winit::{
6 application::ApplicationHandler,
7 event::WindowEvent,
8 event_loop::ActiveEventLoop,
9 window::{Window, WindowId},
10};
11
12struct ViewportDesc {
13 window: Arc<Window>,
14 background: wgpu::Color,
15 surface: wgpu::Surface<'static>,
16}
17
18struct Viewport {
19 desc: ViewportDesc,
20 config: wgpu::SurfaceConfiguration,
21}
22
23impl ViewportDesc {
24 fn new(window: Arc<Window>, background: wgpu::Color, instance: &wgpu::Instance) -> Self {
25 let surface = instance.create_surface(window.clone()).unwrap();
26 Self {
27 window,
28 background,
29 surface,
30 }
31 }
32
33 fn build(self, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Viewport {
34 let size = self.window.inner_size();
35 let config = self
36 .surface
37 .get_default_config(adapter, size.width, size.height)
38 .unwrap();
39 self.surface.configure(device, &config);
40 Viewport { desc: self, config }
41 }
42}
43
44impl Viewport {
45 fn resize(&mut self, device: &wgpu::Device, size: winit::dpi::PhysicalSize<u32>) {
46 self.config.width = size.width;
47 self.config.height = size.height;
48 self.desc.surface.configure(device, &self.config);
49 }
50
51 fn get_current_texture(&mut self) -> CurrentSurfaceTexture {
52 self.desc.surface.get_current_texture()
53 }
54}
55
56const WINDOW_SIZE: u32 = 128;
57const WINDOW_PADDING: u32 = 16;
58const WINDOW_TITLEBAR: u32 = 32;
59const WINDOW_OFFSET: u32 = WINDOW_SIZE + WINDOW_PADDING;
60const ROWS: u32 = 4;
61const COLUMNS: u32 = 4;
62
63enum AppState {
64 Uninitialized,
65 Running {
66 instance: wgpu::Instance,
67 device: wgpu::Device,
68 queue: wgpu::Queue,
69 viewports: HashMap<WindowId, Viewport>,
70 },
71}
72
73struct App {
74 state: AppState,
75}
76
77impl App {
78 fn new() -> Self {
79 Self {
80 state: AppState::Uninitialized,
81 }
82 }
83}
84
85impl ApplicationHandler for App {
86 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
87 if !matches!(self.state, AppState::Uninitialized) {
88 return;
89 }
90
91 let mut windows: Vec<(Arc<Window>, wgpu::Color)> =
93 Vec::with_capacity((ROWS * COLUMNS) as usize);
94 for row in 0..ROWS {
95 for column in 0..COLUMNS {
96 let window = Arc::new(
97 event_loop
98 .create_window(
99 Window::default_attributes()
100 .with_title(format!("x{column}y{row}"))
101 .with_inner_size(winit::dpi::PhysicalSize::new(
102 WINDOW_SIZE,
103 WINDOW_SIZE,
104 )),
105 )
106 .unwrap(),
107 );
108 window.set_outer_position(winit::dpi::PhysicalPosition::new(
109 WINDOW_PADDING + column * WINDOW_OFFSET,
110 WINDOW_PADDING + row * (WINDOW_OFFSET + WINDOW_TITLEBAR),
111 ));
112 fn frac(index: u32, max: u32) -> f64 {
113 index as f64 / max as f64
114 }
115 windows.push((
116 window,
117 wgpu::Color {
118 r: frac(row, ROWS),
119 g: 0.5 - frac(row * column, ROWS * COLUMNS) * 0.5,
120 b: frac(column, COLUMNS),
121 a: 1.0,
122 },
123 ));
124 }
125 }
126
127 let instance =
129 wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle_from_env(
130 Box::new(event_loop.owned_display_handle()),
131 ));
132 let viewport_descs: Vec<_> = windows
133 .into_iter()
134 .map(|(window, color)| ViewportDesc::new(window, color, &instance))
135 .collect();
136 let (adapter, device, queue) = pollster::block_on(async {
137 let adapter = instance
138 .request_adapter(&wgpu::RequestAdapterOptions {
139 compatible_surface: viewport_descs.first().map(|desc| &desc.surface),
140 ..Default::default()
141 })
142 .await
143 .expect("Failed to find an appropriate adapter");
144
145 let (device, queue) = adapter
146 .request_device(&wgpu::DeviceDescriptor {
147 label: None,
148 required_features: wgpu::Features::empty(),
149 required_limits: wgpu::Limits::downlevel_defaults(),
150 experimental_features: wgpu::ExperimentalFeatures::disabled(),
151 memory_hints: wgpu::MemoryHints::MemoryUsage,
152 trace: wgpu::Trace::Off,
153 })
154 .await
155 .expect("Failed to create device");
156
157 (adapter, device, queue)
158 });
159
160 let viewports: HashMap<WindowId, Viewport> = viewport_descs
161 .into_iter()
162 .map(|desc| (desc.window.id(), desc.build(&adapter, &device)))
163 .collect();
164
165 self.state = AppState::Running {
166 instance,
167 device,
168 queue,
169 viewports,
170 };
171 }
172
173 fn window_event(
174 &mut self,
175 event_loop: &ActiveEventLoop,
176 window_id: WindowId,
177 event: WindowEvent,
178 ) {
179 let AppState::Running {
180 instance,
181 device,
182 queue,
183 viewports,
184 } = &mut self.state
185 else {
186 return;
187 };
188
189 match event {
190 WindowEvent::Resized(new_size) => {
191 if let Some(viewport) = viewports.get_mut(&window_id) {
193 viewport.resize(device, new_size);
194 viewport.desc.window.request_redraw();
196 }
197 }
198 WindowEvent::RedrawRequested => {
199 if let Some(viewport) = viewports.get_mut(&window_id) {
200 let frame = match viewport.get_current_texture() {
201 CurrentSurfaceTexture::Success(frame) => frame,
202 CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => {
203 viewport.desc.window.request_redraw();
204 return;
205 }
206 CurrentSurfaceTexture::Suboptimal(_) | CurrentSurfaceTexture::Outdated => {
207 viewport.desc.surface.configure(device, &viewport.config);
208 viewport.desc.window.request_redraw();
209 return;
210 }
211 CurrentSurfaceTexture::Validation => {
212 unreachable!(
213 "No error scope registered, so validation errors will panic"
214 )
215 }
216 CurrentSurfaceTexture::Lost => {
217 viewport.desc.surface = instance
218 .create_surface(viewport.desc.window.clone())
219 .unwrap();
220 viewport.desc.surface.configure(device, &viewport.config);
221 viewport.desc.window.request_redraw();
222 return;
223 }
224 };
225
226 let view = frame
227 .texture
228 .create_view(&wgpu::TextureViewDescriptor::default());
229 let mut encoder = device
230 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
231 {
232 let _rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
233 label: None,
234 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
235 view: &view,
236 depth_slice: None,
237 resolve_target: None,
238 ops: wgpu::Operations {
239 load: wgpu::LoadOp::Clear(viewport.desc.background),
240 store: wgpu::StoreOp::Store,
241 },
242 })],
243 depth_stencil_attachment: None,
244 timestamp_writes: None,
245 occlusion_query_set: None,
246 multiview_mask: None,
247 });
248 }
249
250 queue.submit(Some(encoder.finish()));
251 viewport.desc.window.pre_present_notify();
252 frame.present();
253 }
254 }
255 WindowEvent::Occluded(is_occluded) => {
256 if !is_occluded {
257 if let Some(viewport) = viewports.get(&window_id) {
258 viewport.desc.window.request_redraw();
259 }
260 }
261 }
262 WindowEvent::CloseRequested => {
263 viewports.remove(&window_id);
264 if viewports.is_empty() {
265 event_loop.exit();
266 }
267 }
268 _ => {}
269 }
270 }
271}
272
273pub fn main() {
274 #[cfg(not(target_arch = "wasm32"))]
275 {
276 env_logger::init();
277 let event_loop = winit::event_loop::EventLoop::new().unwrap();
278 let mut app = App::new();
279 event_loop.run_app(&mut app).unwrap();
280 }
281 #[cfg(target_arch = "wasm32")]
282 {
283 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
284 panic!("wasm32 is not supported")
285 }
286}