veloren_voxygen/render/renderer/
pipeline_creation.rs

1use crate::render::pipelines::rain_occlusion;
2
3use super::{
4    super::{
5        AaMode, BloomMode, CloudMode, ExperimentalShader, FluidMode, LightingMode, PipelineModes,
6        ReflectionMode, RenderError, ShadowMode,
7        pipelines::{
8            blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle,
9            postprocess, rope, shadow, skybox, sprite, terrain, trail, ui,
10        },
11    },
12    ImmutableLayouts, Layouts,
13    shaders::Shaders,
14};
15use common_base::{prof_span, prof_span_alloc};
16use std::sync::Arc;
17use tracing::info;
18
19/// All the pipelines
20pub struct Pipelines {
21    pub debug: debug::DebugPipeline,
22    pub figure: figure::FigurePipeline,
23    pub fluid: fluid::FluidPipeline,
24    pub lod_terrain: lod_terrain::LodTerrainPipeline,
25    pub particle: particle::ParticlePipeline,
26    pub rope: rope::RopePipeline,
27    pub trail: trail::TrailPipeline,
28    pub clouds: clouds::CloudsPipeline,
29    pub bloom: Option<bloom::BloomPipelines>,
30    pub postprocess: postprocess::PostProcessPipeline,
31    // Consider reenabling at some time
32    // player_shadow: figure::FigurePipeline,
33    pub skybox: skybox::SkyboxPipeline,
34    pub sprite: sprite::SpritePipeline,
35    pub lod_object: lod_object::LodObjectPipeline,
36    pub terrain: terrain::TerrainPipeline,
37    pub ui: ui::UiPipeline,
38    pub premultiply_alpha: ui::PremultiplyAlphaPipeline,
39    pub blit: blit::BlitPipeline,
40}
41
42/// Pipelines that are needed to render 3D stuff in-game
43/// Use to decouple interface pipeline creation when initializing the renderer
44pub struct IngamePipelines {
45    debug: debug::DebugPipeline,
46    figure: figure::FigurePipeline,
47    fluid: fluid::FluidPipeline,
48    lod_terrain: lod_terrain::LodTerrainPipeline,
49    particle: particle::ParticlePipeline,
50    rope: rope::RopePipeline,
51    trail: trail::TrailPipeline,
52    clouds: clouds::CloudsPipeline,
53    pub bloom: Option<bloom::BloomPipelines>,
54    postprocess: postprocess::PostProcessPipeline,
55    // Consider reenabling at some time
56    // player_shadow: figure::FigurePipeline,
57    skybox: skybox::SkyboxPipeline,
58    sprite: sprite::SpritePipeline,
59    lod_object: lod_object::LodObjectPipeline,
60    terrain: terrain::TerrainPipeline,
61}
62
63pub struct ShadowPipelines {
64    pub point: Option<shadow::PointShadowPipeline>,
65    pub directed: Option<shadow::ShadowPipeline>,
66    pub figure: Option<shadow::ShadowFigurePipeline>,
67    pub debug: Option<shadow::ShadowDebugPipeline>,
68}
69
70pub struct RainOcclusionPipelines {
71    pub terrain: Option<rain_occlusion::RainOcclusionPipeline>,
72    pub figure: Option<rain_occlusion::RainOcclusionFigurePipeline>,
73}
74
75// TODO: Find a better name for this?
76pub struct IngameAndShadowPipelines {
77    pub ingame: IngamePipelines,
78    pub shadow: ShadowPipelines,
79    pub rain_occlusion: RainOcclusionPipelines,
80}
81
82/// Pipelines neccesary to display the UI and take screenshots
83/// Use to decouple interface pipeline creation when initializing the renderer
84pub struct InterfacePipelines {
85    pub ui: ui::UiPipeline,
86    pub premultiply_alpha: ui::PremultiplyAlphaPipeline,
87    pub blit: blit::BlitPipeline,
88}
89
90impl Pipelines {
91    pub fn consolidate(interface: InterfacePipelines, ingame: IngamePipelines) -> Self {
92        Self {
93            debug: ingame.debug,
94            figure: ingame.figure,
95            fluid: ingame.fluid,
96            lod_terrain: ingame.lod_terrain,
97            particle: ingame.particle,
98            rope: ingame.rope,
99            trail: ingame.trail,
100            clouds: ingame.clouds,
101            bloom: ingame.bloom,
102            postprocess: ingame.postprocess,
103            //player_shadow: ingame.player_shadow,
104            skybox: ingame.skybox,
105            sprite: ingame.sprite,
106            lod_object: ingame.lod_object,
107            terrain: ingame.terrain,
108            ui: interface.ui,
109            premultiply_alpha: interface.premultiply_alpha,
110            blit: interface.blit,
111        }
112    }
113}
114
115/// Processed shaders ready for use in pipeline creation
116struct ShaderModules {
117    skybox_vert: wgpu::ShaderModule,
118    skybox_frag: wgpu::ShaderModule,
119    debug_vert: wgpu::ShaderModule,
120    debug_frag: wgpu::ShaderModule,
121    figure_vert: wgpu::ShaderModule,
122    figure_frag: wgpu::ShaderModule,
123    terrain_vert: wgpu::ShaderModule,
124    terrain_frag: wgpu::ShaderModule,
125    fluid_vert: wgpu::ShaderModule,
126    fluid_frag: wgpu::ShaderModule,
127    sprite_vert: wgpu::ShaderModule,
128    sprite_frag: wgpu::ShaderModule,
129    lod_object_vert: wgpu::ShaderModule,
130    lod_object_frag: wgpu::ShaderModule,
131    particle_vert: wgpu::ShaderModule,
132    particle_frag: wgpu::ShaderModule,
133    rope_vert: wgpu::ShaderModule,
134    rope_frag: wgpu::ShaderModule,
135    trail_vert: wgpu::ShaderModule,
136    trail_frag: wgpu::ShaderModule,
137    ui_vert: wgpu::ShaderModule,
138    ui_frag: wgpu::ShaderModule,
139    premultiply_alpha_vert: wgpu::ShaderModule,
140    premultiply_alpha_frag: wgpu::ShaderModule,
141    lod_terrain_vert: wgpu::ShaderModule,
142    lod_terrain_frag: wgpu::ShaderModule,
143    clouds_vert: wgpu::ShaderModule,
144    clouds_frag: wgpu::ShaderModule,
145    dual_downsample_filtered_frag: wgpu::ShaderModule,
146    dual_downsample_frag: wgpu::ShaderModule,
147    dual_upsample_frag: wgpu::ShaderModule,
148    postprocess_vert: wgpu::ShaderModule,
149    postprocess_frag: wgpu::ShaderModule,
150    blit_vert: wgpu::ShaderModule,
151    blit_frag: wgpu::ShaderModule,
152    point_light_shadows_vert: wgpu::ShaderModule,
153    light_shadows_directed_vert: wgpu::ShaderModule,
154    light_shadows_figure_vert: wgpu::ShaderModule,
155    light_shadows_debug_vert: wgpu::ShaderModule,
156    rain_occlusion_directed_vert: wgpu::ShaderModule,
157    rain_occlusion_figure_vert: wgpu::ShaderModule,
158}
159
160impl ShaderModules {
161    pub fn new(
162        device: &wgpu::Device,
163        shaders: &Shaders,
164        pipeline_modes: &PipelineModes,
165        has_shadow_views: bool,
166    ) -> Result<Self, RenderError> {
167        prof_span!(_guard, "ShaderModules::new");
168        use shaderc::{CompileOptions, Compiler, OptimizationLevel, ResolvedInclude, ShaderKind};
169
170        let constants = shaders.get("include.constants").unwrap();
171        let globals = shaders.get("include.globals").unwrap();
172        let sky = shaders.get("include.sky").unwrap();
173        let light = shaders.get("include.light").unwrap();
174        let srgb = shaders.get("include.srgb").unwrap();
175        let random = shaders.get("include.random").unwrap();
176        let lod = shaders.get("include.lod").unwrap();
177        let shadows = shaders.get("include.shadows").unwrap();
178        let rain_occlusion = shaders.get("include.rain_occlusion").unwrap();
179        let point_glow = shaders.get("include.point_glow").unwrap();
180        let fxaa = shaders.get("include.fxaa").unwrap();
181
182        // We dynamically add extra configuration settings to the constants file.
183        let mut constants = format!(
184            r#"
185{}
186
187#define VOXYGEN_COMPUTATION_PREFERENCE {}
188#define FLUID_MODE {}
189#define CLOUD_MODE {}
190#define REFLECTION_MODE {}
191#define LIGHTING_ALGORITHM {}
192#define SHADOW_MODE {}
193
194"#,
195            &constants.0,
196            // TODO: Configurable vertex/fragment shader preference.
197            "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT",
198            match pipeline_modes.fluid {
199                FluidMode::Low => "FLUID_MODE_LOW",
200                FluidMode::Medium => "FLUID_MODE_MEDIUM",
201                FluidMode::High => "FLUID_MODE_HIGH",
202            },
203            match pipeline_modes.cloud {
204                CloudMode::None => "CLOUD_MODE_NONE",
205                CloudMode::Minimal => "CLOUD_MODE_MINIMAL",
206                CloudMode::Low => "CLOUD_MODE_LOW",
207                CloudMode::Medium => "CLOUD_MODE_MEDIUM",
208                CloudMode::High => "CLOUD_MODE_HIGH",
209                CloudMode::Ultra => "CLOUD_MODE_ULTRA",
210            },
211            match pipeline_modes.reflection {
212                ReflectionMode::Low => "REFLECTION_MODE_LOW",
213                ReflectionMode::Medium => "REFLECTION_MODE_MEDIUM",
214                ReflectionMode::High => "REFLECTION_MODE_HIGH",
215            },
216            match pipeline_modes.lighting {
217                LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN",
218                LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG",
219                LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN",
220            },
221            match pipeline_modes.shadow {
222                ShadowMode::None => "SHADOW_MODE_NONE",
223                ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP",
224                ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP",
225            },
226        );
227
228        if pipeline_modes.point_glow > f32::EPSILON {
229            constants += &format!(
230                "\n#define POINT_GLOW_FACTOR {}\n",
231                pipeline_modes.point_glow
232            );
233        }
234
235        if pipeline_modes.flashing_lights_enabled {
236            constants += "#define FLASHING_LIGHTS_ENABLED\n";
237        }
238
239        if pipeline_modes.rain_enabled {
240            constants += "#define RAIN_ENABLED\n";
241        }
242
243        for shader in pipeline_modes.experimental_shaders.iter() {
244            constants += &format!(
245                "#define EXPERIMENTAL_{}\n",
246                format!("{:?}", shader).to_uppercase()
247            );
248        }
249
250        let constants = match pipeline_modes.bloom {
251            BloomMode::Off => constants,
252            BloomMode::On(config) => {
253                format!(
254                    r#"
255{}
256
257#define BLOOM_FACTOR {}
258#define BLOOM_UNIFORM_BLUR {}
259
260"#,
261                    constants,
262                    config.factor.fraction(),
263                    config.uniform_blur,
264                )
265            },
266        };
267
268        let anti_alias = shaders
269            .get(match pipeline_modes.aa {
270                AaMode::None => "antialias.none",
271                AaMode::Bilinear => "antialias.bilinear",
272                AaMode::Fxaa => "antialias.fxaa",
273                AaMode::MsaaX4 => "antialias.msaa-x4",
274                AaMode::MsaaX8 => "antialias.msaa-x8",
275                AaMode::MsaaX16 => "antialias.msaa-x16",
276                AaMode::Hqx => "antialias.hqx",
277                AaMode::FxUpscale => "antialias.fxupscale",
278            })
279            .unwrap();
280
281        let cloud = shaders
282            .get(match pipeline_modes.cloud {
283                CloudMode::None => "include.cloud.none",
284                _ => "include.cloud.regular",
285            })
286            .unwrap();
287
288        let mut compiler = Compiler::new().ok_or(RenderError::ErrorInitializingCompiler)?;
289        let mut options = CompileOptions::new().ok_or(RenderError::ErrorInitializingCompiler)?;
290        let shaderc_opts = !pipeline_modes
291            .experimental_shaders
292            .contains(&ExperimentalShader::DisableShadercOptimization);
293        if shaderc_opts {
294            options.set_optimization_level(OptimizationLevel::Performance);
295            info!("Enabled optimization by shaderc.");
296        } else {
297            options.set_optimization_level(OptimizationLevel::Zero);
298            info!("Disabled optimization by shaderc.");
299        }
300        options.set_forced_version_profile(430, shaderc::GlslProfile::Core);
301        // options.set_generate_debug_info();
302        options.set_include_callback(move |name, _, shader_name, _| {
303            Ok(ResolvedInclude {
304                resolved_name: name.to_string(),
305                content: match name {
306                    "constants.glsl" => constants.clone(),
307                    "globals.glsl" => globals.0.to_owned(),
308                    "shadows.glsl" => shadows.0.to_owned(),
309                    "rain_occlusion.glsl" => rain_occlusion.0.to_owned(),
310                    "sky.glsl" => sky.0.to_owned(),
311                    "light.glsl" => light.0.to_owned(),
312                    "srgb.glsl" => srgb.0.to_owned(),
313                    "random.glsl" => random.0.to_owned(),
314                    "lod.glsl" => lod.0.to_owned(),
315                    "anti-aliasing.glsl" => anti_alias.0.to_owned(),
316                    "cloud.glsl" => cloud.0.to_owned(),
317                    "point_glow.glsl" => point_glow.0.to_owned(),
318                    "fxaa.glsl" => fxaa.0.to_owned(),
319                    other => {
320                        return Err(format!(
321                            "Include {} in {} is not defined",
322                            other, shader_name
323                        ));
324                    },
325                },
326            })
327        });
328
329        let mut create_shader = |name, kind| {
330            let glsl = &shaders
331                .get(name)
332                .unwrap_or_else(|| panic!("Can't retrieve shader: {}", name))
333                .0;
334            let file_name = format!("{}.glsl", name);
335            create_shader_module(device, &mut compiler, glsl, kind, &file_name, &options)
336        };
337
338        let selected_fluid_shader = ["fluid-frag.", match pipeline_modes.fluid {
339            FluidMode::Low => "cheap",
340            _ => "shiny",
341        }]
342        .concat();
343
344        Ok(Self {
345            skybox_vert: create_shader("skybox-vert", ShaderKind::Vertex)?,
346            skybox_frag: create_shader("skybox-frag", ShaderKind::Fragment)?,
347            debug_vert: create_shader("debug-vert", ShaderKind::Vertex)?,
348            debug_frag: create_shader("debug-frag", ShaderKind::Fragment)?,
349            figure_vert: create_shader("figure-vert", ShaderKind::Vertex)?,
350            figure_frag: create_shader("figure-frag", ShaderKind::Fragment)?,
351            terrain_vert: create_shader("terrain-vert", ShaderKind::Vertex)?,
352            terrain_frag: create_shader("terrain-frag", ShaderKind::Fragment)?,
353            fluid_vert: create_shader("fluid-vert", ShaderKind::Vertex)?,
354            fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?,
355            sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?,
356            sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?,
357            lod_object_vert: create_shader("lod-object-vert", ShaderKind::Vertex)?,
358            lod_object_frag: create_shader("lod-object-frag", ShaderKind::Fragment)?,
359            particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
360            particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
361            rope_vert: create_shader("rope-vert", ShaderKind::Vertex)?,
362            rope_frag: create_shader("rope-frag", ShaderKind::Fragment)?,
363            trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
364            trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?,
365            ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?,
366            ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?,
367            premultiply_alpha_vert: create_shader("premultiply-alpha-vert", ShaderKind::Vertex)?,
368            premultiply_alpha_frag: create_shader("premultiply-alpha-frag", ShaderKind::Fragment)?,
369            lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?,
370            lod_terrain_frag: create_shader("lod-terrain-frag", ShaderKind::Fragment)?,
371            clouds_vert: create_shader("clouds-vert", ShaderKind::Vertex)?,
372            clouds_frag: create_shader("clouds-frag", ShaderKind::Fragment)?,
373            dual_downsample_filtered_frag: create_shader(
374                "dual-downsample-filtered-frag",
375                ShaderKind::Fragment,
376            )?,
377            dual_downsample_frag: create_shader("dual-downsample-frag", ShaderKind::Fragment)?,
378            dual_upsample_frag: create_shader("dual-upsample-frag", ShaderKind::Fragment)?,
379            postprocess_vert: create_shader("postprocess-vert", ShaderKind::Vertex)?,
380            postprocess_frag: create_shader("postprocess-frag", ShaderKind::Fragment)?,
381            blit_vert: create_shader("blit-vert", ShaderKind::Vertex)?,
382            blit_frag: create_shader("blit-frag", ShaderKind::Fragment)?,
383            point_light_shadows_vert: create_shader(
384                "point-light-shadows-vert",
385                ShaderKind::Vertex,
386            )?,
387            light_shadows_directed_vert: create_shader(
388                "light-shadows-directed-vert",
389                ShaderKind::Vertex,
390            )?,
391            light_shadows_figure_vert: create_shader(
392                "light-shadows-figure-vert",
393                ShaderKind::Vertex,
394            )?,
395            light_shadows_debug_vert: create_shader(
396                "light-shadows-debug-vert",
397                ShaderKind::Vertex,
398            )?,
399            rain_occlusion_directed_vert: create_shader(
400                "rain-occlusion-directed-vert",
401                ShaderKind::Vertex,
402            )?,
403            rain_occlusion_figure_vert: create_shader(
404                "rain-occlusion-figure-vert",
405                ShaderKind::Vertex,
406            )?,
407        })
408    }
409}
410
411fn create_shader_module(
412    device: &wgpu::Device,
413    compiler: &mut shaderc::Compiler,
414    source: &str,
415    kind: shaderc::ShaderKind,
416    file_name: &str,
417    options: &shaderc::CompileOptions,
418) -> Result<wgpu::ShaderModule, RenderError> {
419    prof_span!(_guard, "create_shader_modules");
420    use std::borrow::Cow;
421
422    let spv = compiler
423        .compile_into_spirv(source, kind, file_name, "main", Some(options))
424        .map_err(|e| (file_name, e))?;
425
426    // Uncomment me to dump shaders to files
427    //
428    // std::fs::create_dir_all("dumpped-shaders").expect("Couldn't create shader
429    // dumps folders");
430    //
431    // let mut file = std::fs::File::create(format!("dumpped-shaders/{}.spv",
432    // file_name))     .expect("Couldn't open shader out");
433    //
434    // use std::io::Write;
435    //
436    // file.write(spv.as_binary_u8())
437    //     .expect("Couldn't write shader out");
438
439    // let label = [file_name, "\n\n", source].concat();
440    #[expect(unsafe_code)]
441    Ok(unsafe {
442        device.create_shader_module_unchecked(wgpu::ShaderModuleDescriptor {
443            label: Some(file_name),
444            source: wgpu::ShaderSource::SpirV(Cow::Borrowed(spv.as_binary())),
445        })
446    })
447}
448
449/// Things needed to create a pipeline
450#[derive(Clone, Copy)]
451struct PipelineNeeds<'a> {
452    device: &'a wgpu::Device,
453    layouts: &'a Layouts,
454    shaders: &'a ShaderModules,
455    pipeline_modes: &'a PipelineModes,
456    surface_config: &'a wgpu::SurfaceConfiguration,
457}
458
459/// Creates InterfacePipelines in parallel
460fn create_interface_pipelines(
461    needs: PipelineNeeds,
462    pool: &rayon::ThreadPool,
463    tasks: [Task; 3],
464) -> InterfacePipelines {
465    prof_span!(_guard, "create_interface_pipelines");
466
467    let [ui_task, premultiply_alpha_task, blit_task] = tasks;
468    // Construct a pipeline for rendering UI elements
469    let create_ui = || {
470        ui_task.run(
471            || {
472                ui::UiPipeline::new(
473                    needs.device,
474                    &needs.shaders.ui_vert,
475                    &needs.shaders.ui_frag,
476                    needs.surface_config,
477                    &needs.layouts.global,
478                    &needs.layouts.ui,
479                )
480            },
481            "ui pipeline creation",
482        )
483    };
484
485    let create_premultiply_alpha = || {
486        premultiply_alpha_task.run(
487            || {
488                ui::PremultiplyAlphaPipeline::new(
489                    needs.device,
490                    &needs.shaders.premultiply_alpha_vert,
491                    &needs.shaders.premultiply_alpha_frag,
492                    &needs.layouts.premultiply_alpha,
493                )
494            },
495            "premultiply alpha pipeline creation",
496        )
497    };
498
499    // Construct a pipeline for blitting, used during screenshotting
500    let create_blit = || {
501        blit_task.run(
502            || {
503                blit::BlitPipeline::new(
504                    needs.device,
505                    &needs.shaders.blit_vert,
506                    &needs.shaders.blit_frag,
507                    needs.surface_config,
508                    &needs.layouts.blit,
509                )
510            },
511            "blit pipeline creation",
512        )
513    };
514
515    let (ui, (premultiply_alpha, blit)) = pool.join(create_ui, || {
516        pool.join(create_premultiply_alpha, create_blit)
517    });
518
519    InterfacePipelines {
520        ui,
521        premultiply_alpha,
522        blit,
523    }
524}
525
526/// Create IngamePipelines and shadow pipelines in parallel
527fn create_ingame_and_shadow_pipelines(
528    needs: PipelineNeeds,
529    pool: &rayon::ThreadPool,
530    // TODO: Reduce the boilerplate in this file
531    tasks: [Task; 20],
532    format: wgpu::TextureFormat,
533) -> IngameAndShadowPipelines {
534    prof_span!(_guard, "create_ingame_and_shadow_pipelines");
535
536    let PipelineNeeds {
537        device,
538        layouts,
539        shaders,
540        pipeline_modes,
541        surface_config: sc_desc,
542    } = needs;
543
544    let [
545        debug_task,
546        skybox_task,
547        figure_task,
548        terrain_task,
549        fluid_task,
550        sprite_task,
551        lod_object_task,
552        particle_task,
553        rope_task,
554        trail_task,
555        lod_terrain_task,
556        clouds_task,
557        bloom_task,
558        postprocess_task,
559        // TODO: if these are ever actually optionally done, counting them
560        // as tasks to do beforehand seems kind of iffy since they will just
561        // be skipped
562        point_shadow_task,
563        terrain_directed_shadow_task,
564        figure_directed_shadow_task,
565        debug_directed_shadow_task,
566        terrain_directed_rain_occlusion_task,
567        figure_directed_rain_occlusion_task,
568    ] = tasks;
569
570    // TODO: pass in format of target color buffer
571
572    // Pipeline for rendering debug shapes
573    let create_debug = || {
574        debug_task.run(
575            || {
576                debug::DebugPipeline::new(
577                    device,
578                    &shaders.debug_vert,
579                    &shaders.debug_frag,
580                    &layouts.global,
581                    &layouts.debug,
582                    pipeline_modes.aa,
583                    format,
584                )
585            },
586            "debug pipeline creation",
587        )
588    };
589    // Pipeline for rendering skyboxes
590    let create_skybox = || {
591        skybox_task.run(
592            || {
593                skybox::SkyboxPipeline::new(
594                    device,
595                    &shaders.skybox_vert,
596                    &shaders.skybox_frag,
597                    &layouts.global,
598                    pipeline_modes.aa,
599                    format,
600                )
601            },
602            "skybox pipeline creation",
603        )
604    };
605    // Pipeline for rendering figures
606    let create_figure = || {
607        figure_task.run(
608            || {
609                figure::FigurePipeline::new(
610                    device,
611                    &shaders.figure_vert,
612                    &shaders.figure_frag,
613                    &layouts.global,
614                    &layouts.figure,
615                    pipeline_modes.aa,
616                    format,
617                )
618            },
619            "figure pipeline creation",
620        )
621    };
622    // Pipeline for rendering terrain
623    let create_terrain = || {
624        terrain_task.run(
625            || {
626                terrain::TerrainPipeline::new(
627                    device,
628                    &shaders.terrain_vert,
629                    &shaders.terrain_frag,
630                    &layouts.global,
631                    &layouts.terrain,
632                    pipeline_modes.aa,
633                    format,
634                )
635            },
636            "terrain pipeline creation",
637        )
638    };
639    // Pipeline for rendering fluids
640    let create_fluid = || {
641        fluid_task.run(
642            || {
643                fluid::FluidPipeline::new(
644                    device,
645                    &shaders.fluid_vert,
646                    &shaders.fluid_frag,
647                    &layouts.global,
648                    &layouts.terrain,
649                    pipeline_modes.aa,
650                    format,
651                )
652            },
653            "fluid pipeline creation",
654        )
655    };
656    // Pipeline for rendering sprites
657    let create_sprite = || {
658        sprite_task.run(
659            || {
660                sprite::SpritePipeline::new(
661                    device,
662                    &shaders.sprite_vert,
663                    &shaders.sprite_frag,
664                    &layouts.global,
665                    &layouts.sprite,
666                    &layouts.terrain,
667                    pipeline_modes.aa,
668                    format,
669                )
670            },
671            "sprite pipeline creation",
672        )
673    };
674    // Pipeline for rendering lod objects
675    let create_lod_object = || {
676        lod_object_task.run(
677            || {
678                lod_object::LodObjectPipeline::new(
679                    device,
680                    &shaders.lod_object_vert,
681                    &shaders.lod_object_frag,
682                    &layouts.global,
683                    pipeline_modes.aa,
684                    format,
685                )
686            },
687            "lod object pipeline creation",
688        )
689    };
690    // Pipeline for rendering particles
691    let create_particle = || {
692        particle_task.run(
693            || {
694                particle::ParticlePipeline::new(
695                    device,
696                    &shaders.particle_vert,
697                    &shaders.particle_frag,
698                    &layouts.global,
699                    pipeline_modes.aa,
700                    format,
701                )
702            },
703            "particle pipeline creation",
704        )
705    };
706    // Pipeline for rendering ropes
707    let create_rope = || {
708        rope_task.run(
709            || {
710                rope::RopePipeline::new(
711                    device,
712                    &shaders.rope_vert,
713                    &shaders.rope_frag,
714                    &layouts.global,
715                    &layouts.rope,
716                    pipeline_modes.aa,
717                    format,
718                )
719            },
720            "rope pipeline creation",
721        )
722    };
723    // Pipeline for rendering weapon trails
724    let create_trail = || {
725        trail_task.run(
726            || {
727                trail::TrailPipeline::new(
728                    device,
729                    &shaders.trail_vert,
730                    &shaders.trail_frag,
731                    &layouts.global,
732                    pipeline_modes.aa,
733                    format,
734                )
735            },
736            "trail pipeline creation",
737        )
738    };
739    // Pipeline for rendering terrain
740    let create_lod_terrain = || {
741        lod_terrain_task.run(
742            || {
743                lod_terrain::LodTerrainPipeline::new(
744                    device,
745                    &shaders.lod_terrain_vert,
746                    &shaders.lod_terrain_frag,
747                    &layouts.global,
748                    pipeline_modes.aa,
749                    format,
750                )
751            },
752            "lod terrain pipeline creation",
753        )
754    };
755    // Pipeline for rendering our clouds (a kind of post-processing)
756    let create_clouds = || {
757        clouds_task.run(
758            || {
759                clouds::CloudsPipeline::new(
760                    device,
761                    &shaders.clouds_vert,
762                    &shaders.clouds_frag,
763                    &layouts.global,
764                    &layouts.clouds,
765                    pipeline_modes.aa,
766                    format,
767                )
768            },
769            "clouds pipeline creation",
770        )
771    };
772    // Pipelines for rendering our bloom
773    let create_bloom = || {
774        bloom_task.run(
775            || {
776                match &pipeline_modes.bloom {
777                    BloomMode::Off => None,
778                    BloomMode::On(config) => Some(config),
779                }
780                .map(|bloom_config| {
781                    bloom::BloomPipelines::new(
782                        device,
783                        &shaders.blit_vert,
784                        &shaders.dual_downsample_filtered_frag,
785                        &shaders.dual_downsample_frag,
786                        &shaders.dual_upsample_frag,
787                        format,
788                        &layouts.bloom,
789                        bloom_config,
790                    )
791                })
792            },
793            "bloom pipelines creation",
794        )
795    };
796    // Pipeline for rendering our post-processing
797    let create_postprocess = || {
798        postprocess_task.run(
799            || {
800                postprocess::PostProcessPipeline::new(
801                    device,
802                    &shaders.postprocess_vert,
803                    &shaders.postprocess_frag,
804                    sc_desc,
805                    &layouts.global,
806                    &layouts.postprocess,
807                )
808            },
809            "postprocess pipeline creation",
810        )
811    };
812
813    //
814    // // Pipeline for rendering the player silhouette
815    // let player_shadow_pipeline = create_pipeline(
816    //     factory,
817    //     figure::pipe::Init {
818    //         tgt_depth: (gfx::preset::depth::PASS_TEST/*,
819    //         Stencil::new(
820    //             Comparison::Equal,
821    //             0xff,
822    //             (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep),
823    //         ),*/),
824    //         ..figure::pipe::new()
825    //     },
826    //     &figure_vert,
827    //     &Glsl::load_watched(
828    //         "voxygen.shaders.player-shadow-frag",
829    //         shader_reload_indicator,
830    //     )
831    //     .unwrap(),
832    //     &include_ctx,
833    //     gfx::state::CullFace::Back,
834    // )?;
835
836    // Pipeline for rendering point light terrain shadow maps.
837    let create_point_shadow = || {
838        point_shadow_task.run(
839            || {
840                shadow::PointShadowPipeline::new(
841                    device,
842                    &shaders.point_light_shadows_vert,
843                    &layouts.global,
844                    &layouts.terrain,
845                    pipeline_modes.aa,
846                )
847            },
848            "point shadow pipeline creation",
849        )
850    };
851    // Pipeline for rendering directional light terrain shadow maps.
852    let create_terrain_directed_shadow = || {
853        terrain_directed_shadow_task.run(
854            || {
855                shadow::ShadowPipeline::new(
856                    device,
857                    &shaders.light_shadows_directed_vert,
858                    &layouts.global,
859                    &layouts.terrain,
860                    pipeline_modes.aa,
861                )
862            },
863            "terrain directed shadow pipeline creation",
864        )
865    };
866    // Pipeline for rendering directional light figure shadow maps.
867    let create_figure_directed_shadow = || {
868        figure_directed_shadow_task.run(
869            || {
870                shadow::ShadowFigurePipeline::new(
871                    device,
872                    &shaders.light_shadows_figure_vert,
873                    &layouts.global,
874                    &layouts.figure,
875                    pipeline_modes.aa,
876                )
877            },
878            "figure directed shadow pipeline creation",
879        )
880    };
881    // Pipeline for rendering directional light debug shadow maps.
882    let create_debug_directed_shadow = || {
883        debug_directed_shadow_task.run(
884            || {
885                shadow::ShadowDebugPipeline::new(
886                    device,
887                    &shaders.light_shadows_debug_vert,
888                    &layouts.global,
889                    &layouts.debug,
890                    pipeline_modes.aa,
891                )
892            },
893            "figure directed shadow pipeline creation",
894        )
895    };
896    // Pipeline for rendering directional light terrain rain occlusion maps.
897    let create_terrain_directed_rain_occlusion = || {
898        terrain_directed_rain_occlusion_task.run(
899            || {
900                rain_occlusion::RainOcclusionPipeline::new(
901                    device,
902                    &shaders.rain_occlusion_directed_vert,
903                    &layouts.global,
904                    &layouts.terrain,
905                    pipeline_modes.aa,
906                )
907            },
908            "terrain directed rain occlusion pipeline creation",
909        )
910    };
911    // Pipeline for rendering directional light figure rain occlusion maps.
912    let create_figure_directed_rain_occlusion = || {
913        figure_directed_rain_occlusion_task.run(
914            || {
915                rain_occlusion::RainOcclusionFigurePipeline::new(
916                    device,
917                    &shaders.rain_occlusion_figure_vert,
918                    &layouts.global,
919                    &layouts.figure,
920                    pipeline_modes.aa,
921                )
922            },
923            "figure directed rain occlusion pipeline creation",
924        )
925    };
926
927    let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure));
928    let j2 = || pool.join(create_terrain, || pool.join(create_fluid, create_bloom));
929    let j3 = || pool.join(create_sprite, create_particle);
930    let j4 = || {
931        pool.join(create_lod_terrain, || {
932            pool.join(create_clouds, create_trail)
933        })
934    };
935    let j5 = || pool.join(create_postprocess, create_point_shadow);
936    let j6 = || {
937        pool.join(create_terrain_directed_shadow, || {
938            pool.join(create_figure_directed_shadow, create_debug_directed_shadow)
939        })
940    };
941    let j7 = || {
942        pool.join(create_lod_object, || {
943            pool.join(
944                create_terrain_directed_rain_occlusion,
945                create_figure_directed_rain_occlusion,
946            )
947        })
948    };
949    let j8 = create_rope;
950
951    // Ignore this
952    let (
953        (
954            ((debug, (skybox, figure)), (terrain, (fluid, bloom))),
955            ((sprite, particle), (lod_terrain, (clouds, trail))),
956        ),
957        (
958            (
959                (postprocess, point_shadow),
960                (terrain_directed_shadow, (figure_directed_shadow, debug_directed_shadow)),
961            ),
962            ((lod_object, (terrain_directed_rain_occlusion, figure_directed_rain_occlusion)), rope),
963        ),
964    ) = pool.join(
965        || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
966        || pool.join(|| pool.join(j5, j6), || pool.join(j7, j8)),
967    );
968
969    IngameAndShadowPipelines {
970        ingame: IngamePipelines {
971            debug,
972            figure,
973            fluid,
974            lod_terrain,
975            particle,
976            rope,
977            trail,
978            clouds,
979            bloom,
980            postprocess,
981            skybox,
982            sprite,
983            lod_object,
984            terrain,
985            // player_shadow_pipeline,
986        },
987        // TODO: skip creating these if the shadow map setting is not enabled
988        shadow: ShadowPipelines {
989            point: Some(point_shadow),
990            directed: Some(terrain_directed_shadow),
991            figure: Some(figure_directed_shadow),
992            debug: Some(debug_directed_shadow),
993        },
994        rain_occlusion: RainOcclusionPipelines {
995            terrain: Some(terrain_directed_rain_occlusion),
996            figure: Some(figure_directed_rain_occlusion),
997        },
998    }
999}
1000
1001/// Creates all the pipelines used to render.
1002/// Use this for the initial creation.
1003/// It blocks the main thread to create the interface pipelines while moving the
1004/// creation of other pipelines into the background
1005/// NOTE: this tries to use all the CPU cores to complete as soon as possible
1006pub(super) fn initial_create_pipelines(
1007    device: Arc<wgpu::Device>,
1008    layouts: Layouts,
1009    shaders: Shaders,
1010    pipeline_modes: PipelineModes,
1011    surface_config: wgpu::SurfaceConfiguration,
1012    has_shadow_views: bool,
1013    intermediate_format: wgpu::TextureFormat,
1014) -> Result<
1015    (
1016        InterfacePipelines,
1017        PipelineCreation<IngameAndShadowPipelines>,
1018    ),
1019    RenderError,
1020> {
1021    prof_span!(_guard, "initial_create_pipelines");
1022
1023    // Process shaders into modules
1024    let shader_modules = ShaderModules::new(&device, &shaders, &pipeline_modes, has_shadow_views)?;
1025
1026    // Create threadpool for parallel portion
1027    let pool = rayon::ThreadPoolBuilder::new()
1028        .thread_name(|n| format!("pipeline-creation-{}", n))
1029        .build()
1030        .unwrap();
1031
1032    let needs = PipelineNeeds {
1033        device: &device,
1034        layouts: &layouts,
1035        shaders: &shader_modules,
1036        pipeline_modes: &pipeline_modes,
1037        surface_config: &surface_config,
1038    };
1039
1040    // Create interface pipelines while blocking the main thread
1041    // Note: we use a throwaway Progress tracker here since we don't need to track
1042    // the progress
1043    let interface_pipelines =
1044        create_interface_pipelines(needs, &pool, Progress::new().create_tasks());
1045
1046    let pool = Arc::new(pool);
1047    let send_pool = Arc::clone(&pool);
1048    // Track pipeline creation progress
1049    let progress = Arc::new(Progress::new());
1050    let (pipeline_send, pipeline_recv) = crossbeam_channel::bounded(0);
1051    let pipeline_creation = PipelineCreation {
1052        progress: Arc::clone(&progress),
1053        recv: pipeline_recv,
1054    };
1055    // Start background compilation
1056    pool.spawn(move || {
1057        let pool = &*send_pool;
1058
1059        let needs = PipelineNeeds {
1060            device: &device,
1061            layouts: &layouts,
1062            shaders: &shader_modules,
1063            pipeline_modes: &pipeline_modes,
1064            surface_config: &surface_config,
1065        };
1066
1067        let pipelines = create_ingame_and_shadow_pipelines(
1068            needs,
1069            pool,
1070            progress.create_tasks(),
1071            intermediate_format,
1072        );
1073
1074        pipeline_send.send(pipelines).expect("Channel disconnected");
1075    });
1076
1077    Ok((interface_pipelines, pipeline_creation))
1078}
1079
1080/// Creates all the pipelines used to render.
1081/// Use this to recreate all the pipelines in the background.
1082/// TODO: report progress
1083/// NOTE: this tries to use all the CPU cores to complete as soon as possible
1084pub(super) fn recreate_pipelines(
1085    device: Arc<wgpu::Device>,
1086    immutable_layouts: Arc<ImmutableLayouts>,
1087    shaders: Shaders,
1088    pipeline_modes: PipelineModes,
1089    surface_config: wgpu::SurfaceConfiguration,
1090    has_shadow_views: bool,
1091    intermediate_format: wgpu::TextureFormat,
1092) -> PipelineCreation<
1093    Result<
1094        (
1095            Pipelines,
1096            ShadowPipelines,
1097            RainOcclusionPipelines,
1098            Arc<postprocess::PostProcessLayout>,
1099        ),
1100        RenderError,
1101    >,
1102> {
1103    prof_span!(_guard, "recreate_pipelines");
1104
1105    // Create threadpool for parallel portion
1106    let pool = rayon::ThreadPoolBuilder::new()
1107        .thread_name(|n| format!("pipeline-recreation-{}", n))
1108        .build()
1109        .unwrap();
1110    let pool = Arc::new(pool);
1111    let send_pool = Arc::clone(&pool);
1112    // Track pipeline creation progress
1113    let progress = Arc::new(Progress::new());
1114    let (result_send, result_recv) = crossbeam_channel::bounded(0);
1115    let pipeline_creation = PipelineCreation {
1116        progress: Arc::clone(&progress),
1117        recv: result_recv,
1118    };
1119    // Start background compilation
1120    pool.spawn(move || {
1121        let pool = &*send_pool;
1122
1123        // Create tasks upfront so the total counter will be accurate
1124        let shader_task = progress.create_task();
1125        let interface_tasks = progress.create_tasks();
1126        let ingame_and_shadow_tasks = progress.create_tasks();
1127
1128        // Process shaders into modules
1129        let guard = shader_task.start("process shaders");
1130        let shader_modules =
1131            match ShaderModules::new(&device, &shaders, &pipeline_modes, has_shadow_views) {
1132                Ok(modules) => modules,
1133                Err(err) => {
1134                    result_send.send(Err(err)).expect("Channel disconnected");
1135                    return;
1136                },
1137            };
1138        drop(guard);
1139
1140        // Create new postprocess layouts
1141        let postprocess_layouts = Arc::new(postprocess::PostProcessLayout::new(
1142            &device,
1143            &pipeline_modes,
1144        ));
1145
1146        let layouts = Layouts {
1147            immutable: immutable_layouts,
1148            postprocess: postprocess_layouts,
1149        };
1150
1151        let needs = PipelineNeeds {
1152            device: &device,
1153            layouts: &layouts,
1154            shaders: &shader_modules,
1155            pipeline_modes: &pipeline_modes,
1156            surface_config: &surface_config,
1157        };
1158
1159        // Create interface pipelines
1160        let interface = create_interface_pipelines(needs, pool, interface_tasks);
1161
1162        // Create the rest of the pipelines
1163        let IngameAndShadowPipelines {
1164            ingame,
1165            shadow,
1166            rain_occlusion,
1167        } = create_ingame_and_shadow_pipelines(
1168            needs,
1169            pool,
1170            ingame_and_shadow_tasks,
1171            intermediate_format,
1172        );
1173
1174        // Send them
1175        result_send
1176            .send(Ok((
1177                Pipelines::consolidate(interface, ingame),
1178                shadow,
1179                rain_occlusion,
1180                layouts.postprocess,
1181            )))
1182            .expect("Channel disconnected");
1183    });
1184
1185    pipeline_creation
1186}
1187
1188use core::sync::atomic::{AtomicUsize, Ordering};
1189
1190/// Represents future task that has not been started
1191/// Dropping this will mark the task as complete though
1192struct Task<'a> {
1193    progress: &'a Progress,
1194}
1195
1196/// Represents in-progress task, drop when complete
1197// NOTE: fields are unused because they are only used for their Drop impls
1198struct StartedTask<'a> {
1199    _span: common_base::ProfSpan,
1200    _task: Task<'a>,
1201}
1202
1203#[derive(Default)]
1204struct Progress {
1205    total: AtomicUsize,
1206    complete: AtomicUsize,
1207    // Note: could easily add a "started counter" if that would be useful
1208}
1209
1210impl Progress {
1211    pub fn new() -> Self { Self::default() }
1212
1213    /// Creates a task incrementing the total number of tasks
1214    /// NOTE: all tasks should be created as upfront as possible so that the
1215    /// total reflects the amount of tasks that will need to be completed
1216    pub fn create_task(&self) -> Task {
1217        self.total.fetch_add(1, Ordering::Relaxed);
1218        Task { progress: self }
1219    }
1220
1221    /// Helper method for creating tasks to do in bulk
1222    pub fn create_tasks<const N: usize>(&self) -> [Task; N] { [(); N].map(|()| self.create_task()) }
1223}
1224
1225impl<'a> Task<'a> {
1226    /// Start a task.
1227    /// The name is used for profiling.
1228    fn start(self, _name: &str) -> StartedTask<'a> {
1229        // _name only used when tracy feature is activated
1230        StartedTask {
1231            _span: {
1232                prof_span_alloc!(guard, _name);
1233                guard
1234            },
1235            _task: self,
1236        }
1237    }
1238
1239    /// Convenience function to run the provided closure as the task
1240    /// Completing the task when this function returns
1241    fn run<T>(self, task: impl FnOnce() -> T, name: &str) -> T {
1242        let _guard = self.start(name);
1243        task()
1244    }
1245}
1246
1247impl Drop for Task<'_> {
1248    fn drop(&mut self) { self.progress.complete.fetch_add(1, Ordering::Relaxed); }
1249}
1250
1251pub struct PipelineCreation<T> {
1252    progress: Arc<Progress>,
1253    recv: crossbeam_channel::Receiver<T>,
1254}
1255
1256impl<T> PipelineCreation<T> {
1257    /// Returns the number of pipelines being built and completed
1258    /// (total, complete)
1259    /// NOTE: there is no guarantee that `total >= complete` due to relaxed
1260    /// atomics but this property should hold most of the time
1261    pub fn status(&self) -> (usize, usize) {
1262        let progress = &*self.progress;
1263        (
1264            progress.total.load(Ordering::Relaxed),
1265            progress.complete.load(Ordering::Relaxed),
1266        )
1267    }
1268
1269    /// Checks if the pipelines were completed and returns the result if they
1270    /// were
1271    pub fn try_complete(self) -> Result<T, Self> {
1272        use crossbeam_channel::TryRecvError;
1273        match self.recv.try_recv() {
1274            // Yay!
1275            Ok(t) => Ok(t),
1276            // Normal error, we have not gotten anything yet
1277            Err(TryRecvError::Empty) => Err(self),
1278            // How rude!
1279            Err(TryRecvError::Disconnected) => {
1280                panic!(
1281                    "Background thread panicked or dropped the sender without sending anything!"
1282                );
1283            },
1284        }
1285    }
1286}