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#[cfg(feature = "profiling")]
95#[derive(Resource)]
96pub(crate) struct RenderProfiler(pub wgpu_profiler::GpuProfiler);
97
98pub(crate) fn install_schedules(world: &mut World) {
99 world.init_resource::<FrameEncoder>();
100 let mut prepare = Schedule::new(RenderPrepare);
101 prepare.configure_sets(
102 (
103 PrepareSystems::Collect,
104 PrepareSystems::PrepareResources,
105 PrepareSystems::Upload,
106 PrepareSystems::PrepareBindGroups,
107 )
108 .chain(),
109 );
110 prepare.add_systems(
111 (prepare_vitems, prepare_mesh_items)
112 .in_set(PrepareSystems::PrepareResources)
113 .ambiguous_with_all(),
114 );
115 world.add_schedule(prepare);
116
117 let mut view = Schedule::new(ViewRender);
118 view.configure_sets(
119 (
120 ViewSystems::Clear,
121 ViewSystems::Compute,
122 ViewSystems::Depth,
123 ViewSystems::Color,
124 ViewSystems::Resolve,
125 )
126 .chain(),
127 );
128 view.add_systems(clear.in_set(ViewSystems::Clear));
129 view.add_systems(vitem::compute.in_set(ViewSystems::Compute));
130 view.add_systems((vitem::depth, mesh_item::depth).in_set(ViewSystems::Depth));
131 view.add_systems((vitem::color, mesh_item::color).in_set(ViewSystems::Color));
132 view.add_systems(oit_resolve::resolve.in_set(ViewSystems::Resolve));
133 world.add_schedule(view);
134
135 let mut graph = Schedule::new(RenderGraph);
136 graph.configure_sets(
137 (
138 RenderGraphSystems::Begin,
139 RenderGraphSystems::Render,
140 RenderGraphSystems::Submit,
141 RenderGraphSystems::Finish,
142 )
143 .chain(),
144 );
145 graph.add_systems(begin_frame.in_set(RenderGraphSystems::Begin));
146 graph.add_systems(view_driver.in_set(RenderGraphSystems::Render));
147 graph.add_systems(submit.in_set(RenderGraphSystems::Submit));
148 graph.add_systems(finish_frame.in_set(RenderGraphSystems::Finish));
149 world.add_schedule(graph);
150}
151
152fn prepare_vitems(
153 ctx: Res<WgpuContext>,
154 mut buffer: ResMut<VItemsBuffer>,
155 items: Query<(&SceneOrder, &VItem)>,
156) {
157 let mut items = items.iter().collect::<Vec<_>>();
158 items.sort_by_key(|(order, _)| order.0);
159 buffer.update(&ctx, items.into_iter().map(|(_, item)| item));
160}
161
162fn prepare_mesh_items(
163 ctx: Res<WgpuContext>,
164 mut buffer: ResMut<MeshItemsBuffer>,
165 items: Query<(&SceneOrder, &MeshItem)>,
166) {
167 let mut items = items.iter().collect::<Vec<_>>();
168 items.sort_by_key(|(order, _)| order.0);
169 buffer.update(&ctx, items.into_iter().map(|(_, item)| item));
170}
171
172fn begin_frame(ctx: Res<WgpuContext>, mut encoder: ResMut<FrameEncoder>) {
173 encoder.0 = Some(
174 ctx.device
175 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()),
176 );
177}
178
179fn clear(mut render: RenderContext, target: Res<FrameTarget>) {
180 let pass_desc = wgpu::RenderPassDescriptor {
181 label: Some("Clear Pass"),
182 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
183 depth_slice: None,
184 view: &target.render_view,
185 resolve_target: None,
186 ops: wgpu::Operations {
187 load: wgpu::LoadOp::Clear(target.clear_color),
188 store: wgpu::StoreOp::Store,
189 },
190 })],
191 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
192 view: &target.depth_stencil_view,
193 depth_ops: Some(wgpu::Operations {
194 load: wgpu::LoadOp::Clear(1.0),
195 store: wgpu::StoreOp::Store,
196 }),
197 stencil_ops: None,
198 }),
199 timestamp_writes: None,
200 occlusion_query_set: None,
201 multiview_mask: None,
202 };
203 render.encoder().begin_render_pass(&pass_desc);
204}
205
206fn view_driver(world: &mut World) {
207 let mut cameras = world
208 .query::<(&SceneOrder, &CameraFrame)>()
209 .iter(world)
210 .map(|(order, camera)| (order.0, camera.clone()))
211 .collect::<Vec<_>>();
212 cameras.sort_by_key(|(order, _)| *order);
213
214 let camera = take_single_camera(cameras);
215 let dimensions = *world.resource::<RenderDimensions>();
216 let uniform = ViewportUniform::from_camera_frame(&camera, dimensions.width, dimensions.height);
217 world.resource_scope(|world, mut viewport: Mut<ViewportGpuPacket>| {
218 viewport.update(world.resource::<WgpuContext>(), &uniform);
219 });
220 world.run_schedule(ViewRender);
221}
222
223fn take_single_camera(mut cameras: Vec<(usize, CameraFrame)>) -> CameraFrame {
224 assert_eq!(
225 cameras.len(),
226 1,
227 "D0002 requires exactly one active CameraFrame per rendered frame"
228 );
229 cameras.pop().unwrap().1
230}
231
232fn submit(
233 ctx: Res<WgpuContext>,
234 mut encoder: ResMut<FrameEncoder>,
235 #[cfg(feature = "profiling")] mut profiler: ResMut<RenderProfiler>,
236) {
237 #[allow(unused_mut)]
238 let mut encoder = encoder.0.take().expect("frame encoder was not initialized");
239 #[cfg(feature = "profiling")]
240 profiler.0.resolve_queries(&mut encoder);
241 ctx.queue.submit(Some(encoder.finish()));
242}
243
244fn finish_frame(
245 target: Res<FrameTarget>,
246 #[cfg(feature = "profiling")] ctx: Res<WgpuContext>,
247 #[cfg(feature = "profiling")] mut profiler: ResMut<RenderProfiler>,
248) {
249 #[cfg(feature = "profiling")]
250 {
251 profiler.0.end_frame().unwrap();
252 ctx.device
253 .poll(wgpu::PollType::wait_indefinitely())
254 .unwrap();
255 if let Some(results) = profiler
256 .0
257 .process_finished_frame(ctx.queue.get_timestamp_period())
258 {
259 let mut gpu_profiler = crate::PUFFIN_GPU_PROFILER.lock().unwrap();
260 wgpu_profiler::puffin::output_frame_to_puffin(&mut gpu_profiler, &results);
261 gpu_profiler.new_frame();
262 }
263 }
264 target.texture_state.mark_dirty();
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn single_camera_is_accepted() {
273 let camera = CameraFrame::default();
274
275 assert_eq!(take_single_camera(vec![(7, camera.clone())]), camera);
276 }
277
278 #[test]
279 #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
280 fn missing_camera_is_rejected() {
281 take_single_camera(Vec::new());
282 }
283
284 #[test]
285 #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
286 fn multiple_cameras_are_rejected() {
287 take_single_camera(vec![
288 (0, CameraFrame::default()),
289 (1, CameraFrame::default()),
290 ]);
291 }
292}