bottomless_pit/
render.rs

1//! Bottomles Pit supports multipass rendering. This means you can go through
2//! multiple render passes within the [Game::render] function. This can be used
3//! to render to a texture then render to the screen. The [RenderHandle] is used
4//! to create render passes and then within the passes the [Renderer] is used to
5//! actually interface with materials and other objects that render.
6//! ```rust
7//! impl Game for MyCoolGame {
8//!     fn render<'o>(&'o mut self, mut render_handle: RenderHandle<'o>) {
9//!         {
10//!             let mut p1 = render_handle.begin_texture_pass(&mut self.uniform_texture, Colour::WHITE);
11//!             // render what ever you want to the texture
12//!         }
13//!         let mut p2 = render_handle.begin_pass(Colour::Black);
14//!         // render whatever you want to the screen
15//!     }
16//! }
17//! ```
18
19use crate::colour::Colour;
20use crate::context::WgpuClump;
21use crate::engine_handle::Engine;
22use crate::resource::{ResourceId, ResourceManager};
23use crate::shader::Shader;
24use crate::texture::UniformTexture;
25use crate::vectors::Vec2;
26use crate::{vec2, Game};
27
28pub(crate) fn make_pipeline(
29    device: &wgpu::Device,
30    topology: wgpu::PrimitiveTopology,
31    bind_group_layouts: &[&wgpu::BindGroupLayout],
32    vertex_buffers: &[wgpu::VertexBufferLayout],
33    shader: &wgpu::ShaderModule,
34    texture_format: wgpu::TextureFormat,
35    label: Option<&str>,
36) -> wgpu::RenderPipeline {
37    let layout_label = label.map(|label| format!("{}_layout", label));
38
39    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
40        label: layout_label.as_deref(), // somehow converss Option<String> to Option<&str>
41        bind_group_layouts,
42        push_constant_ranges: &[],
43    });
44
45    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
46        label,
47        layout: Some(&layout),
48        vertex: wgpu::VertexState {
49            module: shader,
50            entry_point: "vs_main", //specify the entry point (can be whatever as long as it exists)
51            buffers: vertex_buffers, // specfies what type of vertices we want to pass to the shader,
52            compilation_options: wgpu::PipelineCompilationOptions::default(),
53        },
54        fragment: Some(wgpu::FragmentState {
55            // techically optional. Used to store colour data to the surface
56            module: shader,
57            entry_point: "fs_main",
58            targets: &[Some(wgpu::ColorTargetState {
59                // tells wgpu what colour outputs it should set up.
60                format: texture_format,
61                blend: Some(wgpu::BlendState::ALPHA_BLENDING), // specifies that the blending should just replace old pixel data wiht new data,
62                write_mask: wgpu::ColorWrites::ALL,            // writes all colours
63            })],
64            compilation_options: wgpu::PipelineCompilationOptions::default(),
65        }),
66        primitive: wgpu::PrimitiveState {
67            topology,
68            strip_index_format: None,
69            front_face: wgpu::FrontFace::Cw, // triagnle must be counter-clock wise to be considered facing forawrd
70            cull_mode: Some(wgpu::Face::Back), // all triagnles not front facing are culled
71            // setting this to anything other than fill requires Features::NON_FILL_POLYGON_MODE
72            polygon_mode: wgpu::PolygonMode::Fill,
73            // requires Features::DEPTH_CLIP_CONTROLL,
74            unclipped_depth: false,
75            // requires Features::CONSERVATIVE_RASTERIZATION,
76            conservative: false,
77        },
78        depth_stencil: None,
79        multisample: wgpu::MultisampleState {
80            count: 1,                         // determines how many samples the pipeline will use
81            mask: !0, // how many samples the pipeline will use (in this case all of them)
82            alpha_to_coverage_enabled: false, // something to do with AA
83        },
84        multiview: None,
85        cache: None,
86    })
87}
88
89/// RenderInformation contains all the information needed to render objects to the sreen.
90/// You should never have to worry about lifetimes as the way the event loop is strutured
91/// RenderInformation will live long enough, just properly annotate your functions like so:
92/// ```rust
93/// fn render<'a, 'b>(&'b mut self, mut render_handle: RenderInformation<'a, 'b>) where 'b: 'a {
94///     // do something here
95/// }
96/// ```
97pub(crate) fn render<T>(game: &mut T, engine: &mut Engine) -> Result<(), wgpu::SurfaceError>
98where
99    T: Game,
100{
101    // there is a chance an .unwrap() would panic bc of an unloaded resource
102    if engine.is_loading() {
103        return Ok(());
104    }
105
106    let render_handle = RenderHandle::from(engine);
107
108    game.render(render_handle);
109
110    Ok(())
111}
112
113/// The renderer is used to create render passes or [Renderer]s
114pub struct RenderHandle<'a> {
115    // ME WHEN BC HACKS :3
116    encoder: Option<wgpu::CommandEncoder>,
117    surface: Option<wgpu::SurfaceTexture>,
118    pub(crate) resources: &'a ResourceManager,
119    defualt_id: ResourceId<Shader>,
120    defualt_view: wgpu::TextureView,
121    defualt_view_size: Vec2<u32>,
122    camera_bindgroup: &'a wgpu::BindGroup,
123    pub(crate) wgpu: &'a WgpuClump,
124    format: wgpu::TextureFormat,
125}
126
127impl<'a> RenderHandle<'a> {
128    /// Creates a render pass that will render onto the windows surface.
129    pub fn begin_pass<'p>(&mut self, clear_colour: Colour) -> Renderer<'_, 'p> {
130        let mut pass = match &mut self.encoder {
131            Some(encoder) => Self::create_pass(encoder, &self.defualt_view, clear_colour.into()),
132            None => unreachable!(),
133        };
134
135        let pipeline = &self
136            .resources
137            .get_pipeline(&self.defualt_id)
138            .unwrap()
139            .pipeline;
140
141        pass.set_pipeline(pipeline);
142        pass.set_bind_group(1, self.camera_bindgroup, &[]);
143
144        Renderer {
145            pass,
146            size: self.defualt_view_size,
147            defualt_id: self.defualt_id,
148            resources: self.resources,
149            camera_bindgroup: self.camera_bindgroup,
150            wgpu: self.wgpu,
151        }
152    }
153
154    /// Creates a render pass that can be used to render to a texture. This is usefull for
155    /// lightmaps, or generating heightmaps. Any advanced graphics techique that requires
156    /// uniform textures.
157    pub fn begin_texture_pass<'o, 'p>(
158        &'o mut self,
159        texture: &'o mut UniformTexture,
160        clear_colour: Colour,
161    ) -> Renderer<'o, 'p> {
162        let size = texture.get_size();
163
164        let mut pass = match &mut self.encoder {
165            Some(encoder) => Self::create_pass(
166                encoder,
167                texture.make_render_view(self.wgpu, self.format),
168                clear_colour.into(),
169            ),
170            None => unreachable!(),
171        };
172
173        let pipeline = &self
174            .resources
175            .get_pipeline(&self.defualt_id)
176            .unwrap()
177            .pipeline;
178
179        pass.set_pipeline(pipeline);
180        pass.set_bind_group(1, self.camera_bindgroup, &[]);
181
182        Renderer {
183            pass,
184            size,
185            defualt_id: self.defualt_id,
186            resources: self.resources,
187            camera_bindgroup: self.camera_bindgroup,
188            wgpu: self.wgpu,
189        }
190    }
191
192    fn create_pass<'p>(
193        encoder: &'p mut wgpu::CommandEncoder,
194        view: &'p wgpu::TextureView,
195        clear_colour: wgpu::Color,
196    ) -> wgpu::RenderPass<'p> {
197        let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
198            label: Some("Render Pass"),
199            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
200                view,
201                resolve_target: None,
202                ops: wgpu::Operations {
203                    load: wgpu::LoadOp::Clear(clear_colour),
204                    store: wgpu::StoreOp::Store,
205                },
206            })],
207            timestamp_writes: None,
208            occlusion_query_set: None,
209            depth_stencil_attachment: None,
210        });
211
212        render_pass
213    }
214}
215
216impl<'a> From<&'a mut Engine> for RenderHandle<'a> {
217    fn from(value: &'a mut Engine) -> Self {
218        let context = value.context.as_ref().unwrap();
219
220        let encoder = context
221            .wgpu
222            .device
223            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
224                label: Some("render encoder"),
225            });
226
227        let texture = context.get_surface_texture().unwrap();
228
229        let defualt_view_size = texture.texture.size();
230        let defualt_view_size = vec2!(defualt_view_size.width, defualt_view_size.height);
231        let defualt_view = texture
232            .texture
233            .create_view(&wgpu::TextureViewDescriptor::default());
234
235        Self {
236            encoder: Some(encoder),
237            surface: Some(texture),
238            resources: value.get_resources(),
239            defualt_id: value.defualt_pipe_id(),
240            defualt_view,
241            defualt_view_size,
242            camera_bindgroup: &context.camera_bind_group,
243            wgpu: &context.wgpu,
244            format: context.get_texture_format(),
245        }
246    }
247}
248
249impl Drop for RenderHandle<'_> {
250    fn drop(&mut self) {
251        self.wgpu
252            .queue
253            .submit(std::iter::once(self.encoder.take().unwrap().finish()));
254
255        self.surface.take().unwrap().present();
256    }
257}
258
259/// Use the Renderer with [Materials](crate::material::Material) to draw things
260/// to the current render surface
261pub struct Renderer<'o, 'p>
262where
263    'o: 'p,
264{
265    pub(crate) pass: wgpu::RenderPass<'p>,
266    pub(crate) size: Vec2<u32>,
267    pub(crate) resources: &'o ResourceManager,
268    pub(crate) defualt_id: ResourceId<Shader>,
269    pub(crate) camera_bindgroup: &'o wgpu::BindGroup,
270    pub(crate) wgpu: &'o WgpuClump,
271}
272
273impl<'p, 'o> Renderer<'p, 'o> {
274    /// Resets the camera to the defualt camera.
275    pub fn reset_camera(&mut self) {
276        self.pass.set_bind_group(1, self.camera_bindgroup, &[]);
277    }
278
279    /// Gives the size of the current render surface
280    pub fn get_size(&self) -> Vec2<u32> {
281        self.size
282    }
283}