1use std::sync::Arc;
2
3use cgmath::num_traits::ToPrimitive;
4use wgpu::{ExperimentalFeatures, util::DeviceExt};
5use winit::{dpi::PhysicalPosition, window::Window};
6
7use crate::{
8 camera::{self, CameraResources, CameraUniform, Projection}, data_structures::texture, pick::PickId, pipelines::{
9 basic::mk_basic_pipeline,
10 gui::{mk_gui_pipeline, mk_screen_size_bind_group, mk_screen_size_bind_group_layout},
11 light::{LightResources, LightUniform, mk_light_pipeline},
12 pick::mk_pick_pipeline,
13 pick_gui::mk_gui_pick_pipeline,
14 terrain::mk_terrain_pipeline,
15 transparent::mk_transparent_pipeline,
16 }, render::Render
17};
18
19pub trait GPUResource<'a, 'pass> {
20 fn write_to_buffer(&mut self, queue: &wgpu::Queue, device: &wgpu::Device);
21 fn get_render(&'a self) -> Render<'a, 'pass>;
22}
23#[cfg(feature = "integration-tests")]
24impl<'a, 'pass> GPUResource<'a, 'pass> for Box<dyn GPUResource<'a, 'pass> + Send> {
25 fn write_to_buffer(&mut self, queue: &wgpu::Queue, device: &wgpu::Device) {
26 (**self).write_to_buffer(queue, device);
27 }
28
29 fn get_render(&'a self) -> Render<'a, 'pass> {
30 (**self).get_render()
31 }
32}
33#[cfg(feature = "integration-tests")]
34impl<'a, 'pass> From<&'a Box<dyn GPUResource<'a, 'pass>>> for Render<'a, 'pass> {
35 fn from(val: &'a Box<dyn GPUResource<'a, 'pass>>) -> Self {
36 val.get_render()
37 }
38}
39#[cfg(feature = "integration-tests")]
40impl<'a, 'pass> GPUResource<'a, 'pass> for Box<dyn GPUResource<'a, 'pass>> {
41 fn write_to_buffer(&mut self, queue: &wgpu::Queue, device: &wgpu::Device) {
42 (**self).write_to_buffer(queue, device);
43 }
44
45 fn get_render(&'a self) -> Render<'a, 'pass> {
46 (**self).get_render()
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum AntiAliasing {
53 None,
54 MSAA4x,
55}
56
57impl AntiAliasing {
58 pub fn sample_count(self) -> u32 {
59 match self {
60 AntiAliasing::None => 1,
61 AntiAliasing::MSAA4x => 4,
62 }
63 }
64}
65
66#[derive(Debug)]
67pub enum MouseButtonState {
68 Right,
69 Left,
70 None,
71}
72
73#[derive(Debug)]
74pub struct MouseState {
75 pub coords: PhysicalPosition<f64>,
76 pub pressed: MouseButtonState,
77 pub selection: Option<PickId>,
78}
79impl MouseState {
80 pub(crate) fn toggle(&mut self, pick_id: PickId) {
81 self.selection = self
82 .selection
83 .is_none_or(|id| id != pick_id)
84 .then_some(pick_id);
85 }
86}
87
88#[derive(Debug)]
89pub struct Pipelines {
90 pub light: wgpu::RenderPipeline,
91 pub basic: wgpu::RenderPipeline,
92 pub basic_cw: wgpu::RenderPipeline,
93 pub pick: wgpu::RenderPipeline,
94 pub gui: wgpu::RenderPipeline,
95 pub transparent: wgpu::RenderPipeline,
96 pub terrain: wgpu::RenderPipeline,
97 pub flat_pick: wgpu::RenderPipeline,
98}
99
100#[derive(Debug)]
101pub struct ScreenSizeResources {
102 pub buffer: wgpu::Buffer,
103 pub bind_group: wgpu::BindGroup,
104 pub bind_group_layout: wgpu::BindGroupLayout,
105}
106
107#[derive(Debug)]
108pub struct Context {
109 pub window: Arc<Window>,
110 pub(crate) depth_texture: texture::Texture,
111 pub(crate) msaa_view: Option<wgpu::TextureView>,
112 pub anti_aliasing: AntiAliasing,
113 pub tick_duration_millis: u64,
114 pub clear_colour: wgpu::Color,
115 pub surface: wgpu::Surface<'static>,
116 pub device: wgpu::Device,
117 pub queue: wgpu::Queue,
118 pub mouse: MouseState,
119 pub config: wgpu::SurfaceConfiguration,
120 pub camera: CameraResources,
121 pub projection: Projection,
122 pub light: LightResources,
123 pub pipelines: Pipelines,
124 pub screen_size: ScreenSizeResources,
125}
126impl Context {
127 pub(crate) async fn new(window: Arc<Window>) -> Result<Self, anyhow::Error> {
128 let size = window.inner_size();
129
130 log::warn!("WGPU setup");
133 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
134 #[cfg(not(target_arch = "wasm32"))]
135 backends: wgpu::Backends::PRIMARY,
136 #[cfg(target_arch = "wasm32")]
137 backends: wgpu::Backends::GL,
138 ..Default::default()
139 });
140
141 let surface = instance.create_surface(window.clone())?;
142
143 let adapter = instance
144 .request_adapter(&wgpu::RequestAdapterOptions {
145 power_preference: wgpu::PowerPreference::default(),
146 compatible_surface: Some(&surface),
147 force_fallback_adapter: false,
148 })
149 .await?;
150 log::warn!("device and queue");
151 let (device, queue) = adapter
152 .request_device(&wgpu::DeviceDescriptor {
153 label: None,
154 required_features: wgpu::Features::empty(),
155 required_limits: if cfg!(target_arch = "wasm32") {
158 wgpu::Limits::downlevel_webgl2_defaults()
159 } else {
160 wgpu::Limits::default()
161 },
162 memory_hints: Default::default(),
163 trace: wgpu::Trace::Off,
164 experimental_features: ExperimentalFeatures::disabled(),
165 })
166 .await?;
167
168 log::warn!("Surface");
169 let surface_caps = surface.get_capabilities(&adapter);
170 let surface_format = surface_caps
174 .formats
175 .iter()
176 .copied()
177 .find(|f| f.is_srgb() && format!("{:?}", f).starts_with('R'))
179 .or(surface_caps.formats.iter().copied().find(|f| f.is_srgb()))
180 .unwrap_or(surface_caps.formats[0]);
181 let config = wgpu::SurfaceConfiguration {
182 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
183 format: surface_format,
184 width: size.width,
185 height: size.height,
186 present_mode: surface_caps.present_modes[0],
187 alpha_mode: surface_caps.alpha_modes[0],
188 view_formats: vec![],
189 desired_maximum_frame_latency: 2,
190 };
191
192 let camera = camera::Camera::new((0.0, 30.0, 20.0), cgmath::Deg(-90.0), cgmath::Deg(-60.0));
194 let projection =
195 camera::Projection::new(config.width, config.height, cgmath::Deg(45.0), 0.1, 500.0)?;
196 let camera_controller = camera::CameraController::new(10.0, 0.4);
197
198 let mut camera_uniform = CameraUniform::new();
199
200 camera_uniform.update_view_proj(&camera, &projection);
201
202 let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
203 label: Some("Camera Buffer"),
204 contents: bytemuck::cast_slice(&[camera_uniform]),
205 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
206 });
207
208 let camera_bind_group_layout =
209 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
210 entries: &[wgpu::BindGroupLayoutEntry {
211 binding: 0,
212 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
213 ty: wgpu::BindingType::Buffer {
214 ty: wgpu::BufferBindingType::Uniform,
215 has_dynamic_offset: false,
216 min_binding_size: None,
217 },
218 count: None,
219 }],
220 label: Some("camera_bind_group_layout"),
221 });
222
223 let bind_group_layout = camera_bind_group_layout.clone();
224
225 let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
226 layout: &camera_bind_group_layout,
227 entries: &[wgpu::BindGroupEntry {
228 binding: 0,
229 resource: camera_buffer.as_entire_binding(),
230 }],
231 label: Some("camera_bind_group"),
232 });
233
234 let camera = CameraResources {
235 camera,
236 controller: camera_controller,
237 uniform: camera_uniform,
238 buffer: camera_buffer,
239 bind_group: camera_bind_group,
240 bind_group_layout,
241 };
242
243 let anti_aliasing = AntiAliasing::None;
244 let sample_count = anti_aliasing.sample_count();
245
246 let depth_texture = texture::Texture::create_depth_texture(
247 &device,
248 [config.width, config.height],
249 "depth_texture",
250 sample_count,
251 );
252
253 let msaa_view = if sample_count > 1 {
254 Some(texture::Texture::create_msaa_texture(&device, &config, sample_count))
255 } else {
256 None
257 };
258
259 let light_uniform = LightUniform {
260 position: [8.0, 80.0, 50.0],
261 _padding: 0,
262 color: [1.0, 1.0, 1.0],
264 _padding2: 0,
265 };
266
267 let light = LightResources::new(light_uniform, None, &device);
268
269 let clear_colour = wgpu::Color {
270 r: 0.1,
271 g: 0.2,
272 b: 0.2,
273 a: 1.0,
274 };
275
276 let screen_size_data = [config.width as f32, config.height as f32];
278 let screen_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
279 label: Some("Screen Size Uniform Buffer"),
280 contents: bytemuck::cast_slice(&screen_size_data),
281 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
282 });
283 let screen_size_bind_group_layout = mk_screen_size_bind_group_layout(&device);
284 let screen_size_bind_group = mk_screen_size_bind_group(&device, &screen_size_buffer, &screen_size_bind_group_layout);
285 let screen_size = ScreenSizeResources {
286 buffer: screen_size_buffer,
287 bind_group: screen_size_bind_group,
288 bind_group_layout: screen_size_bind_group_layout,
289 };
290
291 let light_pipeline = mk_light_pipeline(
293 &device,
294 &config,
295 &light.bind_group_layout,
296 &camera.bind_group_layout,
297 sample_count,
298 );
299 let basic_pipeline = mk_basic_pipeline(
300 &device,
301 &config,
302 wgpu::FrontFace::Ccw,
303 &light.bind_group_layout,
304 &camera.bind_group_layout,
305 sample_count,
306 );
307 let basic_cw_pipeline = mk_basic_pipeline(
308 &device,
309 &config,
310 wgpu::FrontFace::Cw,
311 &light.bind_group_layout,
312 &camera.bind_group_layout,
313 sample_count,
314 );
315 let pick_pipeline = mk_pick_pipeline(&device, &camera.bind_group_layout);
316 let gui_pipeline = mk_gui_pipeline(&device, &config, &screen_size.bind_group_layout, sample_count);
317 let gui_pick_pipeline = mk_gui_pick_pipeline(&device, &screen_size.bind_group_layout);
318 let transparent_pipeline = mk_transparent_pipeline(
319 &device,
320 &config,
321 &light.bind_group_layout,
322 &camera.bind_group_layout,
323 sample_count,
324 );
325 let terrain_pipeline = mk_terrain_pipeline(
326 &device,
327 &config,
328 &camera.bind_group_layout,
329 &light.bind_group_layout,
330 sample_count,
331 );
332 let pipelines = Pipelines {
333 basic: basic_pipeline,
334 basic_cw: basic_cw_pipeline,
335 gui: gui_pipeline,
336 flat_pick: gui_pick_pipeline,
337 light: light_pipeline,
338 pick: pick_pipeline,
339 transparent: transparent_pipeline,
340 terrain: terrain_pipeline,
341 };
342 let mouse = MouseState {
343 coords: (0.0, 0.0).into(),
344 pressed: MouseButtonState::None,
345 selection: None,
346 };
347 let tick_duration_millis = 500;
348
349 Ok(Self {
350 anti_aliasing,
351 camera,
352 clear_colour,
353 config,
354 depth_texture,
355 device,
356 light,
357 mouse,
358 msaa_view,
359 pipelines,
360 projection,
361 queue,
362 screen_size,
363 surface,
364 tick_duration_millis,
365 window,
366 })
367 }
368
369 pub fn configure_anti_aliasing(&mut self, aa: AntiAliasing) {
371 self.anti_aliasing = aa;
372 let sample_count = aa.sample_count();
373
374 self.depth_texture = texture::Texture::create_depth_texture(
375 &self.device,
376 [self.config.width, self.config.height],
377 "depth_texture",
378 sample_count,
379 );
380
381 self.msaa_view = if sample_count > 1 {
382 Some(texture::Texture::create_msaa_texture(
383 &self.device,
384 &self.config,
385 sample_count,
386 ))
387 } else {
388 None
389 };
390
391 self.pipelines = Pipelines {
392 light: mk_light_pipeline(
393 &self.device,
394 &self.config,
395 &self.light.bind_group_layout,
396 &self.camera.bind_group_layout,
397 sample_count,
398 ),
399 basic: mk_basic_pipeline(
400 &self.device,
401 &self.config,
402 wgpu::FrontFace::Ccw,
403 &self.light.bind_group_layout,
404 &self.camera.bind_group_layout,
405 sample_count,
406 ),
407 basic_cw: mk_basic_pipeline(
408 &self.device,
409 &self.config,
410 wgpu::FrontFace::Cw,
411 &self.light.bind_group_layout,
412 &self.camera.bind_group_layout,
413 sample_count,
414 ),
415 pick: mk_pick_pipeline(&self.device, &self.camera.bind_group_layout),
416 gui: mk_gui_pipeline(
417 &self.device,
418 &self.config,
419 &self.screen_size.bind_group_layout,
420 sample_count,
421 ),
422 transparent: mk_transparent_pipeline(
423 &self.device,
424 &self.config,
425 &self.light.bind_group_layout,
426 &self.camera.bind_group_layout,
427 sample_count,
428 ),
429 terrain: mk_terrain_pipeline(
430 &self.device,
431 &self.config,
432 &self.camera.bind_group_layout,
433 &self.light.bind_group_layout,
434 sample_count,
435 ),
436 flat_pick: mk_gui_pick_pipeline(&self.device, &self.screen_size.bind_group_layout),
437 };
438 }
439
440 pub fn ray_to_floor(&self) -> Option<cgmath::Point2<f32>> {
441 self.camera.camera.cast_ray_from_mouse(
442 self.mouse.coords,
443 self.config.width.to_f32()?,
444 self.config.height.to_f32()?,
445 &self.projection,
446 ).intersect_with_floor()
447 }
448}
449
450#[derive(Clone)]
451pub struct InitContext {
452 pub queue: wgpu::Queue,
453 pub device: wgpu::Device,
454}
455impl From<&Context> for InitContext {
456 fn from(ctx: &Context) -> Self {
457 Self {
458 queue: ctx.queue.clone(),
460 device: ctx.device.clone(),
461 }
462 }
463}