wde_renderer/core/
mod.rs

1//! The renderer module is responsible for rendering the scene.
2//!
3//! It extracts the main world into the render world and runs the render schedule.
4//! It provides the [`Render`] and the [`Extract`] schedule, of the [`RenderApp`] (see [`RenderSet`] for the sets of the render schedule).
5//! It also provides multiple resources, such as the [`RenderInstance`] and the [`SwapchainFrame`], which are used by the render graph and the render pipelines.
6//! Lastly, it also handles window events, such as resizing, and sends the corresponding events to the render app.
7//!
8//!
9//! # Render resources
10//! - The render instance is stored in the [`RenderInstance`] resource (available in the render app), which is an Arc<RwLock<>> to allow parallelism in the render app.
11//! - The current swap chain frame is stored in the [`SwapchainFrame`] resource (available in the render app).
12//! - The device limits are stored in the [`DeviceLimits`] resource (available in both the main app and the render app).
13//!
14//!
15//! # Simple render system
16//! To add a simple render system, you can add a system to the render schedule. For example, to update the camera buffer before rendering, you can add a system to the `RenderSet::Prepare` set of the render schedule:
17//! ```
18//! app.get_sub_app_mut(RenderApp).unwrap()
19//!     .add_systems(Render, update_camera_buffer.in_set(RenderSet::Prepare));
20//! ```
21//!
22//!
23//! # Extract phase
24//! ## Extracting data from the main world
25//! As the render app runs in a separate thread from the main app, it cannot access the main world directly. To extract data from the main world, you can use the extract schedule and the [`ExtractWorld`] system parameter. For example, to extract the camera data from the main world, you can add a system to the extract schedule:
26//! ```
27//! app.get_sub_app_mut(RenderApp).unwrap()
28//!     .add_systems(Extract, extract_camera_data);
29//!
30//! fn extract_camera_data(mut commands: Commands, camera_query: ExtractWorld<Query<&Camera>>) {
31//!     let camera = camera_query.single();
32//!     commands.insert_resource(ExtractedCameraData {
33//!         // ...
34//!     });
35//! }
36//! ```
37//!
38//! ## Extracting while mutating the main world
39//! If you need to extract data from the main world while also mutating it, you can use the [`MainWorld`] resource:
40//! ```
41//! pub fn extract_messages(
42//!   mut render_test_resource: ResMut<TestResource>,
43//!   mut main_world: ResMut<MainWorld>
44//! ) {
45//!    /* (...) */
46//! }
47//! ```
48//! 
49//! ## Extracting resources and entities
50//! To extract resources and entities from the main world, see the [`sync`](crate::sync) module, which provides utilities to automatically extract resources, query and entities from the main to the render world.
51//!
52//! # Window events
53//! The renderer also handles window events, such as resizing. If the window is resized, an event of type [`SurfaceResized`] is sent to the main and render app, which contains the new width and height of the window in physical pixels.
54//! To listen to these events, you can do:
55//! ```
56//! app.get_sub_app_mut(RenderApp).unwrap()
57//!     .add_systems(Render, handle_resize_events);
58//!
59//! fn handle_resize_events(mut window_resized_events: MessageReader<SurfaceResized>) {
60//!     for event in window_resized_events.read() {
61//!         // Handle the resize event
62//!     }
63//! }
64//! ```
65
66mod extract;
67mod extract_macros;
68mod render_manager;
69mod render_multithread;
70mod window;
71
72pub use extract_macros::ExtractWorld;
73pub use window::SurfaceResized;
74
75use bevy::{
76    app::AppLabel,
77    ecs::{
78        schedule::{ScheduleBuildSettings, ScheduleLabel},
79        system::SystemState
80    },
81    prelude::*,
82    tasks::futures_lite,
83    window::{PrimaryWindow, RawHandleWrapperHolder}
84};
85use extract::{apply_extract_commands, main_extract};
86use render_manager::{init_main_world, init_surface, prepare, present};
87use render_multithread::PipelinedRenderingPlugin;
88use std::{
89    ops::{Deref, DerefMut},
90    sync::{Arc, RwLock}
91};
92use wde_wgpu::instance::{Limits, RenderTexture, create_instance};
93use window::{WindowPlugins, extract_surface_size, send_surface_resized};
94
95use crate::passes::{PipelineManagerPlugin, RenderGraph};
96
97/// Stores the main world for rendering as a resource.
98/// This resource is only available during the extract schedule and is used to move data from the main world to the render world.
99/// It is wrapped in a resource to avoid borrowing issues when extracting data from the main world.
100#[derive(Resource, Default)]
101pub struct MainWorld(World);
102impl Deref for MainWorld {
103    type Target = World;
104    fn deref(&self) -> &Self::Target {
105        &self.0
106    }
107}
108impl DerefMut for MainWorld {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        &mut self.0
111    }
112}
113
114/// Used to avoid allocating new worlds every frame when swapping out worlds.
115#[derive(Resource, Default)]
116struct EmptyWorld(World);
117
118/// The schedule that is used to extract the main world into the render world.
119/// Configure it such that it skips applying commands during the extract schedule.
120/// The extract schedule will be executed when sync is called between the main app and the sub app.
121#[derive(ScheduleLabel, Hash, PartialEq, Eq, Clone, Copy, Debug)]
122pub struct Extract;
123
124/// The renderer schedule set.
125/// The render schedule will be executed by the renderer app.
126#[derive(SystemSet, Hash, PartialEq, Eq, Clone, Copy, Debug)]
127pub enum RenderSet {
128    /// Run the extract commands registered during the extract schedule. This set is executed automatically and should not be used directly. Instead, use the Extract schedule.
129    ExtractAuto,
130    /// Prepare resources before rendering. This includes updating buffers, textures, assets, bind groups, etc.
131    Prepare,
132    /// Render commands.
133    Render,
134    /// Submit commands.
135    Submit
136}
137
138/// The renderer schedule.
139/// This schedule is responsible for rendering the scene.
140#[derive(ScheduleLabel, Hash, PartialEq, Eq, Clone, Copy, Debug)]
141pub struct Render;
142impl Render {
143    pub fn base() -> Schedule {
144        use RenderSet::*;
145
146        let mut schedule = Schedule::new(Self);
147        schedule.configure_sets(
148            (
149                ExtractAuto,
150                Prepare,
151                Render,
152                Submit
153            )
154            .chain()
155        );
156
157        schedule
158    }
159}
160
161/// The render app. This is the app that is responsible for rendering the scene. It runs in a separate thread from the main app and has its own schedule and resources. It is used to extract the main world into the render world and run the render schedule. It is also used to manage the swap chain and present the rendered frame to the window.
162#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
163pub struct RenderApp;
164
165/// The wgpu render instance resource.
166/// It is wrapped in an Arc<RwLock<>> to allow it to be shared between the main app and the render app, and to allow it to be mutated from both apps without borrowing issues. It is also wrapped in a resource to avoid borrowing issues when accessing it from different systems.
167#[derive(Resource)]
168pub struct RenderInstance(pub Arc<RwLock<wde_wgpu::RenderInstanceData<'static>>>);
169/// Resource storing the device limits. This is useful for pipelines to know the limits of the device and adjust their behavior accordingly.
170/// It is available in both the main app and the render app, as some pipelines may need to know the limits during extraction.
171#[derive(Resource, Default)]
172pub struct DeviceLimits(pub Limits);
173/// Resource storing the current swap chain frame. This is used to store the current frame that is being rendered to, so that it can be accessed by the render graph and the present system.
174/// It is wrapped in an Option because there may not be a frame available at all times (e.g. when the window is minimized). It is also wrapped in a resource to avoid borrowing issues when accessing it from different systems.
175#[derive(Resource, Default)]
176pub struct SwapchainFrame {
177    pub data: Option<RenderTexture>
178}
179
180/// The plugin that is responsible for the renderer.
181pub(crate) struct RenderCorePlugin;
182impl Plugin for RenderCorePlugin {
183    fn build(&self, app: &mut App) {
184        // === MAIN APP ===
185        // Add window
186        app.add_plugins(WindowPlugins)
187            .add_message::<SurfaceResized>()
188            .add_systems(Update, send_surface_resized);
189
190        // Add empty world component
191        app.add_systems(Startup, init_main_world);
192
193        // === RENDER APP ===
194        let mut render_app = SubApp::new();
195        let mut gpu_limits = None;
196        {
197            // Create the wgpu instance
198            render_app.insert_resource(futures_lite::future::block_on(async {
199                let mut system_state: SystemState<
200                    Query<&RawHandleWrapperHolder, With<PrimaryWindow>>
201                > = SystemState::new(app.world_mut());
202                let primary_window = system_state.get(app.world()).single().ok().cloned();
203
204                // Create the instance
205                let instance = create_instance("wde_renderer", primary_window.as_ref()).await;
206
207                // Get the GPU limits
208                gpu_limits = Some(instance.device.limits());
209
210                // Wrap the instance in an Arc<RwLock<>>
211                RenderInstance(Arc::new(RwLock::new(instance)))
212            }));
213
214            // Copy the asset server from the main app
215            render_app.insert_resource(app.world().resource::<AssetServer>().clone());
216
217            // Register the extract schedule
218            let mut extract_schedule = Schedule::new(Extract);
219            extract_schedule.set_build_settings(ScheduleBuildSettings {
220                auto_insert_apply_deferred: false,
221                ..Default::default()
222            });
223            extract_schedule.set_apply_final_deferred(false);
224            render_app.add_schedule(extract_schedule);
225
226            // Register the render schedule that executed in parallel with the extract schedule.
227            render_app.update_schedule = Some(Render.intern());
228            render_app.add_schedule(Render::base());
229
230            // Add extract command systems
231            render_app
232                .add_systems(
233                    Render,
234                    apply_extract_commands.in_set(RenderSet::ExtractAuto)
235                ) // Apply the extract commands
236                .set_extract(main_extract); // Register the extract commands
237
238            // Add render graph system
239            render_app
240                .init_resource::<RenderGraph>()
241                .add_systems(Render, RenderGraph::render.in_set(RenderSet::Render));
242
243            // Init wgpu instance
244            render_app.add_systems(
245                Extract,
246                (init_surface.run_if(run_once), extract_surface_size).chain()
247            );
248
249            // Add present system
250            render_app
251                .add_systems(Render, prepare.in_set(RenderSet::Prepare))
252                .add_systems(Render, present.in_set(RenderSet::Submit));
253
254            // Add render plugins
255            render_app.add_plugins(PipelineManagerPlugin);
256        }
257
258        // Register the render app
259        app.insert_sub_app(RenderApp, render_app);
260
261        // Add the GPU limits
262        app.insert_resource(DeviceLimits(gpu_limits.as_ref().unwrap().clone()));
263        app.get_sub_app_mut(RenderApp)
264            .unwrap()
265            .insert_resource(DeviceLimits(gpu_limits.unwrap()));
266
267        // Add the render pipeline plugins
268        app.add_plugins(PipelinedRenderingPlugin);
269    }
270}