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}