wgpu_examples/uniform_values/
mod.rs1use std::sync::Arc;
20use 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#[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 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 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 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 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 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 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 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 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 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}