1mod binding;
2mod compiler;
3pub(super) mod drawer;
4mod locals;
6mod pipeline_creation;
7mod rain_occlusion_map;
8mod screenshot;
9mod shaders;
10mod shadow_map;
11
12use locals::Locals;
13use pipeline_creation::{
14 IngameAndShadowPipelines, InterfacePipelines, PipelineCreation, Pipelines, ShadowPipelines,
15};
16use shaders::Shaders;
17use shadow_map::{ShadowMap, ShadowMapRenderer};
18
19use self::{pipeline_creation::RainOcclusionPipelines, rain_occlusion_map::RainOcclusionMap};
20
21use super::{
22 AddressMode, ExperimentalShader, FilterMode, OtherModes, PipelineModes, PresentMode,
23 RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex,
24 buffer::Buffer,
25 consts::Consts,
26 instances::Instances,
27 mesh::Mesh,
28 model::{DynamicModel, Model},
29 pipelines::{
30 GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup, blit, bloom, clouds, debug,
31 figure, postprocess, rain_occlusion, rope, shadow, sprite, terrain, ui,
32 },
33 texture::Texture,
34};
35use common::assets::{self, AssetExt, AssetHandle, ReloadWatcher};
36use common_base::span;
37use core::convert::TryFrom;
38use std::sync::Arc;
39use tracing::{error, info, warn};
40use vek::*;
41
42const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000;
43const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000;
44
45struct ImmutableLayouts {
48 global: GlobalsLayouts,
49
50 debug: debug::DebugLayout,
51 figure: figure::FigureLayout,
52 shadow: shadow::ShadowLayout,
53 rain_occlusion: rain_occlusion::RainOcclusionLayout,
54 sprite: sprite::SpriteLayout,
55 terrain: terrain::TerrainLayout,
56 rope: rope::RopeLayout,
57 clouds: clouds::CloudsLayout,
58 bloom: bloom::BloomLayout,
59 ui: ui::UiLayout,
60 premultiply_alpha: ui::PremultiplyAlphaLayout,
61 blit: blit::BlitLayout,
62}
63
64struct Layouts {
66 immutable: Arc<ImmutableLayouts>,
67
68 postprocess: Arc<postprocess::PostProcessLayout>,
69}
70
71impl core::ops::Deref for Layouts {
72 type Target = ImmutableLayouts;
73
74 fn deref(&self) -> &Self::Target { &self.immutable }
75}
76
77struct Views {
79 _win_depth: wgpu::TextureView,
81
82 tgt_color: wgpu::TextureView,
83 tgt_mat: wgpu::TextureView,
84 tgt_depth: wgpu::TextureView,
85
86 bloom_tgts: Option<[wgpu::TextureView; bloom::NUM_SIZES]>,
87 tgt_color_pp: wgpu::TextureView,
89}
90
91struct Shadow {
93 rain_map: RainOcclusionMap,
94 map: ShadowMap,
95 bind: ShadowTexturesBindGroup,
96}
97
98#[expect(clippy::large_enum_variant)]
102enum State {
103 Nothing,
105 Interface {
106 pipelines: InterfacePipelines,
107 shadow_views: Option<(Texture, Texture)>,
108 rain_occlusion_view: Option<Texture>,
109 creating: PipelineCreation<IngameAndShadowPipelines>,
111 },
112 Complete {
113 pipelines: Pipelines,
114 shadow: Shadow,
115 recreating: Option<(
116 PipelineModes,
117 PipelineCreation<
118 Result<
119 (
120 Pipelines,
121 ShadowPipelines,
122 RainOcclusionPipelines,
123 Arc<postprocess::PostProcessLayout>,
124 ),
125 RenderError,
126 >,
127 >,
128 )>,
129 },
130}
131
132pub struct Renderer {
137 device: wgpu::Device,
138 queue: wgpu::Queue,
139 surface: wgpu::Surface<'static>,
140 surface_config: wgpu::SurfaceConfiguration,
141
142 sampler: wgpu::Sampler,
143 depth_sampler: wgpu::Sampler,
144
145 state: State,
146 recreation_pending: Option<PipelineModes>,
149
150 layouts: Layouts,
151 locals: Locals,
154 views: Views,
155 noise_tex: Texture,
156
157 quad_index_buffer_u16: Buffer<u16>,
158 quad_index_buffer_u32: Buffer<u32>,
159
160 shaders: AssetHandle<Shaders>,
161 shaders_watcher: ReloadWatcher,
162
163 pipeline_modes: PipelineModes,
164 other_modes: OtherModes,
165 resolution: Vec2<u32>,
166
167 take_screenshot: Option<screenshot::ScreenshotFn>,
169
170 profiler: wgpu_profiler::GpuProfiler,
171 profile_times: Vec<wgpu_profiler::GpuTimerQueryResult>,
172 profiler_features_enabled: bool,
173
174 ui_premultiply_uploads: ui::BatchedUploads,
175
176 #[cfg(feature = "egui-ui")]
177 egui_renderer: egui_wgpu::Renderer,
178
179 is_minimized: bool,
182
183 graphics_backend: wgpu::Backend,
185
186 intermediate_format: wgpu::TextureFormat,
188
189 present_modes: Vec<PresentMode>,
191 max_texture_size: u32,
193}
194
195impl Renderer {
196 pub fn new(
199 window: Arc<winit::window::Window>,
200 mode: RenderMode,
201 runtime: &tokio::runtime::Runtime,
202 ) -> Result<Self, RenderError> {
203 let (mut pipeline_modes, mut other_modes) = mode.split();
204 pipeline_modes.remove_unsupported();
205
206 let backends = std::env::var("WGPU_BACKEND")
213 .ok()
214 .and_then(|backend| match backend.to_lowercase().as_str() {
215 "vulkan" | "vk" => Some(wgpu::Backends::VULKAN),
216 "metal" => Some(wgpu::Backends::METAL),
217 "dx12" => Some(wgpu::Backends::DX12),
218 "primary" => Some(wgpu::Backends::PRIMARY),
219 "opengl" | "gl" => Some(wgpu::Backends::GL),
220 "secondary" => Some(wgpu::Backends::SECONDARY),
221 "all" => Some(wgpu::Backends::all()),
222 _ => None,
223 })
224 .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::SECONDARY);
225
226 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
227 backends,
228 flags: wgpu::InstanceFlags::from_build_config().with_env(),
230 backend_options: wgpu::BackendOptions::default(),
231 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
232 });
233
234 let dims = window.inner_size();
235
236 let surface = instance
237 .create_surface(window)
238 .expect("Failed to create a surface");
239
240 let adapters = instance.enumerate_adapters(backends);
241
242 for (i, adapter) in adapters.iter().enumerate() {
243 let info = adapter.get_info();
244 let supported_limits = adapter.limits();
245 info!(
246 ?info.name,
247 ?info.vendor,
248 ?info.backend,
249 ?info.device,
250 ?info.device_type,
251 ?supported_limits.max_texture_dimension_2d,
252 "graphics device #{}", i,
253 );
254 }
255
256 let adapter = match std::env::var("WGPU_ADAPTER").ok() {
257 Some(filter) if !filter.is_empty() => adapters
258 .into_iter()
259 .enumerate()
260 .find_map(|(i, adapter)| {
261 let info = adapter.get_info();
262
263 let full_name = format!("#{} {} {:?}", i, info.name, info.device_type,);
264
265 full_name.contains(&filter).then_some(adapter)
266 })
267 .ok_or(RenderError::CouldNotFindAdapter)?,
268 Some(_) | None => {
269 runtime.block_on(instance.request_adapter(&wgpu::RequestAdapterOptionsBase {
270 power_preference: wgpu::PowerPreference::HighPerformance,
271 compatible_surface: Some(&surface),
272 force_fallback_adapter: false,
273 }))?
274 },
275 };
276
277 let info = adapter.get_info();
278 let supported_limits = adapter.limits();
279 info!(
280 ?info.name,
281 ?info.vendor,
282 ?info.backend,
283 ?info.device,
284 ?info.device_type,
285 ?supported_limits.max_texture_dimension_2d,
286 "selected graphics device"
287 );
288 let graphics_backend = info.backend;
289
290 let required_limits = wgpu::Limits {
291 max_texture_dimension_1d: 0,
292 max_texture_dimension_2d: supported_limits.max_texture_dimension_2d.min(8192),
293 max_texture_dimension_3d: 0,
294 max_push_constant_size: 64,
295 ..Default::default()
296 };
297
298 #[cfg(any())] let trace = if let Some(v) = std::env::var_os("WGPU_TRACE_DIR") {
300 let path = std::path::Path::new(&v);
301 assert!(
303 path.exists(),
304 "WGPU_TRACE_DIR is set to the path \"{}\" which doesn't exist",
305 path.display()
306 );
307 assert!(
308 path.is_dir(),
309 "WGPU_TRACE_DIR is set to the path \"{}\" which is not a directory",
310 path.display()
311 );
312 assert!(
313 path.read_dir()
314 .expect("Could not read the directory that is specified by WGPU_TRACE_DIR")
315 .next()
316 .is_none(),
317 "WGPU_TRACE_DIR is set to the path \"{}\" which already contains other files",
318 path.display()
319 );
320
321 wgpu::Trace::Directory(path)
322 } else {
323 wgpu::Trace::Off
324 };
325
326 let (device, queue) =
327 runtime.block_on(adapter.request_device(&wgpu::DeviceDescriptor {
328 label: None,
330 required_features: wgpu::Features::DEPTH_CLIP_CONTROL
331 | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
332 | wgpu::Features::PUSH_CONSTANTS
333 | if ExperimentalShader::Wireframe.is_supported() {
336 wgpu::Features::POLYGON_MODE_LINE
337 } else {
338 wgpu::Features::empty()
339 }
340 | (adapter.features() & wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES),
341 required_limits,
342 experimental_features: wgpu::ExperimentalFeatures::disabled(),
343 memory_hints: wgpu::MemoryHints::Performance,
344 trace: wgpu::Trace::Off,
345 }))?;
346
347 device.on_uncaptured_error(Arc::new(move |error| {
351 error!("{}", &error);
352 panic!(
353 "wgpu error (handling all wgpu errors as fatal):\n{:?}\n{:?}",
354 error, info,
355 );
356 }));
357
358 let profiler_features_enabled = device
359 .features()
360 .intersects(wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES);
361 if !profiler_features_enabled {
362 info!(
363 "The features for GPU profiling (timestamp queries) are not available on this \
364 adapter"
365 );
366 }
367
368 let max_texture_size = device.limits().max_texture_dimension_2d;
369
370 let surface_capabilities = surface.get_capabilities(&adapter);
371 let format = surface_capabilities.formats[0];
372 info!("Using {:?} as the surface format", format);
373
374 let present_mode = other_modes.present_mode.into();
375 let surface_config = wgpu::SurfaceConfiguration {
376 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
377 desired_maximum_frame_latency: 2,
378 format,
379 width: dims.width,
380 height: dims.height,
381 present_mode: if surface_capabilities.present_modes.contains(&present_mode) {
382 present_mode
383 } else {
384 *surface_capabilities
385 .present_modes
386 .iter()
387 .find(|mode| PresentMode::try_from(**mode).is_ok())
388 .expect("There should never be no supported present modes")
389 },
390 alpha_mode: wgpu::CompositeAlphaMode::Opaque,
391 view_formats: Vec::new(),
392 };
393
394 let supported_internal_formats = [wgpu::TextureFormat::Rgba16Float, format];
395 let intermediate_format = supported_internal_formats
396 .into_iter()
397 .find(|format| {
398 use wgpu::TextureUsages as Usages;
399 use wgpu::TextureFormatFeatureFlags as Flags;
400 use super::AaMode;
401
402 let features = adapter
403 .get_texture_format_features(*format);
404
405 let usage_ok = features
406 .allowed_usages
407 .contains(Usages::RENDER_ATTACHMENT | Usages::COPY_SRC | Usages::TEXTURE_BINDING);
408
409 let msaa_flags = match pipeline_modes.aa {
410 AaMode::None | AaMode::Fxaa | AaMode::Hqx | AaMode::FxUpscale | AaMode::Bilinear => Flags::empty(),
411 AaMode::MsaaX4 => Flags::MULTISAMPLE_X4,
412 AaMode::MsaaX8 => Flags::MULTISAMPLE_X8,
413 AaMode::MsaaX16 => Flags::MULTISAMPLE_X8, };
415
416 let flags_ok = features.flags.contains(Flags::FILTERABLE | msaa_flags);
417
418 usage_ok && flags_ok
419 })
420 .expect("No supported intermediate format");
423 info!("Using {:?} as the intermediate format", intermediate_format);
424
425 surface.configure(&device, &surface_config);
426
427 let shadow_views = ShadowMap::create_shadow_views(
428 &device,
429 (dims.width, dims.height),
430 &ShadowMapMode::try_from(pipeline_modes.shadow).unwrap_or_default(),
431 max_texture_size,
432 )
433 .map_err(|err| {
434 warn!("Could not create shadow map views: {:?}", err);
435 })
436 .ok();
437
438 let rain_occlusion_view = RainOcclusionMap::create_view(
439 &device,
440 &pipeline_modes.rain_occlusion,
441 max_texture_size,
442 )
443 .map_err(|err| {
444 warn!("Could not create rain occlusion map views: {:?}", err);
445 })
446 .ok();
447
448 let shaders = Shaders::load_expect("");
449 let shaders_watcher = shaders.reload_watcher();
450
451 let layouts = {
452 let global = GlobalsLayouts::new(&device);
453
454 let debug = debug::DebugLayout::new(&device);
455 let figure = figure::FigureLayout::new(&device);
456 let shadow = shadow::ShadowLayout::new(&device);
457 let rain_occlusion = rain_occlusion::RainOcclusionLayout::new(&device);
458 let sprite = sprite::SpriteLayout::new(&device);
459 let terrain = terrain::TerrainLayout::new(&device);
460 let rope = rope::RopeLayout::new(&device);
461 let clouds = clouds::CloudsLayout::new(&device);
462 let bloom = bloom::BloomLayout::new(&device);
463 let postprocess = Arc::new(postprocess::PostProcessLayout::new(
464 &device,
465 &pipeline_modes,
466 ));
467 let ui = ui::UiLayout::new(&device);
468 let premultiply_alpha = ui::PremultiplyAlphaLayout::new(&device);
469 let blit = blit::BlitLayout::new(&device);
470
471 let immutable = Arc::new(ImmutableLayouts {
472 global,
473
474 debug,
475 figure,
476 shadow,
477 rain_occlusion,
478 sprite,
479 terrain,
480 rope,
481 clouds,
482 bloom,
483 ui,
484 premultiply_alpha,
485 blit,
486 });
487
488 Layouts {
489 immutable,
490 postprocess,
491 }
492 };
493
494 let (interface_pipelines, creating) = pipeline_creation::initial_create_pipelines(
495 device.clone(),
496 graphics_backend,
497 Layouts {
498 immutable: Arc::clone(&layouts.immutable),
499 postprocess: Arc::clone(&layouts.postprocess),
500 },
501 shaders.cloned(),
502 pipeline_modes.clone(),
503 surface_config.clone(), shadow_views.is_some(),
505 intermediate_format,
506 )?;
507
508 let state = State::Interface {
509 pipelines: interface_pipelines,
510 shadow_views,
511 rain_occlusion_view,
512 creating,
513 };
514
515 let (views, bloom_sizes) = Self::create_rt_views(
516 &device,
517 (dims.width, dims.height),
518 &pipeline_modes,
519 &other_modes,
520 intermediate_format,
521 );
522
523 let create_sampler = |filter| {
524 device.create_sampler(&wgpu::SamplerDescriptor {
525 label: None,
526 address_mode_u: AddressMode::ClampToEdge,
527 address_mode_v: AddressMode::ClampToEdge,
528 address_mode_w: AddressMode::ClampToEdge,
529 mag_filter: filter,
530 min_filter: filter,
531 mipmap_filter: FilterMode::Nearest,
532 compare: None,
533 ..Default::default()
534 })
535 };
536
537 let sampler = create_sampler(FilterMode::Linear);
538 let depth_sampler = create_sampler(FilterMode::Nearest);
539
540 let noise_tex = Texture::new(
541 &device,
542 &queue,
543 &assets::Image::load_expect("voxygen.texture.noise").read().0,
544 Some(FilterMode::Linear),
545 Some(AddressMode::Repeat),
546 )?;
547
548 let clouds_locals =
549 Self::create_consts_inner(&device, &queue, &[clouds::Locals::default()]);
550 let postprocess_locals =
551 Self::create_consts_inner(&device, &queue, &[postprocess::Locals::default()]);
552
553 let locals = Locals::new(
554 &device,
555 &layouts,
556 clouds_locals,
557 postprocess_locals,
558 &views.tgt_color,
559 &views.tgt_mat,
560 &views.tgt_depth,
561 views.bloom_tgts.as_ref().map(|tgts| locals::BloomParams {
562 locals: bloom_sizes.map(|size| {
563 Self::create_consts_inner(&device, &queue, &[bloom::Locals::new(size)])
564 }),
565 src_views: [&views.tgt_color_pp, &tgts[1], &tgts[2], &tgts[3], &tgts[4]],
566 final_tgt_view: &tgts[0],
567 }),
568 &views.tgt_color_pp,
569 &sampler,
570 &depth_sampler,
571 );
572
573 let quad_index_buffer_u16 =
574 create_quad_index_buffer_u16(&device, QUAD_INDEX_BUFFER_U16_START_VERT_LEN as usize);
575 let quad_index_buffer_u32 =
576 create_quad_index_buffer_u32(&device, QUAD_INDEX_BUFFER_U32_START_VERT_LEN as usize);
577 other_modes.profiler_enabled &= profiler_features_enabled;
578 #[cfg(not(feature = "tracy"))]
579 let profiler =
580 wgpu_profiler::GpuProfiler::new(&device, wgpu_profiler::GpuProfilerSettings {
581 enable_timer_queries: other_modes.profiler_enabled,
582 enable_debug_groups: other_modes.profiler_enabled,
583 max_num_pending_frames: 4,
584 })
585 .expect("Error creating profiler");
586 #[cfg(feature = "tracy")]
587 let profiler = wgpu_profiler::GpuProfiler::new_with_tracy_client(
588 wgpu_profiler::GpuProfilerSettings {
589 enable_timer_queries: true,
590 enable_debug_groups: true,
591 max_num_pending_frames: 4,
592 },
593 adapter.get_info().backend,
594 &device,
595 &queue,
596 )
597 .expect("Error creating profiler");
598
599 #[cfg(feature = "egui-ui")]
600 let egui_renderer = egui_wgpu::Renderer::new(&device, format, Default::default());
601
602 let present_modes = surface
603 .get_capabilities(&adapter)
604 .present_modes
605 .into_iter()
606 .filter_map(|present_mode| PresentMode::try_from(present_mode).ok())
607 .collect();
608
609 Ok(Self {
610 device,
611 queue,
612 surface,
613 surface_config,
614
615 state,
616 recreation_pending: None,
617
618 layouts,
619 locals,
620 views,
621
622 sampler,
623 depth_sampler,
624 noise_tex,
625
626 quad_index_buffer_u16,
627 quad_index_buffer_u32,
628
629 shaders,
630 shaders_watcher,
631
632 pipeline_modes,
633 other_modes,
634 resolution: Vec2::new(dims.width, dims.height),
635
636 take_screenshot: None,
637
638 profiler,
639 profile_times: Vec::new(),
640 profiler_features_enabled,
641
642 ui_premultiply_uploads: Default::default(),
643
644 #[cfg(feature = "egui-ui")]
645 egui_renderer,
646
647 is_minimized: false,
648
649 graphics_backend,
650
651 intermediate_format,
652
653 present_modes,
654 max_texture_size,
655 })
656 }
657
658 pub fn graphics_backend(&self) -> wgpu::Backend { self.graphics_backend }
660
661 pub fn pipeline_creation_status(&self) -> Option<(usize, usize)> {
665 if let State::Interface { creating, .. } = &self.state {
666 Some(creating.status())
667 } else {
668 None
669 }
670 }
671
672 pub fn pipeline_recreation_status(&self) -> Option<(usize, usize)> {
676 if let State::Complete { recreating, .. } = &self.state {
677 recreating.as_ref().map(|(_, c)| c.status())
678 } else {
679 None
680 }
681 }
682
683 pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> {
685 let (mut pipeline_modes, other_modes) = mode.split();
686 pipeline_modes.remove_unsupported();
687
688 if self.other_modes != other_modes {
689 self.other_modes = other_modes;
690
691 if self.present_modes.contains(&self.other_modes.present_mode) {
693 self.surface_config.present_mode = self.other_modes.present_mode.into()
694 }
695
696 self.other_modes.profiler_enabled &= self.profiler_features_enabled;
698 if !self.other_modes.profiler_enabled {
700 core::mem::take(&mut self.profile_times);
702 }
703 self.profiler
704 .change_settings(wgpu_profiler::GpuProfilerSettings {
705 enable_timer_queries: self.other_modes.profiler_enabled,
706 enable_debug_groups: self.other_modes.profiler_enabled,
707 max_num_pending_frames: 4,
708 })
709 .expect("Error creating profiler");
710
711 self.on_resize(self.resolution);
713 }
714
715 if self.pipeline_modes != pipeline_modes
719 || self
720 .recreation_pending
721 .as_ref()
722 .is_some_and(|modes| modes != &pipeline_modes)
723 {
724 self.recreate_pipelines(pipeline_modes);
726 }
727
728 Ok(())
729 }
730
731 pub fn pipeline_modes(&self) -> &PipelineModes { &self.pipeline_modes }
733
734 pub fn present_modes(&self) -> &[PresentMode] { &self.present_modes }
736
737 pub fn timings(&self) -> Vec<(u8, &str, f64)> {
741 fn recursive_collect<'a>(
742 vec: &mut Vec<(u8, &'a str, f64)>,
743 result: &'a wgpu_profiler::GpuTimerQueryResult,
744 nest_level: u8,
745 ) {
746 if let Some(time) = &result.time {
747 vec.push((nest_level, &result.label, time.end - time.start));
748 }
749 result
750 .nested_queries
751 .iter()
752 .for_each(|child| recursive_collect(vec, child, nest_level + 1));
753 }
754 let mut vec = Vec::new();
755 self.profile_times
756 .iter()
757 .for_each(|child| recursive_collect(&mut vec, child, 0));
758 vec
759 }
760
761 pub fn on_resize(&mut self, dims: Vec2<u32>) {
763 if dims.x != 0 && dims.y != 0 {
765 self.is_minimized = false;
766 self.resolution = dims;
768 self.surface_config.width = dims.x;
769 self.surface_config.height = dims.y;
770 self.surface.configure(&self.device, &self.surface_config);
771
772 let (views, bloom_sizes) = Self::create_rt_views(
774 &self.device,
775 (dims.x, dims.y),
776 &self.pipeline_modes,
777 &self.other_modes,
778 self.intermediate_format,
779 );
780 self.views = views;
781
782 let bloom_params = self
783 .views
784 .bloom_tgts
785 .as_ref()
786 .map(|tgts| locals::BloomParams {
787 locals: bloom_sizes.map(|size| {
788 Self::create_consts_inner(&self.device, &self.queue, &[bloom::Locals::new(
789 size,
790 )])
791 }),
792 src_views: [
793 &self.views.tgt_color_pp,
794 &tgts[1],
795 &tgts[2],
796 &tgts[3],
797 &tgts[4],
798 ],
799 final_tgt_view: &tgts[0],
800 });
801
802 self.locals.rebind(
803 &self.device,
804 &self.layouts,
805 &self.views.tgt_color,
806 &self.views.tgt_mat,
807 &self.views.tgt_depth,
808 bloom_params,
809 &self.views.tgt_color_pp,
810 &self.sampler,
811 &self.depth_sampler,
812 );
813
814 let shadow_views = match &mut self.state {
816 State::Interface {
817 shadow_views,
818 rain_occlusion_view,
819 ..
820 } => shadow_views
821 .as_mut()
822 .map(|s| (&mut s.0, &mut s.1))
823 .zip(rain_occlusion_view.as_mut()),
824 State::Complete {
825 shadow:
826 Shadow {
827 map: ShadowMap::Enabled(shadow_map),
828 rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
829 ..
830 },
831 ..
832 } => Some((
833 (&mut shadow_map.point_depth, &mut shadow_map.directed_depth),
834 &mut rain_occlusion_map.depth,
835 )),
836 State::Complete { .. } => None,
837 State::Nothing => None, };
839
840 let mut update_shadow_bind = false;
841 let (shadow_views, rain_views) = shadow_views.unzip();
842
843 if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) =
844 (shadow_views, self.pipeline_modes.shadow)
845 {
846 match ShadowMap::create_shadow_views(
847 &self.device,
848 (dims.x, dims.y),
849 &mode,
850 self.max_texture_size,
851 ) {
852 Ok((new_point_depth, new_directed_depth)) => {
853 *point_depth = new_point_depth;
854 *directed_depth = new_directed_depth;
855
856 update_shadow_bind = true;
857 },
858 Err(err) => {
859 warn!("Could not create shadow map views: {:?}", err);
860 },
861 }
862 }
863 if let Some(rain_depth) = rain_views {
864 match RainOcclusionMap::create_view(
865 &self.device,
866 &self.pipeline_modes.rain_occlusion,
867 self.max_texture_size,
868 ) {
869 Ok(new_rain_depth) => {
870 *rain_depth = new_rain_depth;
871
872 update_shadow_bind = true;
873 },
874 Err(err) => {
875 warn!("Could not create rain occlusion map view: {:?}", err);
876 },
877 }
878 }
879 if update_shadow_bind {
880 if let State::Complete {
882 shadow:
883 Shadow {
884 bind,
885 map: ShadowMap::Enabled(shadow_map),
886 rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
887 ..
888 },
889 ..
890 } = &mut self.state
891 {
892 *bind = self.layouts.global.bind_shadow_textures(
893 &self.device,
894 &shadow_map.point_depth,
895 &shadow_map.directed_depth,
896 &rain_occlusion_map.depth,
897 );
898 }
899 }
900 } else {
901 self.is_minimized = true;
902 }
903 }
904
905 pub fn maintain(&self) {
906 if self.is_minimized {
907 self.queue.submit(std::iter::empty());
908 }
909
910 let _ = self.device.poll(wgpu::PollType::Poll);
911 }
912
913 fn create_rt_views(
915 device: &wgpu::Device,
916 size: (u32, u32),
917 pipeline_modes: &PipelineModes,
918 other_modes: &OtherModes,
919 format: wgpu::TextureFormat,
920 ) -> (Views, [Vec2<f32>; bloom::NUM_SIZES]) {
921 let upscaled = Vec2::<u32>::from(size)
922 .map(|e| (e as f32 * other_modes.upscale_mode.factor) as u32)
923 .into_tuple();
924 let (width, height) = upscaled;
925 let sample_count = pipeline_modes.aa.samples();
926 let levels = 1;
927
928 let color_view = |width, height, format| {
929 let tex = device.create_texture(&wgpu::TextureDescriptor {
930 label: None,
931 size: wgpu::Extent3d {
932 width,
933 height,
934 depth_or_array_layers: 1,
935 },
936 mip_level_count: levels,
937 sample_count,
938 dimension: wgpu::TextureDimension::D2,
939 format,
940 usage: wgpu::TextureUsages::TEXTURE_BINDING
941 | wgpu::TextureUsages::RENDER_ATTACHMENT,
942 view_formats: &[],
943 });
944
945 tex.create_view(&wgpu::TextureViewDescriptor {
946 label: None,
947 format: Some(format),
948 dimension: Some(wgpu::TextureViewDimension::D2),
949 usage: None,
950 aspect: wgpu::TextureAspect::All,
952 base_mip_level: 0,
953 mip_level_count: None,
954 base_array_layer: 0,
955 array_layer_count: None,
956 })
957 };
958
959 let tgt_color_view = color_view(width, height, format);
960 let tgt_color_pp_view = color_view(width, height, format);
961
962 let tgt_mat_view = color_view(width, height, wgpu::TextureFormat::Rgba8Uint);
963
964 let mut size_shift = 0;
965 let bloom_sizes = [(); bloom::NUM_SIZES].map(|()| {
967 let size = Vec2::new(width, height).map(|e| (e >> size_shift).max(1));
969 size_shift += 1;
970 size
971 });
972
973 let bloom_tgt_views = pipeline_modes
974 .bloom
975 .is_on()
976 .then(|| bloom_sizes.map(|size| color_view(size.x, size.y, format)));
977
978 let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor {
979 label: None,
980 size: wgpu::Extent3d {
981 width,
982 height,
983 depth_or_array_layers: 1,
984 },
985 mip_level_count: levels,
986 sample_count,
987 dimension: wgpu::TextureDimension::D2,
988 format: wgpu::TextureFormat::Depth32Float,
989 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
990 view_formats: &[],
991 });
992 let tgt_depth_view = tgt_depth_tex.create_view(&wgpu::TextureViewDescriptor {
993 label: None,
994 format: Some(wgpu::TextureFormat::Depth32Float),
995 dimension: Some(wgpu::TextureViewDimension::D2),
996 usage: None,
997 aspect: wgpu::TextureAspect::DepthOnly,
998 base_mip_level: 0,
999 mip_level_count: None,
1000 base_array_layer: 0,
1001 array_layer_count: None,
1002 });
1003
1004 let win_depth_tex = device.create_texture(&wgpu::TextureDescriptor {
1005 label: None,
1006 size: wgpu::Extent3d {
1007 width: size.0,
1008 height: size.1,
1009 depth_or_array_layers: 1,
1010 },
1011 mip_level_count: levels,
1012 sample_count,
1013 dimension: wgpu::TextureDimension::D2,
1014 format: wgpu::TextureFormat::Depth32Float,
1015 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1016 view_formats: &[],
1017 });
1018 let win_depth_view = win_depth_tex.create_view(&wgpu::TextureViewDescriptor {
1020 label: None,
1021 format: Some(wgpu::TextureFormat::Depth32Float),
1022 dimension: Some(wgpu::TextureViewDimension::D2),
1023 usage: None,
1024 aspect: wgpu::TextureAspect::DepthOnly,
1025 base_mip_level: 0,
1026 mip_level_count: None,
1027 base_array_layer: 0,
1028 array_layer_count: None,
1029 });
1030
1031 (
1032 Views {
1033 tgt_color: tgt_color_view,
1034 tgt_mat: tgt_mat_view,
1035 tgt_depth: tgt_depth_view,
1036 bloom_tgts: bloom_tgt_views,
1037 tgt_color_pp: tgt_color_pp_view,
1038 _win_depth: win_depth_view,
1039 },
1040 bloom_sizes.map(|s| s.map(|e| e as f32)),
1041 )
1042 }
1043
1044 pub fn resolution(&self) -> Vec2<u32> { self.resolution }
1046
1047 pub fn internal_resolution(&self) -> Vec2<u32> {
1049 self.resolution
1050 .map(|e| e as f32 * self.other_modes.upscale_mode.factor)
1051 .as_()
1052 }
1053
1054 pub fn get_shadow_resolution(&self) -> (Vec2<u32>, Vec2<u32>) {
1056 match &self.state {
1057 State::Interface { shadow_views, .. } => shadow_views.as_ref().map(|s| (&s.0, &s.1)),
1058 State::Complete {
1059 shadow:
1060 Shadow {
1061 map: ShadowMap::Enabled(shadow_map),
1062 ..
1063 },
1064 ..
1065 } => Some((&shadow_map.point_depth, &shadow_map.directed_depth)),
1066 State::Complete { .. } | State::Nothing => None,
1067 }
1068 .map(|(point, directed)| (point.get_dimensions().xy(), directed.get_dimensions().xy()))
1069 .unwrap_or_else(|| (Vec2::new(1, 1), Vec2::new(1, 1)))
1070 }
1071
1072 pub fn start_recording_frame<'a>(
1078 &'a mut self,
1079 globals: &'a GlobalsBindGroup,
1080 ) -> Result<Option<drawer::Drawer<'a>>, RenderError> {
1081 span!(
1082 _guard,
1083 "start_recording_frame",
1084 "Renderer::start_recording_frame"
1085 );
1086
1087 if self.is_minimized {
1088 return Ok(None);
1089 }
1090
1091 if self.other_modes.profiler_enabled {
1093 let timestamp_period = self.queue.get_timestamp_period();
1095 if let Some(profile_times) = self.profiler.process_finished_frame(timestamp_period) {
1096 self.profile_times = profile_times;
1097 }
1098 }
1099
1100 let state = core::mem::replace(&mut self.state, State::Nothing);
1103 let mut trigger_on_resize = false;
1107 self.state = if let State::Interface {
1109 pipelines: interface,
1110 shadow_views,
1111 rain_occlusion_view,
1112 creating,
1113 } = state
1114 {
1115 match creating.try_complete() {
1116 Ok(pipelines) => {
1117 let IngameAndShadowPipelines {
1118 ingame,
1119 shadow,
1120 rain_occlusion,
1121 } = pipelines;
1122
1123 let pipelines = Pipelines::consolidate(interface, ingame);
1124
1125 let shadow_map = ShadowMap::new(
1126 &self.device,
1127 &self.queue,
1128 shadow.point,
1129 shadow.directed,
1130 shadow.figure,
1131 shadow.debug,
1132 shadow_views,
1133 );
1134
1135 let rain_occlusion_map = RainOcclusionMap::new(
1136 &self.device,
1137 &self.queue,
1138 rain_occlusion.terrain,
1139 rain_occlusion.figure,
1140 rain_occlusion_view,
1141 );
1142
1143 let shadow_bind = {
1144 let (point, directed) = shadow_map.textures();
1145 self.layouts.global.bind_shadow_textures(
1146 &self.device,
1147 point,
1148 directed,
1149 rain_occlusion_map.texture(),
1150 )
1151 };
1152
1153 let shadow = Shadow {
1154 rain_map: rain_occlusion_map,
1155 map: shadow_map,
1156 bind: shadow_bind,
1157 };
1158
1159 State::Complete {
1160 pipelines,
1161 shadow,
1162 recreating: None,
1163 }
1164 },
1165 Err(creating) => State::Interface {
1167 pipelines: interface,
1168 shadow_views,
1169 rain_occlusion_view,
1170 creating,
1171 },
1172 }
1173 } else if let State::Complete {
1175 pipelines,
1176 mut shadow,
1177 recreating: Some((new_pipeline_modes, pipeline_creation)),
1178 } = state
1179 {
1180 match pipeline_creation.try_complete() {
1181 Ok(Ok((
1182 pipelines,
1183 shadow_pipelines,
1184 rain_occlusion_pipelines,
1185 postprocess_layout,
1186 ))) => {
1187 if let (
1188 Some(point_pipeline),
1189 Some(terrain_directed_pipeline),
1190 Some(figure_directed_pipeline),
1191 Some(debug_directed_pipeline),
1192 ShadowMap::Enabled(shadow_map),
1193 ) = (
1194 shadow_pipelines.point,
1195 shadow_pipelines.directed,
1196 shadow_pipelines.figure,
1197 shadow_pipelines.debug,
1198 &mut shadow.map,
1199 ) {
1200 shadow_map.point_pipeline = point_pipeline;
1201 shadow_map.terrain_directed_pipeline = terrain_directed_pipeline;
1202 shadow_map.figure_directed_pipeline = figure_directed_pipeline;
1203 shadow_map.debug_directed_pipeline = debug_directed_pipeline;
1204 }
1205
1206 if let (
1207 Some(terrain_directed_pipeline),
1208 Some(figure_directed_pipeline),
1209 RainOcclusionMap::Enabled(rain_occlusion_map),
1210 ) = (
1211 rain_occlusion_pipelines.terrain,
1212 rain_occlusion_pipelines.figure,
1213 &mut shadow.rain_map,
1214 ) {
1215 rain_occlusion_map.terrain_pipeline = terrain_directed_pipeline;
1216 rain_occlusion_map.figure_pipeline = figure_directed_pipeline;
1217 }
1218
1219 self.pipeline_modes = new_pipeline_modes;
1220 self.layouts.postprocess = postprocess_layout;
1221 trigger_on_resize = true;
1225
1226 State::Complete {
1227 pipelines,
1228 shadow,
1229 recreating: None,
1230 }
1231 },
1232 Ok(Err(e)) => {
1233 error!(?e, "Could not recreate shaders from assets due to an error");
1234 State::Complete {
1235 pipelines,
1236 shadow,
1237 recreating: None,
1238 }
1239 },
1240 Err(pipeline_creation) => State::Complete {
1242 pipelines,
1243 shadow,
1244 recreating: Some((new_pipeline_modes, pipeline_creation)),
1245 },
1246 }
1247 } else {
1248 state
1249 };
1250
1251 if trigger_on_resize {
1255 self.on_resize(self.resolution);
1256 }
1257
1258 if self.shaders_watcher.reloaded() {
1260 self.recreate_pipelines(self.pipeline_modes.clone());
1261 }
1262
1263 if matches!(&self.state, State::Complete {
1265 recreating: None,
1266 ..
1267 }) && let Some(new_pipeline_modes) = self.recreation_pending.take()
1268 {
1269 self.recreate_pipelines(new_pipeline_modes);
1270 }
1271
1272 let texture = match self.surface.get_current_texture() {
1273 Ok(texture) => {
1274 if texture.suboptimal {
1275 warn!("Suboptimal swap chain, recreating");
1276 drop(texture);
1277 self.surface.configure(&self.device, &self.surface_config);
1278 return Ok(None);
1279 } else {
1280 texture
1281 }
1282 },
1283 Err(err @ wgpu::SurfaceError::Lost) => {
1285 warn!("{}. Recreating swap chain. A frame will be missed", err);
1286 self.surface.configure(&self.device, &self.surface_config);
1287 return Ok(None);
1288 },
1289 Err(wgpu::SurfaceError::Timeout) => {
1290 return Ok(None);
1294 },
1295 Err(err @ wgpu::SurfaceError::Outdated) => {
1296 warn!("{}. Recreating the swapchain", err);
1297 self.surface.configure(&self.device, &self.surface_config);
1298 return Ok(None);
1299 },
1300 Err(err @ (wgpu::SurfaceError::OutOfMemory | wgpu::SurfaceError::Other)) => {
1301 return Err(err.into());
1302 },
1303 };
1304 let encoder = self
1305 .device
1306 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1307 label: Some("A render encoder"),
1308 });
1309
1310 Ok(Some(drawer::Drawer::new(encoder, self, texture, globals)))
1311 }
1312
1313 fn recreate_pipelines(&mut self, pipeline_modes: PipelineModes) {
1315 match &mut self.state {
1316 State::Complete { recreating, .. } if recreating.is_some() => {
1317 self.recreation_pending = Some(pipeline_modes);
1320 },
1321 State::Complete {
1322 recreating, shadow, ..
1323 } => {
1324 *recreating = Some((
1325 pipeline_modes.clone(),
1326 pipeline_creation::recreate_pipelines(
1327 self.device.clone(),
1328 self.graphics_backend,
1329 Arc::clone(&self.layouts.immutable),
1330 self.shaders.cloned(),
1331 pipeline_modes,
1332 self.surface_config.clone(), shadow.map.is_enabled(),
1338 self.intermediate_format,
1339 ),
1340 ));
1341 },
1342 State::Interface { .. } => {
1343 self.recreation_pending = Some(pipeline_modes);
1346 },
1347 State::Nothing => {},
1348 }
1349 }
1350
1351 pub fn create_consts<T: Copy + bytemuck::Pod>(&mut self, vals: &[T]) -> Consts<T> {
1353 Self::create_consts_inner(&self.device, &self.queue, vals)
1354 }
1355
1356 pub fn create_consts_inner<T: Copy + bytemuck::Pod>(
1357 device: &wgpu::Device,
1358 queue: &wgpu::Queue,
1359 vals: &[T],
1360 ) -> Consts<T> {
1361 let mut consts = Consts::new(device, vals.len());
1362 consts.update(queue, vals, 0);
1363 consts
1364 }
1365
1366 pub fn update_consts<T: Copy + bytemuck::Pod>(&self, consts: &mut Consts<T>, vals: &[T]) {
1368 consts.update(&self.queue, vals, 0)
1369 }
1370
1371 pub fn update_clouds_locals(&mut self, new_val: clouds::Locals) {
1372 self.locals.clouds.update(&self.queue, &[new_val], 0)
1373 }
1374
1375 pub fn update_postprocess_locals(&mut self, new_val: postprocess::Locals) {
1376 self.locals.postprocess.update(&self.queue, &[new_val], 0)
1377 }
1378
1379 pub fn create_instances<T: Copy + bytemuck::Pod>(&mut self, vals: &[T]) -> Instances<T> {
1381 let mut instances = Instances::new(&self.device, vals.len());
1382 instances.update(&self.queue, vals, 0);
1383 instances
1384 }
1385
1386 pub(super) fn ensure_sufficient_index_length<V: Vertex>(
1389 &mut self,
1390 vert_length: usize,
1392 ) {
1393 let quad_index_length = vert_length / 4 * 6;
1394
1395 #[expect(clippy::collapsible_match)]
1396 match V::QUADS_INDEX {
1397 Some(wgpu::IndexFormat::Uint16) => {
1398 if self.quad_index_buffer_u16.len() < quad_index_length {
1400 if vert_length > u16::MAX as usize {
1402 panic!(
1403 "Vertex type: {} needs to use a larger index type, length: {}",
1404 core::any::type_name::<V>(),
1405 vert_length
1406 );
1407 }
1408 self.quad_index_buffer_u16 =
1409 create_quad_index_buffer_u16(&self.device, vert_length);
1410 }
1411 },
1412 Some(wgpu::IndexFormat::Uint32) => {
1413 if self.quad_index_buffer_u32.len() < quad_index_length {
1415 if vert_length > u32::MAX as usize {
1417 panic!(
1418 "More than u32::MAX({}) verts({}) for type({}) using an index buffer!",
1419 u32::MAX,
1420 vert_length,
1421 core::any::type_name::<V>()
1422 );
1423 }
1424 self.quad_index_buffer_u32 =
1425 create_quad_index_buffer_u32(&self.device, vert_length);
1426 }
1427 },
1428 None => {},
1429 }
1430 }
1431
1432 pub fn create_sprite_verts(&mut self, mesh: Mesh<sprite::Vertex>) -> sprite::SpriteVerts {
1433 self.ensure_sufficient_index_length::<sprite::Vertex>(sprite::VERT_PAGE_SIZE as usize);
1434 sprite::create_verts_buffer(&self.device, mesh)
1435 }
1436
1437 pub fn create_model<V: Vertex>(&mut self, mesh: &Mesh<V>) -> Option<Model<V>> {
1440 self.ensure_sufficient_index_length::<V>(mesh.vertices().len());
1441 Model::new(&self.device, mesh)
1442 }
1443
1444 pub fn create_dynamic_model<V: Vertex>(&mut self, size: usize) -> DynamicModel<V> {
1446 self.ensure_sufficient_index_length::<V>(size);
1447 DynamicModel::new(&self.device, size)
1448 }
1449
1450 pub fn update_model<V: Vertex>(&self, model: &DynamicModel<V>, mesh: &Mesh<V>, offset: usize) {
1452 model.update(&self.queue, mesh, offset)
1453 }
1454
1455 pub fn max_texture_size(&self) -> u32 { self.max_texture_size }
1457
1458 pub fn create_texture_with_data_raw(
1463 &mut self,
1464 texture_info: &wgpu::TextureDescriptor,
1465 view_info: &wgpu::TextureViewDescriptor,
1466 sampler_info: &wgpu::SamplerDescriptor,
1467 data: &[u8],
1468 ) -> Texture {
1469 let tex = Texture::new_raw(&self.device, texture_info, view_info, sampler_info);
1470
1471 let size = texture_info.size;
1472 let block_size = texture_info.format.block_copy_size(None).unwrap();
1473 assert_eq!(
1474 size.width as usize
1475 * size.height as usize
1476 * size.depth_or_array_layers as usize
1477 * block_size as usize,
1478 data.len(),
1479 "Provided data length {} does not fill the provided texture size {:?}",
1480 data.len(),
1481 size,
1482 );
1483
1484 tex.update(
1485 &self.queue,
1486 [0; 2],
1487 [texture_info.size.width, texture_info.size.height],
1488 data,
1489 );
1490
1491 tex
1492 }
1493
1494 pub fn create_texture_raw(
1496 &mut self,
1497 texture_info: &wgpu::TextureDescriptor,
1498 view_info: &wgpu::TextureViewDescriptor,
1499 sampler_info: &wgpu::SamplerDescriptor,
1500 ) -> Texture {
1501 let texture = Texture::new_raw(&self.device, texture_info, view_info, sampler_info);
1502 texture.clear(&self.queue); texture
1504 }
1505
1506 pub fn create_texture(
1508 &mut self,
1509 image: &image::DynamicImage,
1510 filter_method: Option<FilterMode>,
1511 address_mode: Option<AddressMode>,
1512 ) -> Result<Texture, RenderError> {
1513 Texture::new(
1514 &self.device,
1515 &self.queue,
1516 image,
1517 filter_method,
1518 address_mode,
1519 )
1520 }
1521
1522 pub fn create_dynamic_texture(&mut self, dims: Vec2<u32>) -> Texture {
1526 Texture::new_dynamic(&self.device, &self.queue, dims.x, dims.y)
1527 }
1528
1529 pub fn update_texture<T: bytemuck::Pod>(
1531 &mut self,
1532 texture: &Texture,
1533 offset: [u32; 2],
1534 size: [u32; 2],
1535 data: &[T],
1536 ) {
1537 texture.update(&self.queue, offset, size, bytemuck::cast_slice(data))
1538 }
1539
1540 pub fn ui_premultiply_upload(
1542 &mut self,
1543 target_texture: &Arc<Texture>,
1544 batch: ui::UploadBatchId,
1545 image: &image::RgbaImage,
1546 offset: Vec2<u16>,
1547 ) -> ui::UploadBatchId {
1548 let upload = ui::PremultiplyUpload::prepare(
1549 &self.device,
1550 &self.queue,
1551 &self.layouts.premultiply_alpha,
1552 image,
1553 offset,
1554 );
1555 self.ui_premultiply_uploads
1556 .submit(target_texture, batch, upload)
1557 }
1558
1559 pub fn create_screenshot(
1561 &mut self,
1562 screenshot_handler: impl FnOnce(Result<image::RgbImage, String>) + Send + 'static,
1563 ) {
1564 self.take_screenshot = Some(Box::new(screenshot_handler));
1566 if self.other_modes.profiler_enabled {
1568 let file_name = format!(
1569 "frame-trace_{}.json",
1570 std::time::SystemTime::now()
1571 .duration_since(std::time::SystemTime::UNIX_EPOCH)
1572 .map(|d| d.as_millis())
1573 .unwrap_or(0)
1574 );
1575
1576 if let Err(err) = wgpu_profiler::chrometrace::write_chrometrace(
1577 std::path::Path::new(&file_name),
1578 &self.profile_times,
1579 ) {
1580 error!(?err, "Failed to save GPU timing snapshot");
1581 } else {
1582 info!("Saved GPU timing snapshot as: {}", file_name);
1583 }
1584 }
1585 }
1586
1587 }
1635
1636fn create_quad_index_buffer_u16(device: &wgpu::Device, vert_length: usize) -> Buffer<u16> {
1637 assert!(vert_length <= u16::MAX as usize);
1638 let indices = [0, 1, 2, 2, 1, 3]
1639 .iter()
1640 .cycle()
1641 .copied()
1642 .take(vert_length / 4 * 6)
1643 .enumerate()
1644 .map(|(i, b)| (i / 6 * 4 + b) as u16)
1645 .collect::<Vec<_>>();
1646
1647 Buffer::new(device, wgpu::BufferUsages::INDEX, &indices)
1648}
1649
1650fn create_quad_index_buffer_u32(device: &wgpu::Device, vert_length: usize) -> Buffer<u32> {
1651 assert!(vert_length <= u32::MAX as usize);
1652 let indices = [0, 1, 2, 2, 1, 3]
1653 .iter()
1654 .cycle()
1655 .copied()
1656 .take(vert_length / 4 * 6)
1657 .enumerate()
1658 .map(|(i, b)| (i / 6 * 4 + b) as u32)
1659 .collect::<Vec<_>>();
1660
1661 Buffer::new(device, wgpu::BufferUsages::INDEX, &indices)
1662}
1663
1664pub struct AltIndices {
1675 pub deep_end: usize,
1676 pub underground_end: usize,
1677}
1678
1679#[derive(Copy, Clone, Default)]
1682pub enum CullingMode {
1683 #[default]
1685 None,
1686 Surface,
1689 Underground,
1692}