ranim_render/
lib.rs

1//! Rendering stuff in ranim
2// #![warn(missing_docs)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![allow(rustdoc::private_intra_doc_links)]
5#![doc(
6    html_logo_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg",
7    html_favicon_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg"
8)]
9/// Render Graph
10pub mod graph;
11/// The pipelines
12pub mod pipelines;
13/// The basic renderable structs
14pub mod primitives;
15pub mod resource;
16/// Rendering related utils
17pub mod utils;
18
19use glam::{UVec3, uvec3};
20
21use crate::{
22    graph::{AnyGlobalRenderNodeTrait, GlobalRenderGraph, RenderPackets},
23    primitives::{mesh_items::MeshItemsBuffer, viewport::ViewportUniform, vitems::VItemsBuffer},
24    resource::{PipelinesPool, RenderPool, RenderTextures},
25    utils::{WgpuBuffer, WgpuVecBuffer},
26};
27use ranim_core::store::CoreItemStore;
28use utils::WgpuContext;
29
30#[cfg(feature = "profiling")]
31// Since the timing information we get from WGPU may be several frames behind the CPU, we can't report these frames to
32// the singleton returned by `puffin::GlobalProfiler::lock`. Instead, we need our own `puffin::GlobalProfiler` that we
33// can be several frames behind puffin's main global profiler singleton.
34pub static PUFFIN_GPU_PROFILER: std::sync::LazyLock<std::sync::Mutex<puffin::GlobalProfiler>> =
35    std::sync::LazyLock::new(|| std::sync::Mutex::new(puffin::GlobalProfiler::default()));
36
37#[allow(unused)]
38#[cfg(feature = "profiling")]
39mod profiling_utils {
40    use wgpu_profiler::GpuTimerQueryResult;
41
42    pub fn scopes_to_console_recursive(results: &[GpuTimerQueryResult], indentation: u32) {
43        for scope in results {
44            if indentation > 0 {
45                print!("{:<width$}", "|", width = 4);
46            }
47
48            if let Some(time) = &scope.time {
49                println!(
50                    "{:.3}μs - {}",
51                    (time.end - time.start) * 1000.0 * 1000.0,
52                    scope.label
53                );
54            } else {
55                println!("n/a - {}", scope.label);
56            }
57
58            if !scope.nested_queries.is_empty() {
59                scopes_to_console_recursive(&scope.nested_queries, indentation + 1);
60            }
61        }
62    }
63
64    pub fn console_output(
65        results: &Option<Vec<GpuTimerQueryResult>>,
66        enabled_features: wgpu::Features,
67    ) {
68        puffin::profile_scope!("console_output");
69        print!("\x1B[2J\x1B[1;1H"); // Clear terminal and put cursor to first row first column
70        println!("Welcome to wgpu_profiler demo!");
71        println!();
72        println!(
73            "Press space to write out a trace file that can be viewed in chrome's chrome://tracing"
74        );
75        println!();
76        match results {
77            Some(results) => {
78                scopes_to_console_recursive(results, 0);
79            }
80            None => println!("No profiling results available yet!"),
81        }
82    }
83}
84
85#[derive(Clone, Copy)]
86pub struct RenderContext<'a> {
87    pub render_textures: &'a RenderTextures,
88    pub render_pool: &'a RenderPool,
89    pub render_packets: &'a RenderPackets,
90    pub pipelines: &'a PipelinesPool,
91    pub wgpu_ctx: &'a WgpuContext,
92    pub resolution_info: &'a ResolutionInfo,
93    pub clear_color: wgpu::Color,
94    /// Present when using the merged rendering path.
95    pub merged_buffer: Option<&'a VItemsBuffer>,
96    /// Present when using the merged mesh rendering path.
97    pub merged_mesh_buffer: Option<&'a MeshItemsBuffer>,
98}
99
100// MARK: Renderer
101pub struct Renderer {
102    width: u32,
103    height: u32,
104    pub(crate) resolution_info: ResolutionInfo,
105    pub(crate) pipelines: PipelinesPool,
106    packets: RenderPackets,
107    render_graph: GlobalRenderGraph,
108
109    /// Present when using the merged rendering path (lazily initialized on first use).
110    merged_buffer: Option<VItemsBuffer>,
111    /// Present when using the merged mesh rendering path (lazily initialized on first use).
112    merged_mesh_buffer: Option<MeshItemsBuffer>,
113
114    #[cfg(feature = "profiling")]
115    pub(crate) profiler: wgpu_profiler::GpuProfiler,
116}
117
118impl Renderer {
119    pub fn width(&self) -> u32 {
120        self.width
121    }
122
123    pub fn height(&self) -> u32 {
124        self.height
125    }
126
127    pub fn ratio(&self) -> f32 {
128        self.width as f32 / self.height as f32
129    }
130
131    fn build_render_graph() -> GlobalRenderGraph {
132        use graph::*;
133        let mut render_graph = GlobalRenderGraph::new();
134        let clear = render_graph.insert_node(ClearNode);
135        let view_render = render_graph.insert_node({
136            use graph::view::*;
137            let mut render_graph = ViewRenderGraph::new();
138            let vitem_compute = render_graph.insert_node(MergedVItemComputeNode);
139            let vitem_depth = render_graph.insert_node(MergedVItemDepthNode);
140            let mesh_depth = render_graph.insert_node(MergedMeshItemDepthNode);
141            let vitem_color = render_graph.insert_node(MergedVItemColorNode);
142            let mesh_color = render_graph.insert_node(MergedMeshItemColorNode);
143
144            render_graph.insert_edge(vitem_compute, vitem_depth);
145            render_graph.insert_edge(vitem_depth, vitem_color);
146            render_graph.insert_edge(vitem_depth, mesh_color);
147            render_graph.insert_edge(mesh_depth, mesh_color);
148            render_graph.insert_edge(mesh_depth, vitem_color);
149            render_graph
150        });
151        let oit_resolve = render_graph.insert_node(OITResolveNode);
152        render_graph.insert_edge(clear, view_render);
153        render_graph.insert_edge(view_render, oit_resolve);
154        render_graph
155    }
156
157    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
158        Self::new_with_graph(ctx, width, height, oit_layers, Self::build_render_graph())
159    }
160
161    pub fn new_with_graph(
162        ctx: &WgpuContext,
163        width: u32,
164        height: u32,
165        oit_layers: usize,
166        render_graph: GlobalRenderGraph,
167    ) -> Self {
168        let resolution_info = ResolutionInfo::new(ctx, width, height, oit_layers);
169
170        #[cfg(feature = "profiling")]
171        let profiler = wgpu_profiler::GpuProfiler::new(
172            &ctx.device,
173            wgpu_profiler::GpuProfilerSettings::default(),
174        )
175        .unwrap();
176
177        Self {
178            width,
179            height,
180            resolution_info,
181            pipelines: PipelinesPool::default(),
182            packets: RenderPackets::default(),
183            render_graph,
184            merged_buffer: None,
185            merged_mesh_buffer: None,
186            #[cfg(feature = "profiling")]
187            profiler,
188        }
189    }
190
191    pub fn new_render_textures(&self, ctx: &WgpuContext) -> RenderTextures {
192        RenderTextures::new(ctx, self.width, self.height)
193    }
194
195    /// Render a frame. Pushes viewport + VItem packets via pool, then execs the render graph.
196    pub fn render_store_with_pool(
197        &mut self,
198        ctx: &WgpuContext,
199        render_textures: &mut RenderTextures,
200        clear_color: wgpu::Color,
201        store: &CoreItemStore,
202        pool: &mut RenderPool,
203    ) {
204        // Viewport — always needed
205        let camera_frame = &store.camera_frames[0];
206        let viewport = ViewportUniform::from_camera_frame(camera_frame, self.width, self.height);
207        self.packets.push(pool.alloc_packet(ctx, &viewport));
208
209        // Merged buffer (merged nodes read this; old nodes ignore it)
210        let merged = self
211            .merged_buffer
212            .get_or_insert_with(|| VItemsBuffer::new(ctx));
213        merged.update(ctx, &store.vitems);
214
215        // Merged mesh buffer
216        let merged_mesh = self
217            .merged_mesh_buffer
218            .get_or_insert_with(|| MeshItemsBuffer::new(ctx));
219        merged_mesh.update(ctx, &store.mesh_items);
220
221        // Encode & submit
222        {
223            #[cfg(feature = "profiling")]
224            profiling::scope!("render");
225
226            let mut encoder = ctx
227                .device
228                .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
229
230            {
231                #[cfg(feature = "profiling")]
232                let mut scope = self.profiler.scope("render", &mut encoder);
233
234                let render_ctx = RenderContext {
235                    pipelines: &self.pipelines,
236                    render_textures,
237                    render_packets: &self.packets,
238                    render_pool: pool,
239                    wgpu_ctx: ctx,
240                    resolution_info: &self.resolution_info,
241                    clear_color,
242                    merged_buffer: self.merged_buffer.as_ref(),
243                    merged_mesh_buffer: self.merged_mesh_buffer.as_ref(),
244                };
245
246                self.render_graph.exec(
247                    #[cfg(not(feature = "profiling"))]
248                    &mut encoder,
249                    #[cfg(feature = "profiling")]
250                    &mut scope,
251                    render_ctx,
252                );
253            }
254
255            #[cfg(not(feature = "profiling"))]
256            ctx.queue.submit(Some(encoder.finish()));
257
258            #[cfg(feature = "profiling")]
259            {
260                self.profiler.resolve_queries(&mut encoder);
261                {
262                    profiling::scope!("submit");
263                    ctx.queue.submit(Some(encoder.finish()));
264                }
265
266                self.profiler.end_frame().unwrap();
267
268                ctx.device
269                    .poll(wgpu::PollType::wait_indefinitely())
270                    .unwrap();
271                let latest_profiler_results = self
272                    .profiler
273                    .process_finished_frame(ctx.queue.get_timestamp_period());
274                let mut gpu_profiler = PUFFIN_GPU_PROFILER.lock().unwrap();
275                wgpu_profiler::puffin::output_frame_to_puffin(
276                    &mut gpu_profiler,
277                    &latest_profiler_results.unwrap(),
278                );
279                gpu_profiler.new_frame();
280            }
281
282            render_textures.mark_dirty();
283        }
284
285        self.packets.clear();
286    }
287}
288
289#[allow(unused)]
290pub struct ResolutionInfo {
291    buffer: WgpuBuffer<UVec3>,
292    pub(crate) pixel_count_buffer: WgpuVecBuffer<u32>,
293    oit_colors_buffer: WgpuVecBuffer<u32>,
294    oit_depths_buffer: WgpuVecBuffer<f32>,
295    bind_group: wgpu::BindGroup,
296}
297
298impl ResolutionInfo {
299    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
300        let buffer = WgpuBuffer::new_init(
301            ctx,
302            Some("ResolutionInfo Buffer"),
303            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
304            uvec3(width, height, oit_layers as u32),
305        );
306
307        let pixel_count = (width * height) as usize;
308        let total_nodes = pixel_count * oit_layers;
309
310        let pixel_count_buffer = WgpuVecBuffer::new(
311            ctx,
312            Some("OIT Pixel Count Buffer"),
313            wgpu::BufferUsages::STORAGE
314                | wgpu::BufferUsages::COPY_DST
315                | wgpu::BufferUsages::COPY_SRC,
316            pixel_count,
317        );
318        let oit_colors_buffer = WgpuVecBuffer::new(
319            ctx,
320            Some("OIT Colors Buffer"),
321            wgpu::BufferUsages::STORAGE
322                | wgpu::BufferUsages::COPY_SRC
323                | wgpu::BufferUsages::COPY_DST,
324            total_nodes,
325        );
326        let oit_depths_buffer = WgpuVecBuffer::new(
327            ctx,
328            Some("OIT Depths Buffer"),
329            wgpu::BufferUsages::STORAGE
330                | wgpu::BufferUsages::COPY_SRC
331                | wgpu::BufferUsages::COPY_DST,
332            total_nodes,
333        );
334
335        let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
336            label: Some("ResolutionInfo BindGroup"),
337            layout: &Self::create_bind_group_layout(ctx),
338            entries: &[
339                wgpu::BindGroupEntry {
340                    binding: 0,
341                    resource: buffer.as_ref().as_entire_binding(),
342                },
343                wgpu::BindGroupEntry {
344                    binding: 1,
345                    resource: wgpu::BindingResource::Buffer(
346                        pixel_count_buffer.buffer.as_entire_buffer_binding(),
347                    ),
348                },
349                wgpu::BindGroupEntry {
350                    binding: 2,
351                    resource: wgpu::BindingResource::Buffer(
352                        oit_colors_buffer.buffer.as_entire_buffer_binding(),
353                    ),
354                },
355                wgpu::BindGroupEntry {
356                    binding: 3,
357                    resource: wgpu::BindingResource::Buffer(
358                        oit_depths_buffer.buffer.as_entire_buffer_binding(),
359                    ),
360                },
361            ],
362        });
363
364        Self {
365            buffer,
366            bind_group,
367            oit_colors_buffer,
368            oit_depths_buffer,
369            pixel_count_buffer,
370        }
371    }
372    // This may never be used?
373    // pub fn update(&mut self, ctx: &WgpuContext, resolution: UVec2) {
374    //     self.buffer.set(ctx, resolution);
375
376    //     let pixel_count = (data.screen_size[0] * data.screen_size[1]) as usize;
377    //     let layers = data.oit_layers as usize;
378    //     let total_nodes = pixel_count * layers;
379
380    //     let mut bind_group_dirty = false;
381
382    //     if self.pixel_count_buffer.len() != pixel_count {
383    //         self.pixel_count_buffer.resize(ctx, pixel_count);
384    //         bind_group_dirty = true;
385    //     }
386
387    //     if self.oit_colors_buffer.len() != total_nodes {
388    //         self.oit_colors_buffer.resize(ctx, total_nodes);
389    //         bind_group_dirty = true;
390    //     }
391
392    //     if self.oit_depths_buffer.len() != total_nodes {
393    //         self.oit_depths_buffer.resize(ctx, total_nodes);
394    //         bind_group_dirty = true;
395    //     }
396
397    //     if bind_group_dirty {
398    //         self.uniforms_bind_group = ViewportBindGroup::new(
399    //             ctx,
400    //             &self.uniforms_buffer,
401    //             &self.pixel_count_buffer,
402    //             &self.oit_colors_buffer,
403    //             &self.oit_depths_buffer,
404    //         );
405    //     }
406    // }
407    pub fn create_bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout {
408        ctx.device
409            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
410                label: Some("ResolutionInfo BindGroupLayout"),
411                entries: &[
412                    wgpu::BindGroupLayoutEntry {
413                        binding: 0,
414                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT
415                            | wgpu::ShaderStages::COMPUTE,
416                        ty: wgpu::BindingType::Buffer {
417                            ty: wgpu::BufferBindingType::Uniform,
418                            has_dynamic_offset: false,
419                            min_binding_size: None,
420                        },
421                        count: None,
422                    },
423                    wgpu::BindGroupLayoutEntry {
424                        binding: 1,
425                        visibility: wgpu::ShaderStages::FRAGMENT,
426                        ty: wgpu::BindingType::Buffer {
427                            ty: wgpu::BufferBindingType::Storage { read_only: false },
428                            has_dynamic_offset: false,
429                            min_binding_size: None,
430                        },
431                        count: None,
432                    },
433                    wgpu::BindGroupLayoutEntry {
434                        binding: 2,
435                        visibility: wgpu::ShaderStages::FRAGMENT,
436                        ty: wgpu::BindingType::Buffer {
437                            ty: wgpu::BufferBindingType::Storage { read_only: false },
438                            has_dynamic_offset: false,
439                            min_binding_size: None,
440                        },
441                        count: None,
442                    },
443                    wgpu::BindGroupLayoutEntry {
444                        binding: 3,
445                        visibility: wgpu::ShaderStages::FRAGMENT,
446                        ty: wgpu::BindingType::Buffer {
447                            ty: wgpu::BufferBindingType::Storage { read_only: false },
448                            has_dynamic_offset: false,
449                            min_binding_size: None,
450                        },
451                        count: None,
452                    },
453                ],
454            })
455    }
456}