1use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemParam};
2use ranim_core::core_item::{camera_frame::CameraFrame, mesh_item::MeshItem, vitem::VItem};
3
4use crate::{
5 pipelines::{mesh_item, oit_resolve, vitem},
6 primitives::{
7 mesh_items::MeshItemsBuffer,
8 viewport::{ViewportGpuPacket, ViewportUniform},
9 vitems::VItemsBuffer,
10 },
11 resource::{RenderTextureState, RenderTextures},
12 utils::WgpuContext,
13 world::SceneOrder,
14};
15
16#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
17pub(crate) struct RenderPrepare;
18
19#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct RenderGraph;
21
22#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
23struct ViewRender;
24
25#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
26enum PrepareSystems {
27 Collect,
28 PrepareResources,
29 Upload,
30 PrepareBindGroups,
31}
32
33#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
34enum RenderGraphSystems {
35 Begin,
36 Render,
37 Submit,
38 Finish,
39}
40
41#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
42enum ViewSystems {
43 Clear,
44 Compute,
45 Depth,
46 Color,
47 Resolve,
48}
49
50#[derive(Resource, Clone, Copy)]
51pub(crate) struct RenderDimensions {
52 pub width: u32,
53 pub height: u32,
54}
55
56#[derive(Resource)]
57pub(crate) struct FrameTarget {
58 pub(crate) render_view: wgpu::TextureView,
59 pub(crate) depth_stencil_view: wgpu::TextureView,
60 pub(crate) depth_bind_group: wgpu::BindGroup,
61 clear_color: wgpu::Color,
62 texture_state: RenderTextureState,
63}
64
65impl FrameTarget {
66 pub(crate) fn new(textures: &RenderTextures, clear_color: wgpu::Color) -> Self {
67 Self {
68 render_view: textures.render_view.clone(),
69 depth_stencil_view: textures.depth_stencil_view.clone(),
70 depth_bind_group: textures.depth_bind_group.clone(),
71 clear_color,
72 texture_state: textures.state(),
73 }
74 }
75}
76
77#[derive(Resource, Default)]
78struct FrameEncoder(Option<wgpu::CommandEncoder>);
79
80#[derive(SystemParam)]
81pub(crate) struct RenderContext<'w> {
82 encoder: ResMut<'w, FrameEncoder>,
83}
84
85impl RenderContext<'_> {
86 pub(crate) fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
87 self.encoder
88 .0
89 .as_mut()
90 .expect("frame encoder was not initialized")
91 }
92}
93
94#[derive(Resource)]
102pub(crate) struct RenderProfiler {
103 pub(crate) inner: Option<wgpu_profiler::GpuProfiler>,
104 enabled: std::sync::atomic::AtomicBool,
105 pub(crate) last_frame_scopes: Option<Vec<wgpu_profiler::GpuTimerQueryResult>>,
107}
108
109impl RenderProfiler {
110 pub(crate) fn new(_ctx: &WgpuContext) -> Self {
111 Self {
112 inner: wgpu_profiler::GpuProfiler::new(
113 &_ctx.device,
114 wgpu_profiler::GpuProfilerSettings::default(),
115 )
116 .ok(),
117 enabled: std::sync::atomic::AtomicBool::new(
118 std::env::var("RANIM_PROFILE_GPU").is_ok_and(|v| v != "0" && !v.is_empty()),
119 ),
120 last_frame_scopes: None,
121 }
122 }
123
124 pub(crate) fn is_enabled(&self) -> bool {
125 self.enabled.load(std::sync::atomic::Ordering::Relaxed)
126 }
127
128 pub(crate) fn set_enabled(&self, on: bool) {
129 self.enabled.store(on, std::sync::atomic::Ordering::Relaxed);
130 }
131
132 pub(crate) fn scope_pass<R, T>(
135 &self,
136 label: &str,
137 pass: &mut R,
138 f: impl FnOnce(&mut R) -> T,
139 ) -> T
140 where
141 R: wgpu_profiler::ProfilerCommandRecorder,
142 {
143 if !self.is_enabled() {
144 return f(pass);
145 }
146 let Some(profiler) = self.inner.as_ref() else {
147 return f(pass);
148 };
149 let mut scope = profiler.scope(label.to_string(), pass);
150 f(&mut *scope)
151 }
152}
153
154pub(crate) fn install_schedules(world: &mut World) {
155 world.init_resource::<FrameEncoder>();
156 let mut prepare = Schedule::new(RenderPrepare);
157 prepare.configure_sets(
158 (
159 PrepareSystems::Collect,
160 PrepareSystems::PrepareResources,
161 PrepareSystems::Upload,
162 PrepareSystems::PrepareBindGroups,
163 )
164 .chain(),
165 );
166 prepare.add_systems(
167 (prepare_vitems, prepare_mesh_items)
168 .in_set(PrepareSystems::PrepareResources)
169 .ambiguous_with_all(),
170 );
171 world.add_schedule(prepare);
172
173 let mut view = Schedule::new(ViewRender);
174 view.configure_sets(
175 (
176 ViewSystems::Clear,
177 ViewSystems::Compute,
178 ViewSystems::Depth,
179 ViewSystems::Color,
180 ViewSystems::Resolve,
181 )
182 .chain(),
183 );
184 view.add_systems(clear.in_set(ViewSystems::Clear));
185 view.add_systems(vitem::compute.in_set(ViewSystems::Compute));
186 view.add_systems(
187 (vitem::depth, mesh_item::depth)
188 .chain()
189 .in_set(ViewSystems::Depth),
190 );
191 view.add_systems(
192 (vitem::color, mesh_item::color)
193 .chain()
194 .in_set(ViewSystems::Color),
195 );
196 view.add_systems(oit_resolve::resolve.in_set(ViewSystems::Resolve));
197 world.add_schedule(view);
198
199 let mut graph = Schedule::new(RenderGraph);
200 graph.configure_sets(
201 (
202 RenderGraphSystems::Begin,
203 RenderGraphSystems::Render,
204 RenderGraphSystems::Submit,
205 RenderGraphSystems::Finish,
206 )
207 .chain(),
208 );
209 graph.add_systems(begin_frame.in_set(RenderGraphSystems::Begin));
210 graph.add_systems(view_driver.in_set(RenderGraphSystems::Render));
211 graph.add_systems(submit.in_set(RenderGraphSystems::Submit));
212 graph.add_systems(finish_frame.in_set(RenderGraphSystems::Finish));
213 world.add_schedule(graph);
214}
215
216fn prepare_vitems(
217 ctx: Res<WgpuContext>,
218 mut buffer: ResMut<VItemsBuffer>,
219 items: Query<(&SceneOrder, &VItem)>,
220) {
221 let _span = crate::cpu_probe::span("prepare_vitems");
222 let mut items = items.iter().collect::<Vec<_>>();
223 items.sort_by_key(|(order, _)| order.0);
224 buffer.update(
225 &ctx,
226 items.iter().map(|&(order, item)| (order.0 as f32, item)),
227 );
228}
229
230fn prepare_mesh_items(
231 ctx: Res<WgpuContext>,
232 mut buffer: ResMut<MeshItemsBuffer>,
233 items: Query<(&SceneOrder, &MeshItem)>,
234) {
235 let _span = crate::cpu_probe::span("prepare_mesh_items");
236 let mut items = items.iter().collect::<Vec<_>>();
237 items.sort_by_key(|(order, _)| order.0);
238 buffer.update(
239 &ctx,
240 items.iter().map(|&(order, item)| (order.0 as f32, item)),
241 );
242}
243
244fn begin_frame(ctx: Res<WgpuContext>, mut encoder: ResMut<FrameEncoder>) {
245 encoder.0 = Some(
246 ctx.device
247 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()),
248 );
249}
250
251fn clear(mut render: RenderContext, target: Res<FrameTarget>, profiler: Res<RenderProfiler>) {
252 let pass_desc = wgpu::RenderPassDescriptor {
253 label: Some("Clear Pass"),
254 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
255 depth_slice: None,
256 view: &target.render_view,
257 resolve_target: None,
258 ops: wgpu::Operations {
259 load: wgpu::LoadOp::Clear(target.clear_color),
260 store: wgpu::StoreOp::Store,
261 },
262 })],
263 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
264 view: &target.depth_stencil_view,
265 depth_ops: Some(wgpu::Operations {
266 load: wgpu::LoadOp::Clear(1.0),
267 store: wgpu::StoreOp::Store,
268 }),
269 stencil_ops: None,
270 }),
271 timestamp_writes: None,
272 occlusion_query_set: None,
273 multiview_mask: None,
274 };
275 let encoder = render.encoder();
276 let mut pass = encoder.begin_render_pass(&pass_desc);
277 profiler.scope_pass("clear", &mut pass, |_| ());
278}
279
280fn view_driver(world: &mut World) {
281 let mut cameras = world
282 .query::<(&SceneOrder, &CameraFrame)>()
283 .iter(world)
284 .map(|(order, camera)| (order.0, camera.clone()))
285 .collect::<Vec<_>>();
286 cameras.sort_by_key(|(order, _)| *order);
287
288 let camera = take_single_camera(cameras);
289 let dimensions = *world.resource::<RenderDimensions>();
290 let item_count = world.resource::<VItemsBuffer>().item_count()
293 + world.resource::<MeshItemsBuffer>().item_count();
294 let bias_epsilon = crate::primitives::viewport::DEPTH_ORDER_SPAN / item_count.max(1) as f32;
295 let uniform = ViewportUniform::from_camera_frame(
296 &camera,
297 dimensions.width,
298 dimensions.height,
299 bias_epsilon,
300 );
301 world.resource_scope(|world, mut viewport: Mut<ViewportGpuPacket>| {
302 viewport.update(world.resource::<WgpuContext>(), &uniform);
303 });
304 world.run_schedule(ViewRender);
305}
306
307fn take_single_camera(mut cameras: Vec<(usize, CameraFrame)>) -> CameraFrame {
308 assert_eq!(
309 cameras.len(),
310 1,
311 "D0002 requires exactly one active CameraFrame per rendered frame"
312 );
313 cameras.pop().unwrap().1
314}
315
316fn submit(
317 ctx: Res<WgpuContext>,
318 mut encoder: ResMut<FrameEncoder>,
319 mut profiler: ResMut<RenderProfiler>,
320) {
321 let mut encoder = encoder.0.take().expect("frame encoder was not initialized");
322 if profiler.is_enabled()
323 && let Some(inner) = profiler.inner.as_mut()
324 {
325 inner.resolve_queries(&mut encoder);
326 }
327 ctx.queue.submit(Some(encoder.finish()));
328}
329
330fn finish_frame(
331 target: Res<FrameTarget>,
332 ctx: Res<WgpuContext>,
333 mut profiler: ResMut<RenderProfiler>,
334) {
335 if profiler.is_enabled()
338 && let Some(inner) = profiler.inner.as_mut()
339 && inner.end_frame().is_ok()
340 {
341 ctx.device
342 .poll(wgpu::PollType::wait_indefinitely())
343 .unwrap();
344 if let Some(results) = inner.process_finished_frame(ctx.queue.get_timestamp_period()) {
345 profiler.last_frame_scopes = Some(results);
346 }
347 }
348 target.texture_state.mark_dirty();
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn single_camera_is_accepted() {
357 let camera = CameraFrame::default();
358
359 assert_eq!(take_single_camera(vec![(7, camera.clone())]), camera);
360 }
361
362 #[test]
363 #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
364 fn missing_camera_is_rejected() {
365 take_single_camera(Vec::new());
366 }
367
368 #[test]
369 #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
370 fn multiple_cameras_are_rejected() {
371 take_single_camera(vec![
372 (0, CameraFrame::default()),
373 (1, CameraFrame::default()),
374 ]);
375 }
376}