Skip to main content

mraphics_core/render/
renderer.rs

1use crate::constants::{
2    INDEX_BUFFER_LABEL, PROJECTION_MAT_INDEX, PROJECTION_MAT_LABEL, VIEW_MAT_INDEX, VIEW_MAT_LABEL,
3};
4use crate::{
5    Camera, Color, Conveyor, ConveyorManager, GadgetData, GadgetDescriptor, GeometryIndices,
6    PipelineManager, RenderInstance,
7};
8use wgpu::{Texture, TextureFormat, util::DeviceExt};
9
10pub struct Renderer {
11    pub device: wgpu::Device,
12    pub queue: wgpu::Queue,
13
14    pipeline_manager: PipelineManager,
15    mesh_conveyor_manager: ConveyorManager<usize>,
16    material_conveyor_manager: ConveyorManager<String>,
17    shared_conveyor: Conveyor,
18}
19
20impl Renderer {
21    pub fn new(device: wgpu::Device, queue: wgpu::Queue) -> Self {
22        let mut shared_conveyor = Conveyor::new();
23        shared_conveyor.upsert_gadget(
24            &device,
25            &GadgetDescriptor {
26                label: VIEW_MAT_LABEL,
27                index: VIEW_MAT_INDEX,
28                size: 4 * 4 * 4,
29                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
30                ty: wgpu::BufferBindingType::Uniform,
31            },
32        );
33        shared_conveyor.upsert_gadget(
34            &device,
35            &GadgetDescriptor {
36                label: PROJECTION_MAT_LABEL,
37                index: PROJECTION_MAT_INDEX,
38                size: 4 * 4 * 4,
39                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
40                ty: wgpu::BufferBindingType::Uniform,
41            },
42        );
43
44        Self {
45            device,
46            queue,
47            pipeline_manager: PipelineManager::new(),
48            mesh_conveyor_manager: ConveyorManager::new(),
49            material_conveyor_manager: ConveyorManager::new(),
50            shared_conveyor,
51        }
52    }
53
54    /// Reads RGBA pixel data from a GPU texture into a CPU-accessible byte vector.
55    ///
56    /// # Arguments
57    /// * `texture` - The texture to read from
58    /// * `size` - The (width, height) dimensions of the texture
59    ///
60    /// # Returns
61    /// A Vec<u8> containing RGBA pixel data in row-major order
62    pub fn read_texture_rgbau8(&self, texture: &Texture, size: (u32, u32)) -> Vec<u8> {
63        let (width, height) = size;
64        let unpadded_bytes_per_row = width * 4; // rgba
65        let bytes_per_row =
66            wgpu::util::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
67        let buffer_size = (bytes_per_row * height) as wgpu::BufferAddress;
68        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
69            label: Some("Mraphics Texture Mapping Buffer"),
70            size: buffer_size,
71            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
72            mapped_at_creation: false,
73        });
74
75        let mut encoder = self
76            .device
77            .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
78
79        encoder.copy_texture_to_buffer(
80            wgpu::TexelCopyTextureInfoBase {
81                texture,
82                mip_level: 0,
83                origin: wgpu::Origin3d::ZERO,
84                aspect: wgpu::TextureAspect::All,
85            },
86            wgpu::TexelCopyBufferInfoBase {
87                buffer: &buffer,
88                layout: wgpu::TexelCopyBufferLayout {
89                    offset: 0,
90                    bytes_per_row: Some(bytes_per_row),
91                    rows_per_image: Some(height),
92                },
93            },
94            wgpu::Extent3d {
95                width,
96                height,
97                depth_or_array_layers: 1,
98            },
99        );
100
101        self.queue.submit(std::iter::once(encoder.finish()));
102
103        let (sender, receiver) = std::sync::mpsc::sync_channel(1);
104
105        buffer.map_async(wgpu::MapMode::Read, .., move |result| {
106            sender.send(result).unwrap();
107        });
108
109        self.device
110            .poll(wgpu::PollType::wait_indefinitely())
111            .unwrap();
112
113        receiver.recv().unwrap().unwrap();
114
115        let raw_data = buffer.get_mapped_range(..);
116        let mut data = Vec::with_capacity((unpadded_bytes_per_row * height) as usize);
117
118        for row in 0..height {
119            let row_start = (row * bytes_per_row) as usize;
120            let row_end = row_start + unpadded_bytes_per_row as usize;
121
122            data.extend_from_slice(&raw_data[row_start..row_end]);
123        }
124
125        drop(raw_data);
126
127        buffer.unmap();
128
129        data
130    }
131
132    /// Renders a collection of instances to a texture using the specified camera and clear color.
133    /// Updates shared camera matrices, processes each render instance, and submits commands to the GPU.
134    ///
135    /// # Arguments
136    /// * `texture` - The target texture to render to
137    /// * `texture_format` - The format of the target texture,used to build a render pipeline
138    /// * `instances` - Mutable slice of render instances to draw
139    /// * `camera` - Camera providing view and projection matrices
140    /// * `clear_color` - Background color to clear the texture with
141    pub fn render<C: Camera>(
142        &mut self,
143        texture: &Texture,
144        texture_format: TextureFormat,
145        instances: &mut [RenderInstance],
146        camera: &C,
147        clear_color: &Color<f64>,
148    ) {
149        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
150
151        let mut encoder = self
152            .device
153            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
154                label: Some("Mraphics Command Encoder"),
155            });
156
157        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
158            label: Some("Mraphics Render Pass"),
159            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
160                view: &view,
161                resolve_target: None,
162                ops: wgpu::Operations {
163                    load: wgpu::LoadOp::Clear(wgpu::Color {
164                        r: clear_color[0],
165                        g: clear_color[1],
166                        b: clear_color[2],
167                        a: clear_color[3],
168                    }),
169                    store: wgpu::StoreOp::Store,
170                },
171                depth_slice: None,
172            })],
173            ..Default::default()
174        });
175
176        // SAFETY: initialized these gadgets in Renderer::new()
177        self.shared_conveyor
178            .update_gadget(&self.queue, VIEW_MAT_LABEL, camera.view_mat_data())
179            .unwrap();
180        self.shared_conveyor
181            .update_gadget(
182                &self.queue,
183                PROJECTION_MAT_LABEL,
184                camera.projection_mat_data(),
185            )
186            .unwrap();
187
188        fn render_recursive(
189            this: &mut Renderer,
190            texture_format: TextureFormat,
191            render_pass: &mut wgpu::RenderPass,
192            instance: &mut RenderInstance,
193        ) {
194            if instance.visible {
195                this.render_instance(texture_format, render_pass, instance);
196            }
197
198            for mut child in &mut instance.children {
199                render_recursive(this, texture_format, render_pass, &mut child);
200            }
201        }
202
203        for instance in instances {
204            render_recursive(self, texture_format, &mut render_pass, instance);
205        }
206
207        drop(render_pass);
208
209        self.queue.submit(std::iter::once(encoder.finish()));
210    }
211
212    /// Renders a single instance by setting up its geometry and material data,
213    /// updating necessary buffers, and issuing draw commands.
214    ///
215    /// # Arguments
216    /// * `texture_format` - The format of the current render target
217    /// * `render_pass` - The active render pass to record commands into
218    /// * `instance` - The render instance to draw
219    fn render_instance(
220        &mut self,
221        texture_format: TextureFormat,
222        render_pass: &mut wgpu::RenderPass,
223        instance: &mut RenderInstance,
224    ) {
225        let mesh_conveyor = self
226            .mesh_conveyor_manager
227            .acquire_conveyor(&instance.identifier);
228
229        update_gadgets(
230            &self.device,
231            &self.queue,
232            mesh_conveyor,
233            &mut instance.geometry.attributes,
234            wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
235            wgpu::BufferBindingType::Storage { read_only: true },
236        );
237
238        update_gadgets(
239            &self.device,
240            &self.queue,
241            mesh_conveyor,
242            &mut instance.geometry.uniforms,
243            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
244            wgpu::BufferBindingType::Uniform,
245        );
246
247        let material_conveyor = self
248            .material_conveyor_manager
249            .acquire_conveyor(&(instance.identifier.to_string() + &instance.material.identifier));
250
251        update_gadgets(
252            &self.device,
253            &self.queue,
254            material_conveyor,
255            &mut instance.material.attributes,
256            wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
257            wgpu::BufferBindingType::Storage { read_only: true },
258        );
259
260        update_gadgets(
261            &self.device,
262            &self.queue,
263            material_conveyor,
264            &mut instance.material.uniforms,
265            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
266            wgpu::BufferBindingType::Uniform,
267        );
268
269        let needs_update = self.shared_conveyor.needs_update
270            || mesh_conveyor.needs_update
271            || material_conveyor.needs_update;
272        if needs_update {
273            self.shared_conveyor.update_bundles(&self.device);
274            mesh_conveyor.update_bundles(&self.device);
275            material_conveyor.update_bundles(&self.device);
276        }
277
278        let maybe_bind_group_layouts = Conveyor::collect_bind_group_layouts(vec![
279            &self.shared_conveyor.bundles,
280            &mesh_conveyor.bundles,
281            &material_conveyor.bundles,
282        ]);
283        let bind_group_placeholder = if maybe_bind_group_layouts.contains(&None) {
284            Some(
285                self.device
286                    .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
287                        label: Some(&format!("Mraphics bind group layout placeholder",)),
288                        entries: &[],
289                    }),
290            )
291        } else {
292            None
293        };
294        let bind_group_layouts = maybe_bind_group_layouts
295            .iter()
296            .map(|bind_group| {
297                bind_group.unwrap_or_else(|| bind_group_placeholder.as_ref().unwrap())
298            })
299            .collect::<Vec<_>>();
300
301        let pipeline = self.pipeline_manager.acquire_pipeline(
302            &self.device,
303            texture_format,
304            instance,
305            &bind_group_layouts,
306            needs_update,
307        );
308
309        self.shared_conveyor.attach_bundles(render_pass);
310        mesh_conveyor.attach_bundles(render_pass);
311        material_conveyor.attach_bundles(render_pass);
312
313        render_pass.set_pipeline(pipeline);
314
315        match &mut instance.geometry.indices {
316            GeometryIndices::Sequential(indices) => {
317                render_pass.draw(0..*indices, 0..1);
318            }
319            GeometryIndices::CustomU16(indices) => {
320                if indices.buffer.is_none() {
321                    indices.buffer.replace(self.device.create_buffer_init(
322                        &wgpu::util::BufferInitDescriptor {
323                            label: Some(INDEX_BUFFER_LABEL),
324                            contents: bytemuck::cast_slice(&indices.data),
325                            usage: wgpu::BufferUsages::INDEX,
326                        },
327                    ));
328                }
329
330                // SAFETY: Checked upon
331                let buffer = indices.buffer.as_ref().unwrap();
332                render_pass.set_index_buffer(buffer.slice(..), wgpu::IndexFormat::Uint16);
333                render_pass.draw_indexed(0..(indices.data.len() as u32), 0, 0..1);
334            }
335            GeometryIndices::CustomU32(indices) => {
336                if indices.buffer.is_none() {
337                    if indices.buffer.is_none() {
338                        indices.buffer.replace(self.device.create_buffer_init(
339                            &wgpu::util::BufferInitDescriptor {
340                                label: Some(INDEX_BUFFER_LABEL),
341                                contents: bytemuck::cast_slice(&indices.data),
342                                usage: wgpu::BufferUsages::INDEX,
343                            },
344                        ));
345                    }
346                }
347
348                // SAFETY: Checked upon
349                let buffer = indices.buffer.as_ref().unwrap();
350                render_pass.set_index_buffer(buffer.slice(..), wgpu::IndexFormat::Uint32);
351                render_pass.draw_indexed(0..(indices.data.len() as u32), 0, 0..1);
352            }
353        }
354    }
355}
356
357fn update_gadgets(
358    device: &wgpu::Device,
359    queue: &wgpu::Queue,
360    conveyor: &mut Conveyor,
361    gadget_data: &mut Vec<GadgetData>,
362    usage: wgpu::BufferUsages,
363    ty: wgpu::BufferBindingType,
364) {
365    for data in gadget_data {
366        update_gadget(device, queue, conveyor, data, usage, ty);
367    }
368}
369
370fn update_gadget(
371    device: &wgpu::Device,
372    queue: &wgpu::Queue,
373    conveyor: &mut Conveyor,
374    data: &mut GadgetData,
375    usage: wgpu::BufferUsages,
376    ty: wgpu::BufferBindingType,
377) {
378    if data.needs_update_buffer {
379        conveyor.upsert_gadget(
380            device,
381            &GadgetDescriptor {
382                label: &data.label,
383                index: data.index,
384                size: data.data.len() as u64,
385                usage,
386                ty,
387            },
388        );
389
390        data.needs_update_buffer = false;
391    }
392
393    if !data.needs_update_value {
394        return;
395    }
396
397    // SAFETY: This may panic, but it's developer's responsibility
398    conveyor
399        .update_gadget(queue, &data.label, &data.data)
400        .unwrap();
401
402    data.needs_update_value = false;
403}