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                experimental_features: wgpu::ExperimentalFeatures::disabled(),
118                memory_hints: wgpu::MemoryHints::MemoryUsage,
119                trace: wgpu::Trace::Off,
120            })
121            .await
122            .unwrap();
123
124        let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
125
126        // (2)
127        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
128            label: None,
129            size: size_of::<AppState>() as u64,
130            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
131            mapped_at_creation: false,
132        });
133
134        // (3)
135        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
136            label: None,
137            entries: &[wgpu::BindGroupLayoutEntry {
138                binding: 0,
139                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
140                ty: wgpu::BindingType::Buffer {
141                    ty: wgpu::BufferBindingType::Uniform,
142                    has_dynamic_offset: false,
143                    min_binding_size: None,
144                },
145                count: None,
146            }],
147        });
148        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
149            label: None,
150            layout: &bind_group_layout,
151            entries: &[wgpu::BindGroupEntry {
152                binding: 0,
153                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
154                    buffer: &uniform_buffer,
155                    offset: 0,
156                    size: None,
157                }),
158            }],
159        });
160
161        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
162            label: None,
163            // (4)
164            bind_group_layouts: &[&bind_group_layout],
165            push_constant_ranges: &[],
166        });
167
168        let swapchain_capabilities = surface.get_capabilities(&adapter);
169        let swapchain_format = swapchain_capabilities.formats[0];
170
171        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
172            label: None,
173            layout: Some(&pipeline_layout),
174            vertex: wgpu::VertexState {
175                module: &shader,
176                entry_point: Some("vs_main"),
177                compilation_options: Default::default(),
178                buffers: &[],
179            },
180            fragment: Some(wgpu::FragmentState {
181                module: &shader,
182                entry_point: Some("fs_main"),
183                compilation_options: Default::default(),
184                targets: &[Some(swapchain_format.into())],
185            }),
186            primitive: wgpu::PrimitiveState::default(),
187            depth_stencil: None,
188            multisample: wgpu::MultisampleState::default(),
189            multiview: None,
190            cache: None,
191        });
192        let surface_config = surface
193            .get_default_config(&adapter, size.width, size.height)
194            .unwrap();
195        surface.configure(&device, &surface_config);
196
197        // (5)
198        WgpuContext {
199            window,
200            surface,
201            surface_config,
202            device,
203            queue,
204            pipeline,
205            bind_group,
206            uniform_buffer,
207        }
208    }
209
210    fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
211        self.surface_config.width = new_size.width;
212        self.surface_config.height = new_size.height;
213        self.surface.configure(&self.device, &self.surface_config);
214        self.window.request_redraw();
215    }
216}
217
218async fn run(event_loop: EventLoop<()>, window: Arc<Window>) {
219    let mut wgpu_context = Some(WgpuContext::new(window.clone()).await);
220    // (6)
221    let mut state = Some(AppState::default());
222    let main_window_id = wgpu_context.as_ref().unwrap().window.id();
223    event_loop
224        .run(move |event, target| {
225            match event {
226                Event::LoopExiting => {
227                    wgpu_context = None;
228                    state = None;
229                }
230                Event::WindowEvent { window_id, event } if window_id == main_window_id => {
231                    match event {
232                        WindowEvent::CloseRequested => {
233                            target.exit();
234                        }
235                        WindowEvent::KeyboardInput {
236                            event:
237                                KeyEvent {
238                                    logical_key, text, ..
239                                },
240                            ..
241                        } => {
242                            let state_mut = state.as_mut().unwrap();
243                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
244
245                            if let Key::Named(key) = logical_key {
246                                match key {
247                                    NamedKey::Escape => target.exit(),
248                                    NamedKey::ArrowUp => state_mut.translate_view(1, 1),
249                                    NamedKey::ArrowDown => state_mut.translate_view(-1, 1),
250                                    NamedKey::ArrowLeft => state_mut.translate_view(-1, 0),
251                                    NamedKey::ArrowRight => state_mut.translate_view(1, 0),
252                                    _ => {}
253                                }
254                            }
255
256                            if let Some(text) = text {
257                                if text == "u" {
258                                    state_mut.max_iterations += 3;
259                                } else if text == "d" {
260                                    state_mut.max_iterations -= 3;
261                                }
262                            };
263
264                            wgpu_context_ref.window.request_redraw();
265                        }
266                        WindowEvent::MouseWheel { delta, .. } => {
267                            let change = match delta {
268                                winit::event::MouseScrollDelta::LineDelta(_, vertical) => vertical,
269                                winit::event::MouseScrollDelta::PixelDelta(pos) => {
270                                    pos.y as f32 / 20.0
271                                }
272                            };
273                            let state_mut = state.as_mut().unwrap();
274                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
275                            // (7b)
276                            state_mut.zoom(change);
277                            wgpu_context_ref.window.request_redraw();
278                        }
279                        WindowEvent::Resized(new_size) => {
280                            let wgpu_context_mut = wgpu_context.as_mut().unwrap();
281                            wgpu_context_mut.resize(new_size);
282                            wgpu_context_mut.window.request_redraw();
283                        }
284                        WindowEvent::RedrawRequested => {
285                            let wgpu_context_ref = wgpu_context.as_ref().unwrap();
286                            let state_ref = state.as_ref().unwrap();
287                            let frame = wgpu_context_ref.surface.get_current_texture().unwrap();
288                            let view = frame
289                                .texture
290                                .create_view(&wgpu::TextureViewDescriptor::default());
291
292                            // (8)
293                            wgpu_context_ref.queue.write_buffer(
294                                &wgpu_context_ref.uniform_buffer,
295                                0,
296                                &state_ref.as_wgsl_bytes().expect(
297                                    "Error in encase translating AppState \
298                    struct to WGSL bytes.",
299                                ),
300                            );
301                            let mut encoder = wgpu_context_ref.device.create_command_encoder(
302                                &wgpu::CommandEncoderDescriptor { label: None },
303                            );
304                            {
305                                let mut render_pass =
306                                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
307                                        label: None,
308                                        color_attachments: &[Some(
309                                            wgpu::RenderPassColorAttachment {
310                                                view: &view,
311                                                depth_slice: None,
312                                                resolve_target: None,
313                                                ops: wgpu::Operations {
314                                                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
315                                                    store: wgpu::StoreOp::Store,
316                                                },
317                                            },
318                                        )],
319                                        depth_stencil_attachment: None,
320                                        occlusion_query_set: None,
321                                        timestamp_writes: None,
322                                    });
323                                render_pass.set_pipeline(&wgpu_context_ref.pipeline);
324                                // (9)
325                                render_pass.set_bind_group(
326                                    0,
327                                    Some(&wgpu_context_ref.bind_group),
328                                    &[],
329                                );
330                                render_pass.draw(0..3, 0..1);
331                            }
332                            wgpu_context_ref.queue.submit(Some(encoder.finish()));
333                            window.pre_present_notify();
334                            frame.present();
335                        }
336                        _ => {}
337                    }
338                }
339                _ => {}
340            }
341        })
342        .unwrap();
343}
344
345pub fn main() {
346    let event_loop = EventLoop::new().unwrap();
347    #[cfg_attr(
348        not(target_arch = "wasm32"),
349        expect(unused_mut, reason = "`wasm32` re-assigns to specify canvas")
350    )]
351    let mut builder = winit::window::WindowBuilder::new()
352        .with_title("Remember: Use U/D to change sample count!")
353        .with_inner_size(winit::dpi::LogicalSize::new(900, 900));
354
355    #[cfg(target_arch = "wasm32")]
356    {
357        use wasm_bindgen::JsCast;
358        use winit::platform::web::WindowBuilderExtWebSys;
359        let canvas = web_sys::window()
360            .unwrap()
361            .document()
362            .unwrap()
363            .get_element_by_id("canvas")
364            .unwrap()
365            .dyn_into::<web_sys::HtmlCanvasElement>()
366            .unwrap();
367        builder = builder.with_canvas(Some(canvas));
368    }
369    let window = builder.build(&event_loop).unwrap();
370
371    let window = Arc::new(window);
372    #[cfg(not(target_arch = "wasm32"))]
373    {
374        env_logger::builder().format_timestamp_nanos().init();
375        pollster::block_on(run(event_loop, window));
376    }
377    #[cfg(target_arch = "wasm32")]
378    {
379        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
380        console_log::init().expect("could not initialize logger");
381
382        let document = web_sys::window()
383            .and_then(|win| win.document())
384            .expect("Failed to get document.");
385        let body = document.body().unwrap();
386        let controls_text = document
387            .create_element("p")
388            .expect("Failed to create controls text as element.");
389        controls_text.set_inner_html(
390            "Controls: <br/>
391Up, Down, Left, Right: Move view, <br/>
392Scroll: Zoom, <br/>
393U, D: Increase / decrease sample count.",
394        );
395        body.append_child(&controls_text)
396            .expect("Failed to append controls text to body.");
397
398        wasm_bindgen_futures::spawn_local(run(event_loop, window));
399    }
400}