rend3_routine/
skybox.rs

1//! Routine that renders a cubemap as a skybox.
2
3use std::borrow::Cow;
4
5use rend3::{
6    graph::{
7        DataHandle, DepthHandle, RenderGraph, RenderPassDepthTarget, RenderPassTarget, RenderPassTargets,
8        RenderTargetHandle,
9    },
10    types::{SampleCount, TextureHandle},
11    util::bind_merge::{BindGroupBuilder, BindGroupLayoutBuilder},
12    Renderer,
13};
14use wgpu::{
15    BindGroup, BindGroupLayout, BindingType, Color, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
16    DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineLayoutDescriptor, PolygonMode,
17    PrimitiveState, PrimitiveTopology, RenderPipeline, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource,
18    ShaderStages, StencilState, TextureFormat, TextureSampleType, TextureViewDimension, VertexState,
19};
20
21use crate::{common::WholeFrameInterfaces, shaders::WGSL_SHADERS};
22
23struct StoredSkybox {
24    bg: Option<BindGroup>,
25    handle: Option<TextureHandle>,
26}
27
28/// Skybox rendering routine.
29///
30/// See module for documentation.
31pub struct SkyboxRoutine {
32    pipelines: SkyboxPipelines,
33    bgl: BindGroupLayout,
34    current_skybox: StoredSkybox,
35}
36
37impl SkyboxRoutine {
38    /// Create the routine.
39    pub fn new(renderer: &Renderer, interfaces: &WholeFrameInterfaces) -> Self {
40        let bgl = BindGroupLayoutBuilder::new()
41            .append(
42                ShaderStages::FRAGMENT,
43                BindingType::Texture {
44                    sample_type: TextureSampleType::Float { filterable: true },
45                    view_dimension: TextureViewDimension::Cube,
46                    multisampled: false,
47                },
48                None,
49            )
50            .build(&renderer.device, Some("skybox bgl"));
51
52        let pipelines = SkyboxPipelines::new(renderer, interfaces, &bgl);
53
54        Self {
55            current_skybox: StoredSkybox { bg: None, handle: None },
56            bgl,
57            pipelines,
58        }
59    }
60
61    /// Set the current background texture. Bad things will happen if this isn't
62    /// a cube texture.
63    pub fn set_background_texture(&mut self, texture: Option<TextureHandle>) {
64        self.current_skybox.handle = texture;
65        self.current_skybox.bg = None;
66    }
67
68    /// Update data if the background has changed since last frame.
69    pub fn ready(&mut self, renderer: &Renderer) {
70        let data_core = renderer.data_core.lock();
71        let d2c_texture_manager = &data_core.d2c_texture_manager;
72
73        profiling::scope!("Update Skybox");
74
75        if let Some(ref handle) = self.current_skybox.handle {
76            if self.current_skybox.bg.is_none() {
77                let bg = BindGroupBuilder::new()
78                    .append_texture_view(d2c_texture_manager.get_view(handle.get_raw()))
79                    .build(&renderer.device, Some("skybox"), &self.bgl);
80
81                self.current_skybox.bg = Some(bg)
82            }
83        }
84    }
85
86    /// Add rendering the skybox to the given rendergraph.
87    pub fn add_to_graph<'node>(
88        &'node self,
89        graph: &mut RenderGraph<'node>,
90        color: RenderTargetHandle,
91        resolve: Option<RenderTargetHandle>,
92        depth: RenderTargetHandle,
93        forward_uniform_bg: DataHandle<BindGroup>,
94        samples: SampleCount,
95    ) {
96        let mut builder = graph.add_node("Skybox");
97
98        let hdr_color_handle = builder.add_render_target_output(color);
99        let hdr_resolve = builder.add_optional_render_target_output(resolve);
100        let hdr_depth_handle = builder.add_render_target_input(depth);
101
102        let rpass_handle = builder.add_renderpass(RenderPassTargets {
103            targets: vec![RenderPassTarget {
104                color: hdr_color_handle,
105                clear: Color::BLACK,
106                resolve: hdr_resolve,
107            }],
108            depth_stencil: Some(RenderPassDepthTarget {
109                target: DepthHandle::RenderTarget(hdr_depth_handle),
110                depth_clear: Some(0.0),
111                stencil_clear: None,
112            }),
113        });
114
115        let forward_uniform_handle = builder.add_data_input(forward_uniform_bg);
116        let pt_handle = builder.passthrough_ref(self);
117
118        builder.build(move |pt, _renderer, encoder_or_pass, temps, _ready, graph_data| {
119            let this = pt.get(pt_handle);
120            let rpass = encoder_or_pass.get_rpass(rpass_handle);
121
122            let forward_uniform_bg = graph_data.get_data(temps, forward_uniform_handle).unwrap();
123
124            if let Some(ref bg) = this.current_skybox.bg {
125                let pipeline = match samples {
126                    SampleCount::One => &this.pipelines.pipeline_s1,
127                    SampleCount::Four => &this.pipelines.pipeline_s4,
128                };
129
130                rpass.set_pipeline(pipeline);
131                rpass.set_bind_group(0, forward_uniform_bg, &[]);
132                rpass.set_bind_group(1, bg, &[]);
133                rpass.draw(0..3, 0..1);
134            }
135        });
136    }
137}
138
139/// Container for all needed skybox pipelines
140pub struct SkyboxPipelines {
141    pub pipeline_s1: RenderPipeline,
142    pub pipeline_s4: RenderPipeline,
143}
144impl SkyboxPipelines {
145    pub fn new(renderer: &Renderer, interfaces: &WholeFrameInterfaces, bgl: &BindGroupLayout) -> Self {
146        profiling::scope!("build skybox pipeline");
147        let skybox_pass_vert = renderer.device.create_shader_module(&ShaderModuleDescriptor {
148            label: Some("skybox vert"),
149            source: ShaderSource::Wgsl(Cow::Borrowed(
150                WGSL_SHADERS
151                    .get_file("skybox.vert.wgsl")
152                    .unwrap()
153                    .contents_utf8()
154                    .unwrap(),
155            )),
156        });
157        let skybox_pass_frag = renderer.device.create_shader_module(&ShaderModuleDescriptor {
158            label: Some("skybox frag"),
159            source: ShaderSource::Wgsl(Cow::Borrowed(
160                WGSL_SHADERS
161                    .get_file("skybox.frag.wgsl")
162                    .unwrap()
163                    .contents_utf8()
164                    .unwrap(),
165            )),
166        });
167
168        let pll = renderer.device.create_pipeline_layout(&PipelineLayoutDescriptor {
169            label: Some("skybox pass"),
170            bind_group_layouts: &[&interfaces.forward_uniform_bgl, bgl],
171            push_constant_ranges: &[],
172        });
173
174        let inner = |samples| {
175            renderer.device.create_render_pipeline(&RenderPipelineDescriptor {
176                label: Some("skybox pass"),
177                layout: Some(&pll),
178                vertex: VertexState {
179                    module: &skybox_pass_vert,
180                    entry_point: "main",
181                    buffers: &[],
182                },
183                primitive: PrimitiveState {
184                    topology: PrimitiveTopology::TriangleList,
185                    strip_index_format: None,
186                    front_face: FrontFace::Cw,
187                    cull_mode: Some(Face::Back),
188                    unclipped_depth: false,
189                    polygon_mode: PolygonMode::Fill,
190                    conservative: false,
191                },
192                depth_stencil: Some(DepthStencilState {
193                    format: TextureFormat::Depth32Float,
194                    depth_write_enabled: true,
195                    depth_compare: CompareFunction::GreaterEqual,
196                    stencil: StencilState::default(),
197                    bias: DepthBiasState::default(),
198                }),
199                multisample: MultisampleState {
200                    count: samples as u32,
201                    ..Default::default()
202                },
203                fragment: Some(FragmentState {
204                    module: &skybox_pass_frag,
205                    entry_point: "main",
206                    targets: &[ColorTargetState {
207                        format: TextureFormat::Rgba16Float,
208                        blend: None,
209                        write_mask: ColorWrites::all(),
210                    }],
211                }),
212                multiview: None,
213            })
214        };
215
216        Self {
217            pipeline_s1: inner(SampleCount::One),
218            pipeline_s4: inner(SampleCount::Four),
219        }
220    }
221}