Skip to main content

ranim_render/
schedule.rs

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}