Skip to main content

ebb/
rendering.rs

1use wgpu;
2use crate::Instance;
3use crate::ecs;
4use crate::mesh;
5
6/*
7 * TODO:
8 * - Shader abstraction and preprocesser (#include directives and prelude)
9 * - More configurability for render pipelines
10 * - Better abstraction for RenderContext
11*/
12
13/// An interface used for creating command buffers for rendering.
14pub struct RenderContext {
15    encoder: wgpu::CommandEncoder
16}
17
18/// A collection of shader inputs (vertices) and color attachments (framebuffers)
19pub struct RenderPipeline {
20    pipeline: wgpu::RenderPipeline
21}
22impl RenderPipeline {
23    /// Creates a render pipeline from the raw WGPU descriptor.
24    /// # Arguments
25    /// 
26    /// * `instance` - The instance to use when creating the pipeline.
27    /// * `desc` - The WGPU descriptor to use when creating the pipeline.
28    /// 
29    /// # Returns
30    /// 
31    /// The created render pipeline.
32    /// 
33    /// # Examples
34    /// 
35    /// ```ignore
36    /// let pipeline = ebb::rendering::RenderPipeline::from_raw(&g_instance, &descriptor);
37    /// ```
38    pub fn from_raw(instance: &Instance, desc: &wgpu::RenderPipelineDescriptor) -> Self {
39        Self {
40            pipeline: instance.raw_device().create_render_pipeline(desc)
41        }
42    }
43
44    /// Creates a render pipeline from the vertex configuration and shader.
45    /// 
46    /// # Arguments
47    /// 
48    /// * `instance` - The instance to use when creating the pipeline.
49    /// * `buffers` - The set of vertex buffer layouts to use.
50    /// * `shader` - The shader module (`vs_main` `fs_main` required) to use for rendering.
51    /// 
52    /// # Returns
53    /// 
54    /// The created render pipeline.
55    /// 
56    /// # Examples
57    /// 
58    /// ```ignore
59    /// let pipeline = ebb::rendering::RenderPipeline::new(&g_instance, &[MeshVertex::LAYOUT], wgpu::include_wgsl!("shaders/example.wgsl"));
60    /// ```
61    pub fn new(instance: &Instance, buffers: &[wgpu::VertexBufferLayout], shader: wgpu::ShaderModuleDescriptor) -> Self {
62        let shader = instance.raw_device().create_shader_module(shader);
63        let layout = instance.raw_device().create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
64            label: Some("Ebb Builtin RenderPipeline - PipelineLayout"),
65            bind_group_layouts: &[],
66            push_constant_ranges: &[],
67        });
68
69        let render_pipeline = instance.raw_device().create_render_pipeline(&wgpu::RenderPipelineDescriptor {
70            label: Some("Ebb Builtin RenderPipeline - RenderPipeline"),
71            layout: Some(&layout),
72            vertex: wgpu::VertexState {
73                module: &shader,
74                entry_point: "vs_main",
75                buffers,
76                compilation_options: wgpu::PipelineCompilationOptions::default(),
77            },
78            fragment: Some(wgpu::FragmentState {
79                module: &shader,
80                entry_point: "fs_main",
81                targets: &[Some(wgpu::ColorTargetState {
82                    format: instance.raw_config().format,
83                    blend: Some(wgpu::BlendState::REPLACE),
84                    write_mask: wgpu::ColorWrites::ALL,
85                })],
86                compilation_options: wgpu::PipelineCompilationOptions::default(),
87            }),
88            primitive: wgpu::PrimitiveState {
89                topology: wgpu::PrimitiveTopology::TriangleList,
90                strip_index_format: None,
91                front_face: wgpu::FrontFace::Ccw,
92                cull_mode: Some(wgpu::Face::Back),
93                // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
94                polygon_mode: wgpu::PolygonMode::Fill,
95                // Requires Features::DEPTH_CLIP_CONTROL
96                unclipped_depth: false,
97                // Requires Features::CONSERVATIVE_RASTERIZATION
98                conservative: false,
99            },
100            depth_stencil: None,
101            multisample: wgpu::MultisampleState {
102                count: 1,
103                mask: !0,
104                alpha_to_coverage_enabled: false,
105            },
106            multiview: None,
107            cache: None,
108        });
109        
110        Self {
111            pipeline: render_pipeline
112        }
113    }
114
115    /// Creates a render pipeline for a mesh with the specified vertex type.
116    /// 
117    /// # Type Parameters
118    /// 
119    /// * `V` - a [mesh::Vertex], the layout of which is used as the vertex buffer in slot 0.
120    /// 
121    /// # Arguments
122    /// 
123    /// * `instance` - The instance to use when creating the pipeline.
124    /// * `shader` - The shader module (`vs_main` `fs_main` required) to use for rendering.
125    /// 
126    /// # Returns
127    /// 
128    /// The created render pipeline.
129    /// 
130    /// # Examples
131    /// 
132    /// ```ignore
133    /// let pipeline = ebb::rendering::RenderPipeline::for_mesh::<MeshVertex>(&g_instance, wgpu::include_wgsl("shaders/example.wgsl"));
134    /// ```
135    pub fn for_mesh<V: mesh::Vertex>(instance: &Instance, shader: wgpu::ShaderModuleDescriptor) -> Self {
136        Self::new(instance, &[V::LAYOUT], shader)
137    }
138
139    /// Get the raw WGPU pipeline object.
140    /// 
141    /// # Returns
142    /// 
143    /// A reference to the internal [wgpu::RenderPipeline].
144    pub fn raw_pipeline(&self) -> &wgpu::RenderPipeline {
145        &self.pipeline
146    }
147}
148
149/// An [ecs::System] for basic rendering tasks.
150/// It clears the screen, then renders every entity with a RenderMesh component.
151/// 
152/// This struct takes ownership of an [Instance] which is used for rendering.
153pub struct BasicRenderSystem {
154    instance: Instance<'static>,
155    clear_color: wgpu::Color
156}
157impl ecs::System for BasicRenderSystem {
158
159    /// Clears the screen and renders all entities with a RenderMesh component.
160    /// 
161    /// # Arguments
162    /// 
163    /// * `world` - The collection of entities to operate on.
164    fn update(&self, world: &mut Vec<ecs::Entity>) {
165        let mut render_ctx = RenderContext::new(&self.instance);
166        let output = self.instance
167            .window_surface()
168            .wgpu_surface()
169            .get_current_texture()
170            .expect("Ebb - Failed to acquire window surface texture");
171
172        let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
173
174        let mut render_pass = render_ctx.clear(&view, self.clear_color);
175
176        for entity in world {
177            if let Some(component) = entity.get_component::<mesh::RenderMesh>() {
178                render_pass.set_pipeline(&component.get_renderer().pipeline);
179                let vb = component.get_vertex_buffer();
180                let ib = component.get_index_buffer();
181
182                render_pass.set_vertex_buffer(0, vb.slice(..));
183                render_pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint32);
184                render_pass.draw_indexed(0..(component.get_num_indices() as u32), 0, 0..1);
185            }
186        }
187
188        drop(render_pass);
189
190        render_ctx.submit(&self.instance);
191        output.present();
192    }
193}
194
195impl BasicRenderSystem {
196    /// Creates a new [BasicRenderSystem]
197    /// 
198    /// # Arguments
199    /// 
200    /// * `instance` - The [Instance] to use for rendering. Ownership is passed to the [BasicRenderSystem].
201    /// * `clear_color` - The color to clear the screen to before rendering.
202    /// 
203    /// # Returns
204    /// 
205    /// The [BasicRenderSystem].
206    pub fn new(instance: Instance<'static>, clear_color: wgpu::Color) -> Self {
207        Self {
208            instance, clear_color
209        }
210    }
211}
212
213impl RenderContext {
214
215    /// Creates a new [RenderContext].
216    /// 
217    /// # Arguments
218    /// 
219    /// * `instance` - the [Instance] to use for rendering. Borrowed for the duration of the function execution.
220    /// 
221    /// # Returns
222    /// 
223    /// The created [RenderContext].
224    pub fn new<'a>(instance: &Instance<'a>) -> Self {
225        Self {
226            encoder: instance.raw_device().create_command_encoder(&wgpu::CommandEncoderDescriptor {
227                label: Some("Ebb Builtin RenderContext - Encoder")
228            })
229        }
230    }
231
232    /// Creates a [wgpu::RenderPass] from a raw [wgpu::RenderPassDescriptor].
233    /// Should generally only be used internally.
234    /// 
235    /// # Arguments
236    /// 
237    /// * `desc` - the [wgpu::RenderPassDescriptor] to use when creating the [wgpu::RenderPass].
238    /// 
239    /// # Returns
240    /// 
241    /// The [wgpu::RenderPass] matching the argument.
242    pub fn create_render_pass_raw(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass {
243        self.encoder.begin_render_pass(desc)
244    }
245
246    /// Creates a [wgpu::RenderPass] which starts by clearing the input texture to a color.
247    /// Should generally only be used internally.
248    /// 
249    /// # Arguments
250    /// 
251    /// * `surf_view` - the [wgpu::TextureView] to clear.
252    /// * `color` - the [wgpu::Color] to clear the texture to.
253    /// 
254    /// # Returns
255    /// 
256    /// The [wgpu::RenderPass], ready for rendering, starting with a clear op.
257    pub fn clear(&mut self, surf_view: &wgpu::TextureView, color: wgpu::Color) -> wgpu::RenderPass {
258        self.create_render_pass_raw(&wgpu::RenderPassDescriptor {
259            label: Some("Ebb Builtin RenderContext - Clear"),
260            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
261                view: surf_view,
262                resolve_target: None,
263                ops: wgpu::Operations {
264                    load: wgpu::LoadOp::Clear(color),
265                    store: wgpu::StoreOp::Store,
266                },
267            })],
268            depth_stencil_attachment: None,
269            occlusion_query_set: None,
270            timestamp_writes: None,
271        })
272    }
273
274    /// Finishes rendering and creates a command buffer.
275    /// This method consumes `self`.
276    /// This should generally only be used internally.
277    /// 
278    /// # Returns
279    /// 
280    /// The [wgpu::CommandBuffer] which may be submit to the GPU for rendering.
281    pub fn to_command_buffer(self) -> wgpu::CommandBuffer {
282        self.encoder.finish()
283    }
284
285    /// Submits previously issued draw commands to the GPU for rendering.
286    /// 
287    /// # Arguments
288    /// 
289    /// * `instance` - the [Instance] to use for rendering. Borrowed for the duration of the function execution.
290    pub fn submit<'a>(self, instance: &Instance<'a>) {
291        instance.raw_queue().submit(std::iter::once(self.to_command_buffer()));
292    }
293}