Skip to main content

veloren_voxygen/render/renderer/
mod.rs

1mod binding;
2mod compiler;
3pub(super) mod drawer;
4// Consts and bind groups for post-process and clouds
5mod 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
45/// A type that stores all the layouts associated with this renderer that never
46/// change when the RenderMode is modified.
47struct 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
64/// A type that stores all the layouts associated with this renderer.
65struct 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
77/// Render target views
78struct Views {
79    // NOTE: unused for now, maybe... we will want it for something
80    _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    // TODO: rename
88    tgt_color_pp: wgpu::TextureView,
89}
90
91/// Shadow rendering textures, layouts, pipelines, and bind groups
92struct Shadow {
93    rain_map: RainOcclusionMap,
94    map: ShadowMap,
95    bind: ShadowTexturesBindGroup,
96}
97
98/// Represent two states of the renderer:
99/// 1. Only interface pipelines created
100/// 2. All of the pipelines have been created
101#[expect(clippy::large_enum_variant)]
102enum State {
103    // NOTE: this is used as a transient placeholder for moving things out of State temporarily
104    Nothing,
105    Interface {
106        pipelines: InterfacePipelines,
107        shadow_views: Option<(Texture, Texture)>,
108        rain_occlusion_view: Option<Texture>,
109        // In progress creation of the remaining pipelines in the background
110        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
132/// A type that encapsulates rendering state. `Renderer` is central to Voxygen's
133/// rendering subsystem and contains any state necessary to interact with the
134/// GPU, along with pipeline state objects (PSOs) needed to renderer different
135/// kinds of models to the screen.
136pub 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    // Some if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader
147    // hotloading)
148    recreation_pending: Option<PipelineModes>,
149
150    layouts: Layouts,
151    // Note: we keep these here since their bind groups need to be updated if we resize the
152    // color/depth textures
153    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    // If this is Some then a screenshot will be taken and passed to the handler here
168    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    // This checks is added because windows resizes the window to 0,0 when
180    // minimizing and this causes a bunch of validation errors
181    is_minimized: bool,
182
183    // To remember backend after initialiation for debug info and other purposes.
184    graphics_backend: wgpu::Backend,
185
186    /// The texture format used for the intermediate rendering passes
187    intermediate_format: wgpu::TextureFormat,
188
189    /// Supported present modes.
190    present_modes: Vec<PresentMode>,
191    /// Cached max texture size.
192    max_texture_size: u32,
193}
194
195impl Renderer {
196    /// Create a new `Renderer` from a variety of backend-specific components
197    /// and the window targets.
198    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        // Enable seamless cubemaps globally, where available--they are essentially a
207        // strict improvement on regular cube maps.
208        //
209        // Note that since we only have to enable this once globally, there is no point
210        // in doing this on rerender.
211        // Self::enable_seamless_cube_maps(&mut device);
212        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            // TODO: Look into what we want here.
229            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())] // Add this back when tracing is added back to `wgpu`
299        let trace = if let Some(v) = std::env::var_os("WGPU_TRACE_DIR") {
300            let path = std::path::Path::new(&v);
301            // We don't want to continue if we can't actually collect the api trace
302            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                // TODO
329                label: None,
330                required_features: wgpu::Features::DEPTH_CLIP_CONTROL
331                    | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER
332                    | wgpu::Features::PUSH_CONSTANTS
333                    // Only used for `ExperimentalShader::Wireframe`, so don't gate all builds on
334                    // its presence.
335                    | 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        // Set error handler for wgpu errors
348        // This is better for use than their default because it includes the error in
349        // the panic message
350        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, // TODO?
414                };
415
416                let flags_ok = features.flags.contains(Flags::FILTERABLE | msaa_flags);
417
418                usage_ok && flags_ok
419            })
420            // This should be unreachable as the surface format should always support the
421            // needed capabilities
422            .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(), // Note: cheap clone
504            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    /// Get the graphics backend being used.
659    pub fn graphics_backend(&self) -> wgpu::Backend { self.graphics_backend }
660
661    /// Check the status of the intial pipeline creation
662    /// Returns `None` if complete
663    /// Returns `Some((total, complete))` if in progress
664    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    /// Check the status the pipeline recreation
673    /// Returns `None` if pipelines are currently not being recreated
674    /// Returns `Some((total, complete))` if in progress
675    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    /// Change the render mode.
684    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            // Update present mode in swap chain descriptor if it is supported.
692            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            // Only enable profiling if the wgpu features are enabled
697            self.other_modes.profiler_enabled &= self.profiler_features_enabled;
698            // Enable/disable profiler
699            if !self.other_modes.profiler_enabled {
700                // Clear the times if disabled
701                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            // Recreate render target
712            self.on_resize(self.resolution);
713        }
714
715        // We can't cancel the pending recreation even if the new settings are equal
716        // to the current ones becuase the recreation could be triggered by something
717        // else like shader hotloading
718        if self.pipeline_modes != pipeline_modes
719            || self
720                .recreation_pending
721                .as_ref()
722                .is_some_and(|modes| modes != &pipeline_modes)
723        {
724            // Recreate pipelines with new modes
725            self.recreate_pipelines(pipeline_modes);
726        }
727
728        Ok(())
729    }
730
731    /// Get the pipelines mode.
732    pub fn pipeline_modes(&self) -> &PipelineModes { &self.pipeline_modes }
733
734    /// Get the supported present modes.
735    pub fn present_modes(&self) -> &[PresentMode] { &self.present_modes }
736
737    /// Get the current profiling times
738    /// Nested timings immediately follow their parent
739    /// Returns Vec<(how nested this timing is, label, length in seconds)>
740    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    /// Resize internal render targets to match window render target dimensions.
762    pub fn on_resize(&mut self, dims: Vec2<u32>) {
763        // Avoid panics when creating texture with w,h of 0,0.
764        if dims.x != 0 && dims.y != 0 {
765            self.is_minimized = false;
766            // Resize swap chain
767            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            // Resize other render targets
773            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            // Get mutable reference to shadow views out of the current state
815            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, // Should never hit this
838            };
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                // Recreate the shadow bind group if needed
881                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    /// Create render target views
914    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                // TODO: why is this not Color?
951                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        // TODO: skip creating bloom stuff when it is disabled
966        let bloom_sizes = [(); bloom::NUM_SIZES].map(|()| {
967            // .max(1) to ensure we don't create zero sized textures
968            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        // TODO: Consider no depth buffer for the final draw to the window?
1019        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    /// Get the resolution of the render target.
1045    pub fn resolution(&self) -> Vec2<u32> { self.resolution }
1046
1047    /// Get the internal resolution of the render target.
1048    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    /// Get the resolution of the shadow render target.
1055    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    /// Start recording the frame
1073    /// When the returned `Drawer` is dropped the recorded draw calls will be
1074    /// submitted to the queue
1075    /// If there is an intermittent issue with the swap chain then Ok(None) will
1076    /// be returned
1077    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        // Try to get the latest profiling results
1092        if self.other_modes.profiler_enabled {
1093            // Note: this lags a few frames behind
1094            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        // Handle polling background pipeline creation/recreation
1101        // Temporarily set to nothing and then replace in the statement below
1102        let state = core::mem::replace(&mut self.state, State::Nothing);
1103        // Indicator for if pipeline recreation finished and we need to recreate bind
1104        // groups / render targets (handling defered so that State will be valid
1105        // when calling Self::on_resize)
1106        let mut trigger_on_resize = false;
1107        // If still creating initial pipelines, check if complete
1108        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                // Not complete
1166                Err(creating) => State::Interface {
1167                    pipelines: interface,
1168                    shadow_views,
1169                    rain_occlusion_view,
1170                    creating,
1171                },
1172            }
1173        // If recreating the pipelines, check if that is complete
1174        } 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                    // TODO: we have the potential to skip recreating bindings / render targets on
1222                    // pipeline recreation trigged by shader reloading (would need to ensure new
1223                    // postprocess_layout is not created...)
1224                    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                // Not complete
1241                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        // Call on_resize to recreate render targets and their bind groups if the
1252        // pipelines were recreated with a new postprocess layout and or changes in the
1253        // render modes
1254        if trigger_on_resize {
1255            self.on_resize(self.resolution);
1256        }
1257
1258        // If the shaders files were changed attempt to recreate the shaders
1259        if self.shaders_watcher.reloaded() {
1260            self.recreate_pipelines(self.pipeline_modes.clone());
1261        }
1262
1263        // Or if we have a recreation pending
1264        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            // If lost recreate the swap chain
1284            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                // This will probably be resolved on the next frame
1291                // NOTE: we don't log this because it happens very frequently with
1292                // PresentMode::Fifo and unlimited FPS on certain machines
1293                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    /// Recreate the pipelines
1314    fn recreate_pipelines(&mut self, pipeline_modes: PipelineModes) {
1315        match &mut self.state {
1316            State::Complete { recreating, .. } if recreating.is_some() => {
1317                // Defer recreation so that we are not building multiple sets of pipelines in
1318                // the background at once
1319                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                        // NOTE: if present_mode starts to be used to configure pipelines then it
1333                        // needs to become a part of the pipeline modes
1334                        // (note here since the present mode is accessible
1335                        // through the swap chain descriptor)
1336                        self.surface_config.clone(), // Note: cheap clone
1337                        shadow.map.is_enabled(),
1338                        self.intermediate_format,
1339                    ),
1340                ));
1341            },
1342            State::Interface { .. } => {
1343                // Defer recreation so that we are not building multiple sets of pipelines in
1344                // the background at once
1345                self.recreation_pending = Some(pipeline_modes);
1346            },
1347            State::Nothing => {},
1348        }
1349    }
1350
1351    /// Create a new set of constants with the provided values.
1352    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    /// Update a set of constants with the provided values.
1367    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    /// Create a new set of instances with the provided values.
1380    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    /// Ensure that the quad index buffer is large enough for a quad vertex
1387    /// buffer with this many vertices
1388    pub(super) fn ensure_sufficient_index_length<V: Vertex>(
1389        &mut self,
1390        // Length of the vert buffer with 4 verts per quad
1391        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                // Make sure the global quad index buffer is large enough
1399                if self.quad_index_buffer_u16.len() < quad_index_length {
1400                    // Make sure we aren't over the max
1401                    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                // Make sure the global quad index buffer is large enough
1414                if self.quad_index_buffer_u32.len() < quad_index_length {
1415                    // Make sure we aren't over the max
1416                    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    /// Create a new model from the provided mesh.
1438    /// If the provided mesh is empty this returns None
1439    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    /// Create a new dynamic model with the specified size.
1445    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    /// Update a dynamic model with a mesh and a offset.
1451    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    /// Return the maximum supported texture size.
1456    pub fn max_texture_size(&self) -> u32 { self.max_texture_size }
1457
1458    /// Create a new immutable texture from the provided image.
1459    /// # Panics
1460    /// If the provided data doesn't completely fill the texture this function
1461    /// will panic.
1462    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    /// Create a new raw texture.
1495    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); // Needs to be fully initialized for partial writes to work on Dx12 AMD
1503        texture
1504    }
1505
1506    /// Create a new texture from the provided image.
1507    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    /// Create a new dynamic texture with the specified dimensions.
1523    ///
1524    /// Currently only supports Rgba8Srgb.
1525    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    /// Update a texture with the provided offset, size, and data.
1530    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    /// See docs on [`ui::BatchedUploads::submit`].
1541    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    /// Queue to obtain a screenshot on the next frame render
1560    pub fn create_screenshot(
1561        &mut self,
1562        screenshot_handler: impl FnOnce(Result<image::RgbImage, String>) + Send + 'static,
1563    ) {
1564        // Queue screenshot
1565        self.take_screenshot = Some(Box::new(screenshot_handler));
1566        // Take profiler snapshot
1567        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    // Consider reenabling at some time
1588    //
1589    // /// Queue the rendering of the player silhouette in the upcoming frame.
1590    // pub fn render_player_shadow(
1591    //     &mut self,
1592    //     _model: &figure::FigureModel,
1593    //     _col_lights: &Texture<ColLightFmt>,
1594    //     _global: &GlobalModel,
1595    //     _bones: &Consts<figure::BoneData>,
1596    //     _lod: &lod_terrain::LodData,
1597    //     _locals: &Consts<shadow::Locals>,
1598    // ) { // FIXME: Consider reenabling at some point. /* let (point_shadow_maps,
1599    //   directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { (
1600    //   ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), (
1601    //   shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), )
1602    //   } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
1603    //   (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; let
1604    //   model = &model.opaque;
1605
1606    //     self.encoder.draw(
1607    //         &gfx::Slice {
1608    //             start: model.vertex_range().start,
1609    //             end: model.vertex_range().end,
1610    //             base_vertex: 0,
1611    //             instances: None,
1612    //             buffer: gfx::IndexBuffer::Auto,
1613    //         },
1614    //         &self.player_shadow_pipeline.pso,
1615    //         &figure::pipe::Data {
1616    //             vbuf: model.vbuf.clone(),
1617    //             col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()),
1618    //             locals: locals.buf.clone(),
1619    //             globals: global.globals.buf.clone(),
1620    //             bones: bones.buf.clone(),
1621    //             lights: global.lights.buf.clone(),
1622    //             shadows: global.shadows.buf.clone(),
1623    //             light_shadows: global.shadow_mats.buf.clone(),
1624    //             point_shadow_maps,
1625    //             directed_shadow_maps,
1626    //             noise: (self.noise_tex.srv.clone(),
1627    // self.noise_tex.sampler.clone()),             alt: (lod.alt.srv.clone(),
1628    // lod.alt.sampler.clone()),             horizon: (lod.horizon.srv.clone(),
1629    // lod.horizon.sampler.clone()),             tgt_color:
1630    // self.tgt_color_view.clone(),             tgt_depth:
1631    // (self.tgt_depth_view.clone()/* , (0, 0) */),         },
1632    //     ); */
1633    // }
1634}
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
1664/// Terrain-related buffers segment themselves by depth to allow us to do
1665/// primitive occlusion culling based on whether the camera is underground or
1666/// not. This struct specifies the buffer offsets at which various layers start
1667/// and end.
1668///
1669/// 'Deep' structures appear within the range `0..deep_end`.
1670///
1671/// 'Shallow' structures appear within the range `deep_end..underground_end`.
1672///
1673/// 'Surface' structures appear within the range `underground_end..`.
1674pub struct AltIndices {
1675    pub deep_end: usize,
1676    pub underground_end: usize,
1677}
1678
1679/// The mode with which culling based on the camera position relative to the
1680/// terrain is performed.
1681#[derive(Copy, Clone, Default)]
1682pub enum CullingMode {
1683    /// We need to render all elements of the given structure
1684    #[default]
1685    None,
1686    /// We only need to render surface and shallow (i.e: in the overlapping
1687    /// region) elements of the structure
1688    Surface,
1689    /// We only need to render shallow (i.e: in the overlapping region) and deep
1690    /// elements of the structure
1691    Underground,
1692}