1#![recursion_limit = "256"]
3#![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)]
10pub mod cpu_probe;
13pub mod extract;
14pub mod pipelines;
16pub mod primitives;
18pub mod resource;
19pub mod schedule;
21mod setup;
22pub mod upload_probe;
25pub mod utils;
27pub mod world;
28
29use bevy_app::{App, PluginsState, SubApp};
30use bevy_derive::AppLabel;
31use bevy_ecs::prelude::*;
32use glam::{UVec3, uvec3};
33use ranim_core::frame::EvaluatedFrame;
34
35pub use setup::RenderSetup;
36
37use crate::{
38 primitives::viewport::{ViewportGpuPacket, ViewportUniform},
39 resource::{PipelinesPool, RenderTextures},
40 schedule::{FrameTarget, RenderDimensions, RenderProfiler, install_schedules},
41 utils::{WgpuBuffer, WgpuVecBuffer},
42};
43use utils::WgpuContext;
44
45pub mod profiling_utils {
46 use wgpu_profiler::GpuTimerQueryResult;
47
48 pub fn scopes_to_console_recursive(results: &[GpuTimerQueryResult], indentation: u32) {
50 for scope in results {
51 if indentation > 0 {
52 print!("{:<width$}", "|", width = 4);
53 }
54
55 if let Some(time) = &scope.time {
56 println!(
57 "{:.3}μs - {}",
58 (time.end - time.start) * 1000.0 * 1000.0,
59 scope.label
60 );
61 } else {
62 println!("n/a - {}", scope.label);
63 }
64
65 if !scope.nested_queries.is_empty() {
66 scopes_to_console_recursive(&scope.nested_queries, indentation + 1);
67 }
68 }
69 }
70}
71
72#[derive(AppLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub struct RanimRenderApp;
78
79pub fn build_render_app(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> SubApp {
87 let mut render_app = SubApp::new();
88 render_app
89 .insert_resource(ctx.clone())
90 .insert_resource(RenderDimensions { width, height })
91 .insert_resource(ResolutionInfo::new(ctx, width, height, oit_layers))
92 .insert_resource(ViewportGpuPacket::new(ctx, &ViewportUniform::default()))
93 .insert_resource(RenderProfiler::new(ctx))
94 .init_resource::<PipelinesPool>()
95 .init_resource::<EvaluatedFrame>();
96 install_schedules(&mut render_app);
97 render_app
98}
99
100pub fn install_render_app(
113 app: &mut App,
114 ctx: &WgpuContext,
115 width: u32,
116 height: u32,
117 oit_layers: usize,
118 setup: RenderSetup,
119) {
120 let mut render_app = build_render_app(ctx, width, height, oit_layers);
121 render_app.add_plugins(setup);
122 assert_eq!(
123 render_app.plugins_state(),
124 PluginsState::Ready,
125 "render plugins must be ready after synchronous installation"
126 );
127 render_app.finish();
128 render_app.cleanup();
129 render_app.set_extract(|main, render| {
130 let Some(mut frame) = main.get_resource_mut::<EvaluatedFrame>() else {
131 return;
132 };
133 std::mem::swap(&mut *frame, &mut *render.resource_mut::<EvaluatedFrame>());
134 });
135 app.insert_sub_app(RanimRenderApp, render_app);
136}
137
138pub trait RenderAppExt {
140 fn set_frame_target(&mut self, render_textures: &RenderTextures, clear_color: wgpu::Color);
145
146 fn render_frame(
152 &mut self,
153 render_textures: &mut RenderTextures,
154 clear_color: wgpu::Color,
155 frame: &mut EvaluatedFrame,
156 );
157
158 fn take_last_gpu_scopes(&mut self) -> Option<Vec<wgpu_profiler::GpuTimerQueryResult>>;
162
163 fn gpu_timers_supported(&self) -> bool;
165
166 fn gpu_timers_enabled(&self) -> bool;
168
169 fn set_gpu_timers_enabled(&self, on: bool);
172}
173
174impl RenderAppExt for SubApp {
175 fn set_frame_target(&mut self, render_textures: &RenderTextures, clear_color: wgpu::Color) {
176 self.insert_resource(FrameTarget::new(render_textures, clear_color));
177 }
178
179 fn render_frame(
180 &mut self,
181 render_textures: &mut RenderTextures,
182 clear_color: wgpu::Color,
183 frame: &mut EvaluatedFrame,
184 ) {
185 std::mem::swap(
186 frame,
187 &mut self.world_mut().resource_mut::<EvaluatedFrame>(),
188 );
189 self.insert_resource(FrameTarget::new(render_textures, clear_color));
190 self.update();
191 std::mem::swap(
192 frame,
193 &mut self.world_mut().resource_mut::<EvaluatedFrame>(),
194 );
195 }
196
197 fn take_last_gpu_scopes(&mut self) -> Option<Vec<wgpu_profiler::GpuTimerQueryResult>> {
198 self.world_mut()
199 .resource_mut::<RenderProfiler>()
200 .last_frame_scopes
201 .take()
202 }
203
204 fn gpu_timers_supported(&self) -> bool {
205 self.world().resource::<RenderProfiler>().inner.is_some()
206 }
207
208 fn gpu_timers_enabled(&self) -> bool {
209 self.world().resource::<RenderProfiler>().is_enabled()
210 }
211
212 fn set_gpu_timers_enabled(&self, on: bool) {
213 self.world().resource::<RenderProfiler>().set_enabled(on);
214 }
215}
216
217#[allow(unused)]
218#[derive(Resource)]
219pub struct ResolutionInfo {
220 buffer: WgpuBuffer<UVec3>,
221 pub(crate) pixel_count_buffer: WgpuVecBuffer<u32>,
222 oit_colors_buffer: WgpuVecBuffer<u32>,
223 oit_depths_buffer: WgpuVecBuffer<f32>,
224 bind_group: wgpu::BindGroup,
225}
226
227impl ResolutionInfo {
228 pub fn bind_group(&self) -> &wgpu::BindGroup {
230 &self.bind_group
231 }
232
233 pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self {
234 let buffer = WgpuBuffer::new_init(
235 ctx,
236 Some("ResolutionInfo Buffer"),
237 wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
238 uvec3(width, height, oit_layers as u32),
239 );
240
241 let pixel_count = (width * height) as usize;
242 let total_nodes = pixel_count * oit_layers;
243
244 let pixel_count_buffer = WgpuVecBuffer::new(
245 ctx,
246 Some("OIT Pixel Count Buffer"),
247 wgpu::BufferUsages::STORAGE
248 | wgpu::BufferUsages::COPY_DST
249 | wgpu::BufferUsages::COPY_SRC,
250 pixel_count,
251 );
252 let oit_colors_buffer = WgpuVecBuffer::new(
253 ctx,
254 Some("OIT Colors Buffer"),
255 wgpu::BufferUsages::STORAGE
256 | wgpu::BufferUsages::COPY_SRC
257 | wgpu::BufferUsages::COPY_DST,
258 total_nodes,
259 );
260 let oit_depths_buffer = WgpuVecBuffer::new(
261 ctx,
262 Some("OIT Depths Buffer"),
263 wgpu::BufferUsages::STORAGE
264 | wgpu::BufferUsages::COPY_SRC
265 | wgpu::BufferUsages::COPY_DST,
266 total_nodes,
267 );
268
269 let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
270 label: Some("ResolutionInfo BindGroup"),
271 layout: &Self::create_bind_group_layout(ctx),
272 entries: &[
273 wgpu::BindGroupEntry {
274 binding: 0,
275 resource: buffer.as_ref().as_entire_binding(),
276 },
277 wgpu::BindGroupEntry {
278 binding: 1,
279 resource: wgpu::BindingResource::Buffer(
280 pixel_count_buffer.buffer.as_entire_buffer_binding(),
281 ),
282 },
283 wgpu::BindGroupEntry {
284 binding: 2,
285 resource: wgpu::BindingResource::Buffer(
286 oit_colors_buffer.buffer.as_entire_buffer_binding(),
287 ),
288 },
289 wgpu::BindGroupEntry {
290 binding: 3,
291 resource: wgpu::BindingResource::Buffer(
292 oit_depths_buffer.buffer.as_entire_buffer_binding(),
293 ),
294 },
295 ],
296 });
297
298 Self {
299 buffer,
300 bind_group,
301 oit_colors_buffer,
302 oit_depths_buffer,
303 pixel_count_buffer,
304 }
305 }
306 pub fn create_bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout {
342 ctx.device
343 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
344 label: Some("ResolutionInfo BindGroupLayout"),
345 entries: &[
346 wgpu::BindGroupLayoutEntry {
347 binding: 0,
348 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT
349 | wgpu::ShaderStages::COMPUTE,
350 ty: wgpu::BindingType::Buffer {
351 ty: wgpu::BufferBindingType::Uniform,
352 has_dynamic_offset: false,
353 min_binding_size: None,
354 },
355 count: None,
356 },
357 wgpu::BindGroupLayoutEntry {
358 binding: 1,
359 visibility: wgpu::ShaderStages::FRAGMENT,
360 ty: wgpu::BindingType::Buffer {
361 ty: wgpu::BufferBindingType::Storage { read_only: false },
362 has_dynamic_offset: false,
363 min_binding_size: None,
364 },
365 count: None,
366 },
367 wgpu::BindGroupLayoutEntry {
368 binding: 2,
369 visibility: wgpu::ShaderStages::FRAGMENT,
370 ty: wgpu::BindingType::Buffer {
371 ty: wgpu::BufferBindingType::Storage { read_only: false },
372 has_dynamic_offset: false,
373 min_binding_size: None,
374 },
375 count: None,
376 },
377 wgpu::BindGroupLayoutEntry {
378 binding: 3,
379 visibility: wgpu::ShaderStages::FRAGMENT,
380 ty: wgpu::BindingType::Buffer {
381 ty: wgpu::BufferBindingType::Storage { read_only: false },
382 has_dynamic_offset: false,
383 min_binding_size: None,
384 },
385 count: None,
386 },
387 ],
388 })
389 }
390}