wgpu_examples/uniform_values/
mod.rs

1//! Points of interest for seeing uniforms in action:
2//!
3//! 1. the struct for the data stored in the uniform buffer is defined.
4//! 2. the uniform buffer itself is created.
5//! 3. the bind group that will bind the uniform buffer and it's layout are created.
6//! 4. the bind group layout is attached to the pipeline layout.
7//! 5. the uniform buffer and the bind group are stored alongside the pipeline.
8//! 6. an instance of `AppState` is created. This variable will be modified
9//!    to change parameters in the shader and modified by app events to preform and save
10//!    those changes.
11//! 7. (7a and 7b) the `state` variable created at (6) is modified by commands such
12//!    as pressing the arrow keys or zooming in or out.
13//! 8. the contents of the `AppState` are loaded into the uniform buffer in preparation.
14//! 9. the bind group with the uniform buffer is attached to the render pass.
15//!
16//! The usage of the uniform buffer within the shader itself is pretty self-explanatory given
17//! some understanding of WGSL.
18
19use std::sync::Arc;
20// We won't bring StorageBuffer into scope as that might be too easy to confuse
21// with actual GPU-allocated WGPU storage buffers.
22use encase::ShaderType;
23use winit::{
24    event::{Event, KeyEvent, WindowEvent},
25    event_loop::EventLoop,
26    keyboard::{Key, NamedKey},
27    window::Window,
28};
29
30const ZOOM_INCREMENT_FACTOR: f32 = 1.1;
31const CAMERA_POS_INCREMENT_FACTOR: f32 = 0.1;
32
33// (1)
34#[derive(Debug, ShaderType)]
35struct AppState {
36    pub cursor_pos: glam::Vec2,
37    pub zoom: f32,
38    pub max_iterations: u32,
39}
40
41impl AppState {
42    // Translating Rust structures to WGSL is always tricky and can prove
43    // incredibly difficult to remember all the rules by which WGSL
44    // lays out and formats structs in memory. It is also often extremely
45    // frustrating to debug when things don't go right.
46    //
47    // You may sometimes see structs translated to bytes through
48    // using `#[repr(C)]` on the struct so that the struct has a defined,
49    // guaranteed internal layout and then implementing bytemuck's POD
50    // trait so that one can preform a bitwise cast. There are issues with
51    // this approach though as C's struct layouts aren't always compatible
52    // with WGSL, such as when special WGSL types like vec's and mat's
53    // get involved that have special alignment rules and especially
54    // when the target buffer is going to be used in the uniform memory
55    // space.
56    //
57    // Here though, we use the encase crate which makes translating potentially
58    // complex Rust structs easy through combined use of the [`ShaderType`] trait
59    // / derive macro and the buffer structs which hold data formatted for WGSL
60    // in either the storage or uniform spaces.
61    fn as_wgsl_bytes(&self) -> encase::internal::Result<Vec<u8>> {
62        let mut buffer = encase::UniformBuffer::new(Vec::new());
63        buffer.write(self)?;
64        Ok(buffer.into_inner())
65    }
66
67    fn translate_view(&mut self, increments: i32, axis: usize) {
68        self.cursor_pos[axis] += CAMERA_POS_INCREMENT_FACTOR * increments as f32 / self.zoom;
69    }
70
71    fn zoom(&mut self, amount: f32) {
72        self.zoom += ZOOM_INCREMENT_FACTOR * amount * self.zoom.powf(1.02);
73        self.zoom = self.zoom.max(1.1);
74    }
75}
76
77impl Default for AppState {
78    fn default() -> Self {
79        AppState {
80            cursor_pos: glam::Vec2::ZERO,
81            zoom: 1.0,
82            max_iterations: 50,
83        }
84    }
85}
86
87struct WgpuContext {
88    pub window: Arc<Window>,
89    pub surface: wgpu::Surface<'static>,
90    pub surface_config: wgpu::SurfaceConfiguration,
91    pub device: wgpu::Device,
92    pub queue: wgpu::Queue,
93    pub pipeline: wgpu::RenderPipeline,
94    pub bind_group: wgpu::BindGroup,
95    pub uniform_buffer: wgpu::Buffer,
96}
97
98impl WgpuContext {
99    async fn new(window: Arc<Window>) -> WgpuContext {
100        let size = window.inner_size();
101
102        let instance = wgpu::Instance::default();
103        let surface = instance.create_surface(window.clone()).unwrap();
104        let adapter = instance
105            .request_adapter(&wgpu::RequestAdapterOptions {
106                power_preference: wgpu::PowerPreference::HighPerformance,
107                compatible_surface: Some(&surface),
108                force_fallback_adapter: false,
109            })
110            .await
111            .unwrap();
112        let (device, queue) = adapter
113            .request_device(&wgpu::DeviceDescriptor {
114                label: None,
115                required_features: wgpu::Features::empty(),
116                required_limits: wgpu::Limits::downlevel_defaults(),
117                memory_hints: wgpu::MemoryHints::MemoryUsage,
118                trace: wgpu::Trace::Off,
119            })
120            .await
121            .unwrap();
122
123        let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
124
125        // (2)
126        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
127            label: None,
128            size: size_of::<AppState>() as u64,
129            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
130            mapped_at_creation: false,
131        });
132
133        // (3)
134        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
135            label: None,
136            entries: &[wgpu::BindGroupLayoutEntry {
137                binding: 0,
138                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
139                ty: wgpu::BindingType::Buffer {
140                    ty: wgpu::BufferBindingType::Uniform,
141                    has_dynamic_offset: false,
142                    min_binding_size: None,
143                },
144                count: None,
145            }],
146        });
147        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
148            label: None,
149            layout: &bind_group_layout,
150            entries: &[wgpu::BindGroupEntry {
151                binding: 0,
152                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
153                    buffer: &uniform_buffer,
154                    offset: 0,
155                    size: None,
156                }),
157            }],
158        });
159
160        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
161            label: None,
162            // (4)
163            bind_group_layouts: &[&bind_group_layout],
164            push_constant_ranges: &[],
165        });
166
167        let swapchain_capabilities = surface.get_capabilities(&adapter);
168        let swapchain_format = swapchain_capabilities.formats[0];
169
170        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
171            label: None,
172            layout: Some(&pipeline_layout),
173            vertex: wgpu::VertexState {
174                module: &shader,
175                entry_point: Some("vs_main"),
176                compilation_options: Default::default(),
177                buffers: &[],
178            },
179            fragment: Some(wgpu::FragmentState {
180                module: &shader,
181                entry_point: Some("fs_main"),
182                compilation_options: Default::default(),
183                targets: &[Some(swapchain_format.into())],
184            }),
185            primitive: wgpu::PrimitiveState::default(),
186            depth_stencil: None,
187            multisample: wgpu::MultisampleState::default(),
188            multiview: None,
189            cache: None,
190        });
191        let surface_config = surface
192            .get_default_config(&adapter, size.width, size.height)
193            .unwrap();
194        surface.configure(&device, &surface_config);
195
196        // (5)
197        WgpuContext {
198            window,
199            surface,
200            surface_config,
201            device,
202            queue,
203            pipeline,
204            bind_group,
205            uniform_buffer,
206        }
207    }
208
209    fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
210        self.surface_config.width = new_size.width;
211        self.surface_config.height = new_size.height;
212        self.surface.configure(&self.device, &self.surface_config);
213        self.window.request_redraw();
214    }
215}
216
217async fn run(event_loop: EventLoop<()>, window: Arc<Window>) {
218    let mut wgpu_context = Some(WgpuContext::new(window.clone()).await);
219    // (6)
220    let mut state = Some(AppState::default());
221    let main_window_id = wgpu_context.as_ref().unwrap().window.id();
222    event_loop
223        .run(move |event, target| {
224            match event {
225                Event::LoopExiting => {
226                    wgpu_context = None;
227                    state = None;
228                }
229                Event::WindowEvent { window_id, event } if window_id == main_window_id => {
230                    match event {
231                        WindowEvent::CloseRequested => {
232                            target.exit();
233                        }
234                        WindowEvent::KeyboardInput {
235                            event:
236                                KeyEvent {
237                                    logical_key, text, ..
238                                },
239                            ..
240                        } => {
241                            let state_mut = state.as_mut().unwrap();
242                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
243
244                            if let Key::Named(key) = logical_key {
245                                match key {
246                                    NamedKey::Escape => target.exit(),
247                                    NamedKey::ArrowUp => state_mut.translate_view(1, 1),
248                                    NamedKey::ArrowDown => state_mut.translate_view(-1, 1),
249                                    NamedKey::ArrowLeft => state_mut.translate_view(-1, 0),
250                                    NamedKey::ArrowRight => state_mut.translate_view(1, 0),
251                                    _ => {}
252                                }
253                            }
254
255                            if let Some(text) = text {
256                                if text == "u" {
257                                    state_mut.max_iterations += 3;
258                                } else if text == "d" {
259                                    state_mut.max_iterations -= 3;
260                                }
261                            };
262
263                            wgpu_context_ref.window.request_redraw();
264                        }
265                        WindowEvent::MouseWheel { delta, .. } => {
266                            let change = match delta {
267                                winit::event::MouseScrollDelta::LineDelta(_, vertical) => vertical,
268                                winit::event::MouseScrollDelta::PixelDelta(pos) => {
269                                    pos.y as f32 / 20.0
270                                }
271                            };
272                            let state_mut = state.as_mut().unwrap();
273                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
274                            // (7b)
275                            state_mut.zoom(change);
276                            wgpu_context_ref.window.request_redraw();
277                        }
278                        WindowEvent::Resized(new_size) => {
279                            let wgpu_context_mut = wgpu_context.as_mut().unwrap();
280                            wgpu_context_mut.resize(new_size);
281                            wgpu_context_mut.window.request_redraw();
282                        }
283                        WindowEvent::RedrawRequested => {
284                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
285                            let state_ref = state.as_ref().unwrap();
286                            let frame = wgpu_context_ref.surface.get_current_texture().unwrap();
287                            let view = frame
288                                .texture
289                                .create_view(&wgpu::TextureViewDescriptor::default());
290
291                            // (8)
292                            wgpu_context_ref.queue.write_buffer(
293                                &wgpu_context_ref.uniform_buffer,
294                                0,
295                                &state_ref.as_wgsl_bytes().expect(
296                                    "Error in encase translating AppState \
297                    struct to WGSL bytes.",
298                                ),
299                            );
300                            let mut encoder = wgpu_context_ref.device.create_command_encoder(
301                                &wgpu::CommandEncoderDescriptor { label: None },
302                            );
303                            {
304                                let mut render_pass =
305                                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
306                                        label: None,
307                                        color_attachments: &[Some(
308                                            wgpu::RenderPassColorAttachment {
309                                                view: &view,
310                                                depth_slice: None,
311                                                resolve_target: None,
312                                                ops: wgpu::Operations {
313                                                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
314                                                    store: wgpu::StoreOp::Store,
315                                                },
316                                            },
317                                        )],
318                                        depth_stencil_attachment: None,
319                                        occlusion_query_set: None,
320                                        timestamp_writes: None,
321                                    });
322                                render_pass.set_pipeline(&wgpu_context_ref.pipeline);
323                                // (9)
324                                render_pass.set_bind_group(
325                                    0,
326                                    Some(&wgpu_context_ref.bind_group),
327                                    &[],
328                                );
329                                render_pass.draw(0..3, 0..1);
330                            }
331                            wgpu_context_ref.queue.submit(Some(encoder.finish()));
332                            window.pre_present_notify();
333                            frame.present();
334                        }
335                        _ => {}
336                    }
337                }
338                _ => {}
339            }
340        })
341        .unwrap();
342}
343
344pub fn main() {
345    let event_loop = EventLoop::new().unwrap();
346    #[cfg_attr(
347        not(target_arch = "wasm32"),
348        expect(unused_mut, reason = "`wasm32` re-assigns to specify canvas")
349    )]
350    let mut builder = winit::window::WindowBuilder::new()
351        .with_title("Remember: Use U/D to change sample count!")
352        .with_inner_size(winit::dpi::LogicalSize::new(900, 900));
353
354    #[cfg(target_arch = "wasm32")]
355    {
356        use wasm_bindgen::JsCast;
357        use winit::platform::web::WindowBuilderExtWebSys;
358        let canvas = web_sys::window()
359            .unwrap()
360            .document()
361            .unwrap()
362            .get_element_by_id("canvas")
363            .unwrap()
364            .dyn_into::<web_sys::HtmlCanvasElement>()
365            .unwrap();
366        builder = builder.with_canvas(Some(canvas));
367    }
368    let window = builder.build(&event_loop).unwrap();
369
370    let window = Arc::new(window);
371    #[cfg(not(target_arch = "wasm32"))]
372    {
373        env_logger::builder().format_timestamp_nanos().init();
374        pollster::block_on(run(event_loop, window));
375    }
376    #[cfg(target_arch = "wasm32")]
377    {
378        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
379        console_log::init().expect("could not initialize logger");
380
381        let document = web_sys::window()
382            .and_then(|win| win.document())
383            .expect("Failed to get document.");
384        let body = document.body().unwrap();
385        let controls_text = document
386            .create_element("p")
387            .expect("Failed to create controls text as element.");
388        controls_text.set_inner_html(
389            "Controls: <br/>
390Up, Down, Left, Right: Move view, <br/>
391Scroll: Zoom, <br/>
392U, D: Increase / decrease sample count.",
393        );
394        body.append_child(&controls_text)
395            .expect("Failed to append controls text to body.");
396
397        wasm_bindgen_futures::spawn_local(run(event_loop, window));
398    }
399}