gloss_renderer/forward_renderer/
renderer.rs

1use crate::{
2    components::ConfigChanges,
3    config::{Config, RenderConfig},
4};
5
6use crate::forward_renderer::render_passes::{prepass::PrePass, shadow_pass::ShadowPass, upload_pass::UploadPass};
7
8use crate::{camera::Camera, scene::Scene};
9
10use easy_wgpu::{
11    framebuffer::{FrameBuffer, FrameBufferBuilder},
12    gpu::Gpu,
13    texture::{TexParams, Texture},
14};
15
16use enum_map::Enum;
17use log::debug;
18
19use super::main_pass::MainPass;
20
21pub const ENTITY_ID_PASS_SCALE_FACTOR: u32 = 8;
22
23#[derive(Debug, Enum)]
24pub enum OffscreenTarget {
25    //order determines the binding index in the shader
26    Color,       //for drawing to offscreen
27    MSAAColor,   //useful for drawing during MSAA and then resolving to another view
28    Depth,       // multi-sampled depth target (incompatible depth target when using with EntityID)
29    EntityID,    // useful for rendering entity IDs, to be used by click2select
30    SingleDepth, // non-multi-sampled depth target, to be used by click2select
31}
32
33///  Contains long-living objects that will stay alive for the whole duration of
34/// the Renderer
35pub struct RenderData {
36    pub framebuffer: FrameBuffer<OffscreenTarget>,
37}
38impl RenderData {
39    pub fn new(gpu: &Gpu, params: &RenderConfig, surface_format: Option<wgpu::TextureFormat>) -> Self {
40        //create long-living objects
41        let frambuffer_builder = FrameBufferBuilder::<OffscreenTarget>::new(128, 128);
42        let depth_texture_usage = if cfg!(target_arch = "wasm32") {
43            wgpu::TextureUsages::RENDER_ATTACHMENT
44        } else {
45            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
46            // because for headless rendering we want to download it to cpu
47        };
48
49        let mut offscreen_color_format = wgpu::TextureFormat::Rgba8Unorm;
50        if params.offscreen_color_float_tex {
51            offscreen_color_format = wgpu::TextureFormat::Rgba32Float;
52        }
53
54        let framebuffer = frambuffer_builder
55            .add_render_target(
56                gpu.device(),
57                OffscreenTarget::Color,
58                surface_format.unwrap_or(offscreen_color_format),
59                wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, /* because for headless rendering we want to download it
60                                                                                         * to cpu */
61                TexParams::default(),
62            )
63            .add_render_target(
64                gpu.device(),
65                OffscreenTarget::MSAAColor,
66                surface_format.unwrap_or(wgpu::TextureFormat::Rgba8Unorm),
67                wgpu::TextureUsages::RENDER_ATTACHMENT,
68                TexParams {
69                    sample_count: params.msaa_nr_samples,
70                    ..Default::default()
71                },
72            )
73            .add_render_target(
74                gpu.device(),
75                OffscreenTarget::Depth,
76                wgpu::TextureFormat::Depth32FloatStencil8,
77                depth_texture_usage,
78                TexParams {
79                    sample_count: params.msaa_nr_samples,
80                    ..Default::default()
81                },
82            )
83            .add_render_target(
84                gpu.device(),
85                OffscreenTarget::SingleDepth,
86                wgpu::TextureFormat::Depth16Unorm,
87                depth_texture_usage,
88                TexParams {
89                    scale_factor: ENTITY_ID_PASS_SCALE_FACTOR,
90                    ..Default::default()
91                },
92            )
93            .add_render_target(
94                gpu.device(),
95                OffscreenTarget::EntityID,
96                wgpu::TextureFormat::Rgba8Unorm,
97                wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST,
98                TexParams {
99                    scale_factor: ENTITY_ID_PASS_SCALE_FACTOR,
100                    ..Default::default()
101                },
102            )
103            .build(gpu.device());
104
105        Self { framebuffer }
106    }
107}
108
109pub struct RenderPasses {
110    pub upload_pass: UploadPass, //uploads from CPU to GPU everything that we need globally like settings, camera parameters, lights, etc.
111    shadow_pass: ShadowPass,     //renders depth maps towards all lights
112    main_pass: MainPass,
113}
114impl RenderPasses {
115    pub fn new(gpu: &Gpu, params: &RenderConfig, color_target_format: wgpu::TextureFormat, depth_target_format: wgpu::TextureFormat) -> Self {
116        let upload_pass = UploadPass::new(gpu, params);
117        let shadow_pass = ShadowPass::new(gpu);
118        let main_pass = MainPass::new(gpu, params, color_target_format, depth_target_format);
119        Self {
120            upload_pass,
121            shadow_pass,
122            main_pass,
123        }
124    }
125
126    #[allow(clippy::too_many_arguments)]
127    pub fn run(&mut self, out_view: &wgpu::TextureView, data: &RenderData, gpu: &Gpu, camera: &mut Camera, scene: &mut Scene, config: &mut Config) {
128        //update ubos
129        let global_uniforms = self.upload_pass.run(gpu, camera, scene, &config.render);
130        //render all the geoemtry towards shadow maps
131        self.shadow_pass.run(gpu, global_uniforms, scene);
132        self.main_pass
133            .run(gpu, global_uniforms, &data.framebuffer, out_view, scene, &config.render);
134    }
135}
136
137/// Renderer is the main point of entry for any rendering functionality. It
138/// contains various rendering passes which are executed in sequence. I iterated
139/// through the entities that need rendering from the ECS world and calls the
140/// appropriate rendering passes depending on which components are present on
141/// the entities.
142pub struct Renderer {
143    //long-living objects
144    pub data: RenderData,
145    //passes
146    prepass: PrePass,
147    pub passes: RenderPasses,
148}
149
150impl Renderer {
151    pub fn new(gpu: &Gpu, params: &RenderConfig, surface_format: Option<wgpu::TextureFormat>) -> Self {
152        let data = RenderData::new(gpu, params, surface_format);
153
154        //passes
155        // let debug_pass = DebugPass::new(gpu.device(), &surface_format.unwrap());
156        let prepass = PrePass::new();
157
158        //the color framebuffer will have the same the same format as the surface if
159        // the surface_format.is_some()
160        let color_target_format = data.framebuffer.get(OffscreenTarget::Color).unwrap().texture.format();
161        let depth_target_format = data.framebuffer.get(OffscreenTarget::Depth).unwrap().texture.format();
162
163        let passes = RenderPasses::new(gpu, params, color_target_format, depth_target_format);
164
165        Self { data, prepass, passes }
166    }
167
168    /// Calls the full rendering functionality and writes the final image to a
169    /// texture view. Useful when rendering directly to screen and there is no
170    /// need to save the texture for later # Panics
171    /// This function may panic if the GPU or camera is not available.
172    #[allow(clippy::too_many_arguments)]
173    pub fn render_to_view(
174        &mut self,
175        out_view: &wgpu::TextureView,
176        // width: u32,
177        // height: u32,
178        gpu: &Gpu,
179        camera: &mut Camera,
180        scene: &mut Scene,
181        config: &mut Config,
182        _dt: core::time::Duration,
183    ) {
184        self.begin_frame(gpu, camera, scene, config);
185
186        self.passes.run(out_view, &self.data, gpu, camera, scene, config);
187
188        self.end_frame(scene);
189    }
190
191    /// Calls the full rendering functionality and writes the final image to an
192    /// internal texture which can be recovered using
193    /// [`Renderer::rendered_tex`]. Useful when rendering on a headless machine
194    /// and there is no need to render towards a window. Renders the scene
195    /// to a texture.
196    ///
197    /// # Panics
198    /// This function may panic if the GPU or camera is not available.
199    #[allow(clippy::too_many_arguments)]
200    pub fn render_to_texture(
201        &mut self,
202        // width: u32,
203        // height: u32,
204        gpu: &Gpu,
205        camera: &mut Camera,
206        scene: &mut Scene,
207        config: &mut Config,
208        _dt: core::time::Duration,
209    ) {
210        self.begin_frame(gpu, camera, scene, config);
211
212        let out_view = &self.data.framebuffer.get(OffscreenTarget::Color).unwrap().view;
213        self.passes.run(out_view, &self.data, gpu, camera, scene, config);
214
215        self.end_frame(scene);
216    }
217
218    fn prepare_for_rendering(&mut self, gpu: &Gpu, camera: &mut Camera, scene: &mut Scene, config: &mut Config) {
219        //modify config if needed
220        if let Ok(delta) = scene.get_resource::<&ConfigChanges>() {
221            config.apply_deltas(&delta);
222        }
223
224        //prepass
225        self.prepass.run(gpu, camera, scene, config);
226    }
227
228    fn begin_frame(&mut self, gpu: &Gpu, camera: &mut Camera, scene: &mut Scene, config: &mut Config) {
229        //resize gbuffer and other internal things if necessery if necessery
230        let (width, height) = camera.get_target_res(scene);
231        self.resize_if_necesary(width, height, gpu);
232        self.prepare_for_rendering(gpu, camera, scene, config);
233    }
234
235    fn end_frame(&self, scene: &mut Scene) {
236        //if we do manual ecs without the bevy system, we need to call clear trackers
237        // so that the changed flag gets cleared for the next frame
238        scene.world.clear_trackers();
239    }
240
241    /// # Panics
242    /// Will panic if the framebuffer we render to does not have a target callet
243    /// `Gtarget::Final`
244    pub fn rendered_tex(&self) -> &Texture {
245        self.data.framebuffer.get(OffscreenTarget::Color).unwrap()
246    }
247    /// # Panics
248    /// Will panic if the framebuffer we render to does not have a target callet
249    /// `Gtarget::Final`
250    pub fn rendered_tex_mut(&mut self) -> &mut Texture {
251        self.data.framebuffer.get_mut(OffscreenTarget::Color).unwrap()
252    }
253    /// # Panics
254    /// Will panic if the framebuffer we render to does not have a target callet
255    /// `Gtarget::Final`
256    pub fn depth_buffer(&self) -> &Texture {
257        self.data.framebuffer.get(OffscreenTarget::Depth).unwrap()
258    }
259
260    pub fn entity_id_buffer(&self) -> &Texture {
261        self.data.framebuffer.get(OffscreenTarget::EntityID).unwrap()
262    }
263
264    fn resize_if_necesary(&mut self, width: u32, height: u32, gpu: &Gpu) {
265        if self.data.framebuffer.width != width || self.data.framebuffer.height != height {
266            debug!(
267                "resizing framebuffer because it is size {}, {}",
268                self.data.framebuffer.width, self.data.framebuffer.height
269            );
270            self.data.framebuffer.resize(gpu.device(), width, height);
271        }
272    }
273}