wgpu_examples/hello_triangle/
mod.rs1use std::{borrow::Cow, future::Future, sync::Arc};
2use wgpu::CurrentSurfaceTexture;
3use winit::{
4 application::ApplicationHandler,
5 event::WindowEvent,
6 event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
7 window::Window,
8};
9
10#[cfg(not(target_arch = "wasm32"))]
13fn spawn(f: impl Future<Output = ()> + 'static) {
14 pollster::block_on(f);
15}
16
17#[cfg(target_arch = "wasm32")]
20fn spawn(f: impl Future<Output = ()> + 'static) {
21 wasm_bindgen_futures::spawn_local(f);
22}
23
24struct WgpuState {
25 instance: wgpu::Instance,
26 window: Arc<Window>,
27 device: wgpu::Device,
28 queue: wgpu::Queue,
29 surface: wgpu::Surface<'static>,
30 config: wgpu::SurfaceConfiguration,
31 render_pipeline: wgpu::RenderPipeline,
32}
33
34enum TriangleAction {
35 Initialized(WgpuState),
36}
37
38#[expect(clippy::large_enum_variant)]
39enum AppState {
40 Uninitialized,
41 Loading,
42 Running(WgpuState),
43}
44
45struct App {
46 proxy: EventLoopProxy<TriangleAction>,
47 window: Option<Arc<Window>>,
48 state: AppState,
49}
50
51impl App {
52 fn new(event_loop: &EventLoop<TriangleAction>) -> Self {
53 Self {
54 proxy: event_loop.create_proxy(),
55 window: None,
56 state: AppState::Uninitialized,
57 }
58 }
59}
60
61impl ApplicationHandler<TriangleAction> for App {
62 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
63 if !matches!(self.state, AppState::Uninitialized) {
64 return;
65 }
66 self.state = AppState::Loading;
67
68 #[cfg_attr(
69 not(target_arch = "wasm32"),
70 expect(unused_mut, reason = "wasm32 re-assigns to specify canvas")
71 )]
72 let mut attributes = Window::default_attributes();
73
74 #[cfg(target_arch = "wasm32")]
75 {
76 use wasm_bindgen::JsCast;
77 use winit::platform::web::WindowAttributesExtWebSys;
78 let canvas = web_sys::window()
79 .unwrap()
80 .document()
81 .unwrap()
82 .get_element_by_id("canvas")
83 .unwrap()
84 .dyn_into::<web_sys::HtmlCanvasElement>()
85 .unwrap();
86 attributes = attributes.with_canvas(Some(canvas));
87 }
88
89 let window = Arc::new(
90 event_loop
91 .create_window(attributes)
92 .expect("Failed to create window"),
93 );
94 self.window = Some(window.clone());
95
96 let display_handle = event_loop.owned_display_handle();
97 let proxy = self.proxy.clone();
98
99 spawn(async move {
100 let mut size = window.inner_size();
101 size.width = size.width.max(1);
102 size.height = size.height.max(1);
103
104 let instance =
105 wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle_from_env(
106 Box::new(display_handle),
107 ));
108
109 let surface = instance.create_surface(window.clone()).unwrap();
110 let adapter = instance
111 .request_adapter(&wgpu::RequestAdapterOptions {
112 power_preference: wgpu::PowerPreference::default(),
113 compatible_surface: Some(&surface),
115 ..Default::default()
116 })
117 .await
118 .expect("Failed to find an appropriate adapter");
119
120 let (device, queue) = adapter
122 .request_device(&wgpu::DeviceDescriptor {
123 label: None,
124 required_features: wgpu::Features::empty(),
125 required_limits: wgpu::Limits::downlevel_webgl2_defaults()
128 .using_resolution(adapter.limits()),
129 experimental_features: wgpu::ExperimentalFeatures::disabled(),
130 memory_hints: wgpu::MemoryHints::MemoryUsage,
131 trace: wgpu::Trace::Off,
132 })
133 .await
134 .expect("Failed to create device");
135
136 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
138 label: None,
139 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
140 });
141
142 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
143 label: None,
144 bind_group_layouts: &[],
145 immediate_size: 0,
146 });
147
148 let swapchain_capabilities = surface.get_capabilities(&adapter);
149 let swapchain_format = swapchain_capabilities.formats[0];
150
151 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
152 label: None,
153 layout: Some(&pipeline_layout),
154 vertex: wgpu::VertexState {
155 module: &shader,
156 entry_point: Some("vs_main"),
157 buffers: &[],
158 compilation_options: Default::default(),
159 },
160 fragment: Some(wgpu::FragmentState {
161 module: &shader,
162 entry_point: Some("fs_main"),
163 compilation_options: Default::default(),
164 targets: &[Some(swapchain_format.into())],
165 }),
166 primitive: wgpu::PrimitiveState::default(),
167 depth_stencil: None,
168 multisample: wgpu::MultisampleState::default(),
169 multiview_mask: None,
170 cache: None,
171 });
172
173 let config = surface
174 .get_default_config(&adapter, size.width, size.height)
175 .unwrap();
176 surface.configure(&device, &config);
177
178 let _ = proxy.send_event(TriangleAction::Initialized(WgpuState {
179 instance,
180 window,
181 device,
182 queue,
183 surface,
184 config,
185 render_pipeline,
186 }));
187 });
188 }
189
190 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: TriangleAction) {
191 match event {
192 TriangleAction::Initialized(wgpu_state) => {
193 self.state = AppState::Running(wgpu_state);
194 if let Some(window) = &self.window {
195 window.request_redraw();
196 }
197 }
198 }
199 }
200
201 fn window_event(
202 &mut self,
203 event_loop: &ActiveEventLoop,
204 _window_id: winit::window::WindowId,
205 event: WindowEvent,
206 ) {
207 let AppState::Running(wgpu_state) = &mut self.state else {
208 return;
209 };
210
211 match event {
212 WindowEvent::Resized(new_size) => {
213 wgpu_state.config.width = new_size.width.max(1);
215 wgpu_state.config.height = new_size.height.max(1);
216 wgpu_state
217 .surface
218 .configure(&wgpu_state.device, &wgpu_state.config);
219 if let Some(window) = &self.window {
221 window.request_redraw();
222 }
223 }
224 WindowEvent::RedrawRequested => {
225 let frame = match wgpu_state.surface.get_current_texture() {
226 CurrentSurfaceTexture::Success(frame) => frame,
227 CurrentSurfaceTexture::Timeout | CurrentSurfaceTexture::Occluded => {
228 if let Some(window) = &self.window {
230 window.request_redraw();
231 }
232 return;
233 }
234 CurrentSurfaceTexture::Suboptimal(texture) => {
235 drop(texture);
236
237 wgpu_state
238 .surface
239 .configure(&wgpu_state.device, &wgpu_state.config);
240 if let Some(window) = &self.window {
241 window.request_redraw();
242 }
243 return;
244 }
245 CurrentSurfaceTexture::Outdated => {
246 wgpu_state
247 .surface
248 .configure(&wgpu_state.device, &wgpu_state.config);
249 if let Some(window) = &self.window {
250 window.request_redraw();
251 }
252 return;
253 }
254 CurrentSurfaceTexture::Validation => {
255 unreachable!("No error scope registered, so validation errors will panic")
256 }
257 CurrentSurfaceTexture::Lost => {
258 wgpu_state.surface = wgpu_state
259 .instance
260 .create_surface(wgpu_state.window.clone())
261 .unwrap();
262 wgpu_state
263 .surface
264 .configure(&wgpu_state.device, &wgpu_state.config);
265 if let Some(window) = &self.window {
266 window.request_redraw();
267 }
268 return;
269 }
270 };
271
272 let view = frame
273 .texture
274 .create_view(&wgpu::TextureViewDescriptor::default());
275 let mut encoder = wgpu_state
276 .device
277 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
278 {
279 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
280 label: None,
281 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
282 view: &view,
283 depth_slice: None,
284 resolve_target: None,
285 ops: wgpu::Operations {
286 load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
287 store: wgpu::StoreOp::Store,
288 },
289 })],
290 depth_stencil_attachment: None,
291 timestamp_writes: None,
292 occlusion_query_set: None,
293 multiview_mask: None,
294 });
295 rpass.set_pipeline(&wgpu_state.render_pipeline);
296 rpass.draw(0..3, 0..1);
297 }
298
299 wgpu_state.queue.submit(Some(encoder.finish()));
300 if let Some(window) = &self.window {
301 window.pre_present_notify();
302 }
303 wgpu_state.queue.present(frame);
304 }
305 WindowEvent::Occluded(is_occluded) => {
306 if !is_occluded {
307 if let Some(window) = &self.window {
308 window.request_redraw();
309 }
310 }
311 }
312 WindowEvent::CloseRequested => event_loop.exit(),
313 _ => {}
314 }
315 }
316}
317
318pub fn main() {
319 cfg_if::cfg_if! {
320 if #[cfg(target_arch = "wasm32")] {
321 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
322 console_log::init().expect("could not initialize logger");
323 } else {
324 env_logger::init();
325 }
326 }
327
328 let event_loop = EventLoop::with_user_event().build().unwrap();
329
330 #[cfg_attr(target_arch = "wasm32", expect(unused_mut))]
331 let mut app = App::new(&event_loop);
332
333 cfg_if::cfg_if! {
334 if #[cfg(target_arch = "wasm32")] {
335 use winit::platform::web::EventLoopExtWebSys;
336 event_loop.spawn_app(app);
337 } else {
338 event_loop.run_app(&mut app).unwrap();
339 }
340 }
341}