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