Skip to main content

ranim_render/
lib.rs

1//! Rendering stuff in ranim
2#![recursion_limit = "256"]
3// #![warn(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![allow(rustdoc::private_intra_doc_links)]
6#![doc(
7    html_logo_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg",
8    html_favicon_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg"
9)]
10/// Lightweight CPU timing spans, drained per frame by the preview
11/// profiler panel.
12pub mod cpu_probe;
13/// The pipelines
14pub mod pipelines;
15/// The basic renderable structs
16pub mod primitives;
17pub mod resource;
18mod schedule;
19/// Upload instrumentation and experimental upload strategies
20/// (enabled via `RANIM_PROFILE_UPLOAD`).
21pub mod upload_probe;
22/// Rendering related utils
23pub mod utils;
24pub mod world;
25
26use bevy_ecs::prelude::*;
27use glam::{UVec3, uvec3};
28
29use crate::{
30    primitives::{mesh_items::MeshItemsBuffer, viewport::ViewportUniform, vitems::VItemsBuffer},
31    resource::{PipelinesPool, RenderTextures},
32    schedule::{FrameTarget, RenderDimensions, RenderGraph, RenderPrepare, install_schedules},
33    utils::{WgpuBuffer, WgpuVecBuffer},
34    world::{CoreItemEntities, RenderFrame, reconcile},
35};
36use utils::WgpuContext;
37
38pub mod profiling_utils {
39    use wgpu_profiler::GpuTimerQueryResult;
40
41    /// Print a `GpuTimerQueryResult` tree (label + duration) to stdout.
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
65// MARK: Renderer
66pub struct Renderer {
67    width: u32,
68    height: u32,
69    world: World,
70}
71
72impl Renderer {
73    pub fn width(&self) -> u32 {
74        self.width
75    }
76
77    pub fn height(&self) -> u32 {
78        self.height
79    }
80
81    pub fn ratio(&self) -> f32 {
82        self.width as f32 / self.height as f32
83    }
84
85    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
86        let mut world = World::new();
87        world.insert_resource(ctx.clone());
88        world.insert_resource(RenderDimensions { width, height });
89        world.insert_resource(ResolutionInfo::new(ctx, width, height, oit_layers));
90        world.init_resource::<PipelinesPool>();
91        world.insert_resource(VItemsBuffer::new(ctx));
92        world.insert_resource(MeshItemsBuffer::new(ctx));
93        world.insert_resource(primitives::viewport::ViewportGpuPacket::new(
94            ctx,
95            &ViewportUniform::from_camera_frame(
96                &Default::default(),
97                width,
98                height,
99                primitives::viewport::DEPTH_ORDER_SPAN,
100            ),
101        ));
102        world.init_resource::<CoreItemEntities>();
103        world.insert_resource(schedule::RenderProfiler::new(ctx));
104        install_schedules(&mut world);
105
106        Self {
107            width,
108            height,
109            world,
110        }
111    }
112
113    pub fn new_render_textures(&self, ctx: &WgpuContext) -> RenderTextures {
114        RenderTextures::new(ctx, self.width, self.height)
115    }
116
117    /// Reconcile and render one evaluated frame.
118    pub fn render_frame(
119        &mut self,
120        render_textures: &mut RenderTextures,
121        clear_color: wgpu::Color,
122        frame: &RenderFrame,
123    ) {
124        {
125            let _span = cpu_probe::span("reconcile");
126            reconcile(&mut self.world, frame);
127        }
128        self.world
129            .insert_resource(FrameTarget::new(render_textures, clear_color));
130        self.world.run_schedule(RenderPrepare);
131        {
132            let _span = cpu_probe::span("render_graph");
133            self.world.run_schedule(RenderGraph);
134        }
135    }
136
137    /// Take the GPU timer scopes recorded for the most recent processed
138    /// frame (empty unless GPU timers are enabled and the device supports
139    /// timestamp queries).
140    pub fn take_last_gpu_scopes(&mut self) -> Option<Vec<wgpu_profiler::GpuTimerQueryResult>> {
141        self.world
142            .resource_mut::<schedule::RenderProfiler>()
143            .last_frame_scopes
144            .take()
145    }
146
147    /// Whether the device supports GPU timestamp queries.
148    pub fn gpu_timers_supported(&self) -> bool {
149        self.world
150            .resource::<schedule::RenderProfiler>()
151            .inner
152            .is_some()
153    }
154
155    /// Whether GPU timer scopes are currently being recorded (see
156    /// [`Renderer::set_gpu_timers_enabled`]).
157    pub fn gpu_timers_enabled(&self) -> bool {
158        self.world
159            .resource::<schedule::RenderProfiler>()
160            .is_enabled()
161    }
162
163    /// Enable/disable GPU timer recording at runtime. While enabled, each
164    /// frame pays a device poll to read the timers back.
165    pub fn set_gpu_timers_enabled(&mut self, on: bool) {
166        self.world
167            .resource_mut::<schedule::RenderProfiler>()
168            .set_enabled(on);
169    }
170}
171
172#[allow(unused)]
173#[derive(Resource)]
174pub struct ResolutionInfo {
175    buffer: WgpuBuffer<UVec3>,
176    pub(crate) pixel_count_buffer: WgpuVecBuffer<u32>,
177    oit_colors_buffer: WgpuVecBuffer<u32>,
178    oit_depths_buffer: WgpuVecBuffer<f32>,
179    bind_group: wgpu::BindGroup,
180}
181
182impl ResolutionInfo {
183    pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
184        let buffer = WgpuBuffer::new_init(
185            ctx,
186            Some("ResolutionInfo Buffer"),
187            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
188            uvec3(width, height, oit_layers as u32),
189        );
190
191        let pixel_count = (width * height) as usize;
192        let total_nodes = pixel_count * oit_layers;
193
194        let pixel_count_buffer = WgpuVecBuffer::new(
195            ctx,
196            Some("OIT Pixel Count Buffer"),
197            wgpu::BufferUsages::STORAGE
198                | wgpu::BufferUsages::COPY_DST
199                | wgpu::BufferUsages::COPY_SRC,
200            pixel_count,
201        );
202        let oit_colors_buffer = WgpuVecBuffer::new(
203            ctx,
204            Some("OIT Colors Buffer"),
205            wgpu::BufferUsages::STORAGE
206                | wgpu::BufferUsages::COPY_SRC
207                | wgpu::BufferUsages::COPY_DST,
208            total_nodes,
209        );
210        let oit_depths_buffer = WgpuVecBuffer::new(
211            ctx,
212            Some("OIT Depths Buffer"),
213            wgpu::BufferUsages::STORAGE
214                | wgpu::BufferUsages::COPY_SRC
215                | wgpu::BufferUsages::COPY_DST,
216            total_nodes,
217        );
218
219        let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
220            label: Some("ResolutionInfo BindGroup"),
221            layout: &Self::create_bind_group_layout(ctx),
222            entries: &[
223                wgpu::BindGroupEntry {
224                    binding: 0,
225                    resource: buffer.as_ref().as_entire_binding(),
226                },
227                wgpu::BindGroupEntry {
228                    binding: 1,
229                    resource: wgpu::BindingResource::Buffer(
230                        pixel_count_buffer.buffer.as_entire_buffer_binding(),
231                    ),
232                },
233                wgpu::BindGroupEntry {
234                    binding: 2,
235                    resource: wgpu::BindingResource::Buffer(
236                        oit_colors_buffer.buffer.as_entire_buffer_binding(),
237                    ),
238                },
239                wgpu::BindGroupEntry {
240                    binding: 3,
241                    resource: wgpu::BindingResource::Buffer(
242                        oit_depths_buffer.buffer.as_entire_buffer_binding(),
243                    ),
244                },
245            ],
246        });
247
248        Self {
249            buffer,
250            bind_group,
251            oit_colors_buffer,
252            oit_depths_buffer,
253            pixel_count_buffer,
254        }
255    }
256    // This may never be used?
257    // pub fn update(&mut self, ctx: &WgpuContext, resolution: UVec2) {
258    //     self.buffer.set(ctx, resolution);
259
260    //     let pixel_count = (data.screen_size[0] * data.screen_size[1]) as usize;
261    //     let layers = data.oit_layers as usize;
262    //     let total_nodes = pixel_count * layers;
263
264    //     let mut bind_group_dirty = false;
265
266    //     if self.pixel_count_buffer.len() != pixel_count {
267    //         self.pixel_count_buffer.resize(ctx, pixel_count);
268    //         bind_group_dirty = true;
269    //     }
270
271    //     if self.oit_colors_buffer.len() != total_nodes {
272    //         self.oit_colors_buffer.resize(ctx, total_nodes);
273    //         bind_group_dirty = true;
274    //     }
275
276    //     if self.oit_depths_buffer.len() != total_nodes {
277    //         self.oit_depths_buffer.resize(ctx, total_nodes);
278    //         bind_group_dirty = true;
279    //     }
280
281    //     if bind_group_dirty {
282    //         self.uniforms_bind_group = ViewportBindGroup::new(
283    //             ctx,
284    //             &self.uniforms_buffer,
285    //             &self.pixel_count_buffer,
286    //             &self.oit_colors_buffer,
287    //             &self.oit_depths_buffer,
288    //         );
289    //     }
290    // }
291    pub fn create_bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout {
292        ctx.device
293            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
294                label: Some("ResolutionInfo BindGroupLayout"),
295                entries: &[
296                    wgpu::BindGroupLayoutEntry {
297                        binding: 0,
298                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT
299                            | wgpu::ShaderStages::COMPUTE,
300                        ty: wgpu::BindingType::Buffer {
301                            ty: wgpu::BufferBindingType::Uniform,
302                            has_dynamic_offset: false,
303                            min_binding_size: None,
304                        },
305                        count: None,
306                    },
307                    wgpu::BindGroupLayoutEntry {
308                        binding: 1,
309                        visibility: wgpu::ShaderStages::FRAGMENT,
310                        ty: wgpu::BindingType::Buffer {
311                            ty: wgpu::BufferBindingType::Storage { read_only: false },
312                            has_dynamic_offset: false,
313                            min_binding_size: None,
314                        },
315                        count: None,
316                    },
317                    wgpu::BindGroupLayoutEntry {
318                        binding: 2,
319                        visibility: wgpu::ShaderStages::FRAGMENT,
320                        ty: wgpu::BindingType::Buffer {
321                            ty: wgpu::BufferBindingType::Storage { read_only: false },
322                            has_dynamic_offset: false,
323                            min_binding_size: None,
324                        },
325                        count: None,
326                    },
327                    wgpu::BindGroupLayoutEntry {
328                        binding: 3,
329                        visibility: wgpu::ShaderStages::FRAGMENT,
330                        ty: wgpu::BindingType::Buffer {
331                            ty: wgpu::BufferBindingType::Storage { read_only: false },
332                            has_dynamic_offset: false,
333                            min_binding_size: None,
334                        },
335                        count: None,
336                    },
337                ],
338            })
339    }
340}