Skip to main content

veloren_voxygen/render/renderer/
pipeline_creation.rs

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