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    /// Create a new renderer with default [`Self::build_render_graph`]
158    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
159        Self::new_with_graph(ctx, width, height, oit_layers, Self::build_render_graph())
160    }
161
162    /// Create a new renderer with the given render graph
163    pub fn new_with_graph(
164        ctx: &WgpuContext,
165        width: u32,
166        height: u32,
167        oit_layers: usize,
168        render_graph: GlobalRenderGraph,
169    ) -> Self {
170        let resolution_info = ResolutionInfo::new(ctx, width, height, oit_layers);
171
172        #[cfg(feature = "profiling")]
173        let profiler = wgpu_profiler::GpuProfiler::new(
174            &ctx.device,
175            wgpu_profiler::GpuProfilerSettings::default(),
176        )
177        .unwrap();
178
179        Self {
180            width,
181            height,
182            resolution_info,
183            pipelines: PipelinesPool::default(),
184            packets: RenderPackets::default(),
185            render_graph,
186            merged_buffer: None,
187            merged_mesh_buffer: None,
188            #[cfg(feature = "profiling")]
189            profiler,
190        }
191    }
192
193    pub fn new_render_textures(&self, ctx: &WgpuContext) -> RenderTextures {
194        RenderTextures::new(ctx, self.width, self.height)
195    }
196
197    /// Render a frame. Pushes viewport + VItem packets via pool, then execs the render graph.
198    pub fn render_store_with_pool(
199        &mut self,
200        ctx: &WgpuContext,
201        render_textures: &mut RenderTextures,
202        clear_color: wgpu::Color,
203        store: &CoreItemStore,
204        pool: &mut RenderPool,
205    ) {
206        // Viewport — always needed
207        let camera_frame = &store.camera_frames[0];
208        let viewport = ViewportUniform::from_camera_frame(camera_frame, self.width, self.height);
209        self.packets.push(pool.alloc_packet(ctx, &viewport));
210
211        // Merged buffer (merged nodes read this; old nodes ignore it)
212        let merged = self
213            .merged_buffer
214            .get_or_insert_with(|| VItemsBuffer::new(ctx));
215        merged.update(ctx, &store.vitems);
216
217        // Merged mesh buffer
218        let merged_mesh = self
219            .merged_mesh_buffer
220            .get_or_insert_with(|| MeshItemsBuffer::new(ctx));
221        merged_mesh.update(ctx, &store.mesh_items);
222
223        // Encode & submit
224        {
225            #[cfg(feature = "profiling")]
226            profiling::scope!("render");
227
228            let mut encoder = ctx
229                .device
230                .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
231
232            {
233                #[cfg(feature = "profiling")]
234                let mut scope = self.profiler.scope("render", &mut encoder);
235
236                let render_ctx = RenderContext {
237                    pipelines: &self.pipelines,
238                    render_textures,
239                    render_packets: &self.packets,
240                    render_pool: pool,
241                    wgpu_ctx: ctx,
242                    resolution_info: &self.resolution_info,
243                    clear_color,
244                    merged_buffer: self.merged_buffer.as_ref(),
245                    merged_mesh_buffer: self.merged_mesh_buffer.as_ref(),
246                };
247
248                self.render_graph.exec(
249                    #[cfg(not(feature = "profiling"))]
250                    &mut encoder,
251                    #[cfg(feature = "profiling")]
252                    &mut scope,
253                    render_ctx,
254                );
255            }
256
257            #[cfg(not(feature = "profiling"))]
258            ctx.queue.submit(Some(encoder.finish()));
259
260            #[cfg(feature = "profiling")]
261            {
262                self.profiler.resolve_queries(&mut encoder);
263                {
264                    profiling::scope!("submit");
265                    ctx.queue.submit(Some(encoder.finish()));
266                }
267
268                self.profiler.end_frame().unwrap();
269
270                ctx.device
271                    .poll(wgpu::PollType::wait_indefinitely())
272                    .unwrap();
273                let latest_profiler_results = self
274                    .profiler
275                    .process_finished_frame(ctx.queue.get_timestamp_period());
276                let mut gpu_profiler = PUFFIN_GPU_PROFILER.lock().unwrap();
277                wgpu_profiler::puffin::output_frame_to_puffin(
278                    &mut gpu_profiler,
279                    &latest_profiler_results.unwrap(),
280                );
281                gpu_profiler.new_frame();
282            }
283
284            render_textures.mark_dirty();
285        }
286
287        self.packets.clear();
288    }
289}
290
291#[allow(unused)]
292pub struct ResolutionInfo {
293    buffer: WgpuBuffer<UVec3>,
294    pub(crate) pixel_count_buffer: WgpuVecBuffer<u32>,
295    oit_colors_buffer: WgpuVecBuffer<u32>,
296    oit_depths_buffer: WgpuVecBuffer<f32>,
297    bind_group: wgpu::BindGroup,
298}
299
300impl ResolutionInfo {
301    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
302        let buffer = WgpuBuffer::new_init(
303            ctx,
304            Some("ResolutionInfo Buffer"),
305            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
306            uvec3(width, height, oit_layers as u32),
307        );
308
309        let pixel_count = (width * height) as usize;
310        let total_nodes = pixel_count * oit_layers;
311
312        let pixel_count_buffer = WgpuVecBuffer::new(
313            ctx,
314            Some("OIT Pixel Count Buffer"),
315            wgpu::BufferUsages::STORAGE
316                | wgpu::BufferUsages::COPY_DST
317                | wgpu::BufferUsages::COPY_SRC,
318            pixel_count,
319        );
320        let oit_colors_buffer = WgpuVecBuffer::new(
321            ctx,
322            Some("OIT Colors Buffer"),
323            wgpu::BufferUsages::STORAGE
324                | wgpu::BufferUsages::COPY_SRC
325                | wgpu::BufferUsages::COPY_DST,
326            total_nodes,
327        );
328        let oit_depths_buffer = WgpuVecBuffer::new(
329            ctx,
330            Some("OIT Depths Buffer"),
331            wgpu::BufferUsages::STORAGE
332                | wgpu::BufferUsages::COPY_SRC
333                | wgpu::BufferUsages::COPY_DST,
334            total_nodes,
335        );
336
337        let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
338            label: Some("ResolutionInfo BindGroup"),
339            layout: &Self::create_bind_group_layout(ctx),
340            entries: &[
341                wgpu::BindGroupEntry {
342                    binding: 0,
343                    resource: buffer.as_ref().as_entire_binding(),
344                },
345                wgpu::BindGroupEntry {
346                    binding: 1,
347                    resource: wgpu::BindingResource::Buffer(
348                        pixel_count_buffer.buffer.as_entire_buffer_binding(),
349                    ),
350                },
351                wgpu::BindGroupEntry {
352                    binding: 2,
353                    resource: wgpu::BindingResource::Buffer(
354                        oit_colors_buffer.buffer.as_entire_buffer_binding(),
355                    ),
356                },
357                wgpu::BindGroupEntry {
358                    binding: 3,
359                    resource: wgpu::BindingResource::Buffer(
360                        oit_depths_buffer.buffer.as_entire_buffer_binding(),
361                    ),
362                },
363            ],
364        });
365
366        Self {
367            buffer,
368            bind_group,
369            oit_colors_buffer,
370            oit_depths_buffer,
371            pixel_count_buffer,
372        }
373    }
374    // This may never be used?
375    // pub fn update(&mut self, ctx: &WgpuContext, resolution: UVec2) {
376    //     self.buffer.set(ctx, resolution);
377
378    //     let pixel_count = (data.screen_size[0] * data.screen_size[1]) as usize;
379    //     let layers = data.oit_layers as usize;
380    //     let total_nodes = pixel_count * layers;
381
382    //     let mut bind_group_dirty = false;
383
384    //     if self.pixel_count_buffer.len() != pixel_count {
385    //         self.pixel_count_buffer.resize(ctx, pixel_count);
386    //         bind_group_dirty = true;
387    //     }
388
389    //     if self.oit_colors_buffer.len() != total_nodes {
390    //         self.oit_colors_buffer.resize(ctx, total_nodes);
391    //         bind_group_dirty = true;
392    //     }
393
394    //     if self.oit_depths_buffer.len() != total_nodes {
395    //         self.oit_depths_buffer.resize(ctx, total_nodes);
396    //         bind_group_dirty = true;
397    //     }
398
399    //     if bind_group_dirty {
400    //         self.uniforms_bind_group = ViewportBindGroup::new(
401    //             ctx,
402    //             &self.uniforms_buffer,
403    //             &self.pixel_count_buffer,
404    //             &self.oit_colors_buffer,
405    //             &self.oit_depths_buffer,
406    //         );
407    //     }
408    // }
409    pub fn create_bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout {
410        ctx.device
411            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
412                label: Some("ResolutionInfo BindGroupLayout"),
413                entries: &[
414                    wgpu::BindGroupLayoutEntry {
415                        binding: 0,
416                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT
417                            | wgpu::ShaderStages::COMPUTE,
418                        ty: wgpu::BindingType::Buffer {
419                            ty: wgpu::BufferBindingType::Uniform,
420                            has_dynamic_offset: false,
421                            min_binding_size: None,
422                        },
423                        count: None,
424                    },
425                    wgpu::BindGroupLayoutEntry {
426                        binding: 1,
427                        visibility: wgpu::ShaderStages::FRAGMENT,
428                        ty: wgpu::BindingType::Buffer {
429                            ty: wgpu::BufferBindingType::Storage { read_only: false },
430                            has_dynamic_offset: false,
431                            min_binding_size: None,
432                        },
433                        count: None,
434                    },
435                    wgpu::BindGroupLayoutEntry {
436                        binding: 2,
437                        visibility: wgpu::ShaderStages::FRAGMENT,
438                        ty: wgpu::BindingType::Buffer {
439                            ty: wgpu::BufferBindingType::Storage { read_only: false },
440                            has_dynamic_offset: false,
441                            min_binding_size: None,
442                        },
443                        count: None,
444                    },
445                    wgpu::BindGroupLayoutEntry {
446                        binding: 3,
447                        visibility: wgpu::ShaderStages::FRAGMENT,
448                        ty: wgpu::BindingType::Buffer {
449                            ty: wgpu::BufferBindingType::Storage { read_only: false },
450                            has_dynamic_offset: false,
451                            min_binding_size: None,
452                        },
453                        count: None,
454                    },
455                ],
456            })
457    }
458}