Skip to main content

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, WindowIcon};
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::{
94    PrimaryWindowIcon, WindowPlugins, apply_window_icon, extract_surface_size, send_surface_resized
95};
96
97use crate::passes::{PipelineManagerPlugin, RenderGraph};
98
99/// Stores the main world for rendering as a resource.
100/// This resource is only available during the extract schedule and is used to move data from the main world to the render world.
101/// It is wrapped in a resource to avoid borrowing issues when extracting data from the main world.
102#[derive(Resource, Default)]
103pub struct MainWorld(World);
104impl Deref for MainWorld {
105    type Target = World;
106    fn deref(&self) -> &Self::Target {
107        &self.0
108    }
109}
110impl DerefMut for MainWorld {
111    fn deref_mut(&mut self) -> &mut Self::Target {
112        &mut self.0
113    }
114}
115
116/// Used to avoid allocating new worlds every frame when swapping out worlds.
117#[derive(Resource, Default)]
118struct EmptyWorld(World);
119
120/// The schedule that is used to extract the main world into the render world.
121/// Configure it such that it skips applying commands during the extract schedule.
122/// The extract schedule will be executed when sync is called between the main app and the sub app.
123#[derive(ScheduleLabel, Hash, PartialEq, Eq, Clone, Copy, Debug)]
124pub struct Extract;
125
126/// The renderer schedule set.
127/// The render schedule will be executed by the renderer app.
128#[derive(SystemSet, Hash, PartialEq, Eq, Clone, Copy, Debug)]
129pub enum RenderSet {
130    /// 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.
131    ExtractAuto,
132    /// Prepare resources before rendering. This includes updating buffers, textures, assets, bind groups, etc.
133    Prepare,
134    /// Render commands.
135    Render,
136    /// Submit commands.
137    Submit
138}
139
140/// The renderer schedule.
141/// This schedule is responsible for rendering the scene.
142#[derive(ScheduleLabel, Hash, PartialEq, Eq, Clone, Copy, Debug)]
143pub struct Render;
144impl Render {
145    pub fn base() -> Schedule {
146        use RenderSet::*;
147
148        let mut schedule = Schedule::new(Self);
149        schedule.configure_sets((ExtractAuto, Prepare, Render, Submit).chain());
150
151        schedule
152    }
153}
154
155/// 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.
156#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
157pub struct RenderApp;
158
159/// The wgpu render instance resource.
160/// 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.
161#[derive(Resource)]
162pub struct RenderInstance(pub Arc<RwLock<wde_wgpu::RenderInstanceData<'static>>>);
163/// Resource storing the device limits. This is useful for pipelines to know the limits of the device and adjust their behavior accordingly.
164/// It is available in both the main app and the render app, as some pipelines may need to know the limits during extraction.
165#[derive(Resource, Default)]
166pub struct DeviceLimits(pub Limits);
167/// 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.
168/// 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.
169#[derive(Resource, Default)]
170pub struct SwapchainFrame {
171    pub data: Option<RenderTexture>
172}
173
174/// The plugin that is responsible for the renderer.
175pub(crate) struct RenderCorePlugin {
176    pub window_title: String,
177    pub window_resolution: (u32, u32),
178    pub window_icon: Option<WindowIcon>
179}
180impl Plugin for RenderCorePlugin {
181    fn build(&self, app: &mut App) {
182        // === MAIN APP ===
183        // Add window
184        app.add_plugins(WindowPlugins {
185            title: self.window_title.clone(),
186            resolution: self.window_resolution
187        })
188        .insert_resource(PrimaryWindowIcon(self.window_icon.clone()))
189        .add_systems(Startup, apply_window_icon)
190        .add_message::<SurfaceResized>()
191        .add_systems(Update, send_surface_resized);
192
193        // Add empty world component
194        app.add_systems(Startup, init_main_world);
195
196        // === RENDER APP ===
197        let mut render_app = SubApp::new();
198        let mut gpu_limits = None;
199        {
200            // Create the wgpu instance
201            render_app.insert_resource(futures_lite::future::block_on(async {
202                let mut system_state: SystemState<
203                    Query<&RawHandleWrapperHolder, With<PrimaryWindow>>
204                > = SystemState::new(app.world_mut());
205                let primary_window = system_state.get(app.world()).single().ok().cloned();
206
207                // Create the instance
208                let instance = create_instance("wde_renderer", primary_window.as_ref()).await;
209
210                // Get the GPU limits
211                gpu_limits = Some(instance.device.limits());
212
213                // Wrap the instance in an Arc<RwLock<>>
214                RenderInstance(Arc::new(RwLock::new(instance)))
215            }));
216
217            // Copy the asset server from the main app
218            render_app.insert_resource(app.world().resource::<AssetServer>().clone());
219
220            // Register the extract schedule
221            let mut extract_schedule = Schedule::new(Extract);
222            extract_schedule.set_build_settings(ScheduleBuildSettings {
223                auto_insert_apply_deferred: false,
224                ..Default::default()
225            });
226            extract_schedule.set_apply_final_deferred(false);
227            render_app.add_schedule(extract_schedule);
228
229            // Register the render schedule that executed in parallel with the extract schedule.
230            render_app.update_schedule = Some(Render.intern());
231            render_app.add_schedule(Render::base());
232
233            // Add extract command systems
234            render_app
235                .add_systems(
236                    Render,
237                    apply_extract_commands.in_set(RenderSet::ExtractAuto)
238                ) // Apply the extract commands
239                .set_extract(main_extract); // Register the extract commands
240
241            // Add render graph system
242            render_app
243                .init_resource::<RenderGraph>()
244                .add_systems(Render, RenderGraph::render.in_set(RenderSet::Render));
245
246            // Init wgpu instance
247            render_app.add_systems(
248                Extract,
249                (init_surface.run_if(run_once), extract_surface_size).chain()
250            );
251
252            // Add present system
253            render_app
254                .add_systems(Render, prepare.in_set(RenderSet::Prepare))
255                .add_systems(Render, present.in_set(RenderSet::Submit));
256
257            // Add render plugins
258            render_app.add_plugins(PipelineManagerPlugin);
259        }
260
261        // Register the render app
262        app.insert_sub_app(RenderApp, render_app);
263
264        // Add the GPU limits
265        app.insert_resource(DeviceLimits(gpu_limits.as_ref().unwrap().clone()));
266        app.get_sub_app_mut(RenderApp)
267            .unwrap()
268            .insert_resource(DeviceLimits(gpu_limits.unwrap()));
269
270        // Add the render pipeline plugins
271        app.add_plugins(PipelinedRenderingPlugin);
272    }
273}