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(_) | CurrentSurfaceTexture::Outdated => {
235 wgpu_state
236 .surface
237 .configure(&wgpu_state.device, &wgpu_state.config);
238 if let Some(window) = &self.window {
239 window.request_redraw();
240 }
241 return;
242 }
243 CurrentSurfaceTexture::Validation => {
244 unreachable!("No error scope registered, so validation errors will panic")
245 }
246 CurrentSurfaceTexture::Lost => {
247 wgpu_state.surface = wgpu_state
248 .instance
249 .create_surface(wgpu_state.window.clone())
250 .unwrap();
251 wgpu_state
252 .surface
253 .configure(&wgpu_state.device, &wgpu_state.config);
254 if let Some(window) = &self.window {
255 window.request_redraw();
256 }
257 return;
258 }
259 };
260
261 let view = frame
262 .texture
263 .create_view(&wgpu::TextureViewDescriptor::default());
264 let mut encoder = wgpu_state
265 .device
266 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
267 {
268 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
269 label: None,
270 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
271 view: &view,
272 depth_slice: None,
273 resolve_target: None,
274 ops: wgpu::Operations {
275 load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
276 store: wgpu::StoreOp::Store,
277 },
278 })],
279 depth_stencil_attachment: None,
280 timestamp_writes: None,
281 occlusion_query_set: None,
282 multiview_mask: None,
283 });
284 rpass.set_pipeline(&wgpu_state.render_pipeline);
285 rpass.draw(0..3, 0..1);
286 }
287
288 wgpu_state.queue.submit(Some(encoder.finish()));
289 if let Some(window) = &self.window {
290 window.pre_present_notify();
291 }
292 frame.present();
293 }
294 WindowEvent::Occluded(is_occluded) => {
295 if !is_occluded {
296 if let Some(window) = &self.window {
297 window.request_redraw();
298 }
299 }
300 }
301 WindowEvent::CloseRequested => event_loop.exit(),
302 _ => {}
303 }
304 }
305}
306
307pub fn main() {
308 cfg_if::cfg_if! {
309 if #[cfg(target_arch = "wasm32")] {
310 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
311 console_log::init().expect("could not initialize logger");
312 } else {
313 env_logger::init();
314 }
315 }
316
317 let event_loop = EventLoop::with_user_event().build().unwrap();
318
319 #[cfg_attr(target_arch = "wasm32", expect(unused_mut))]
320 let mut app = App::new(&event_loop);
321
322 cfg_if::cfg_if! {
323 if #[cfg(target_arch = "wasm32")] {
324 use winit::platform::web::EventLoopExtWebSys;
325 event_loop.spawn_app(app);
326 } else {
327 event_loop.run_app(&mut app).unwrap();
328 }
329 }
330}