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