wgpu_examples/hello_windows/
mod.rs

1#![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        // Create all 16 windows.
92        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        // Initialize wgpu synchronously (native-only).
128        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                // Recreate the swap chain with the new size
192                if let Some(viewport) = viewports.get_mut(&window_id) {
193                    viewport.resize(device, new_size);
194                    // On macos the window needs to be redrawn manually after resizing
195                    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}