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((ExtractAuto, Prepare, Render, Submit).chain());
148
149 schedule
150 }
151}
152
153/// 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.
154#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
155pub struct RenderApp;
156
157/// The wgpu render instance resource.
158/// 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.
159#[derive(Resource)]
160pub struct RenderInstance(pub Arc<RwLock<wde_wgpu::RenderInstanceData<'static>>>);
161/// Resource storing the device limits. This is useful for pipelines to know the limits of the device and adjust their behavior accordingly.
162/// It is available in both the main app and the render app, as some pipelines may need to know the limits during extraction.
163#[derive(Resource, Default)]
164pub struct DeviceLimits(pub Limits);
165/// 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.
166/// 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.
167#[derive(Resource, Default)]
168pub struct SwapchainFrame {
169 pub data: Option<RenderTexture>
170}
171
172/// The plugin that is responsible for the renderer.
173pub(crate) struct RenderCorePlugin;
174impl Plugin for RenderCorePlugin {
175 fn build(&self, app: &mut App) {
176 // === MAIN APP ===
177 // Add window
178 app.add_plugins(WindowPlugins)
179 .add_message::<SurfaceResized>()
180 .add_systems(Update, send_surface_resized);
181
182 // Add empty world component
183 app.add_systems(Startup, init_main_world);
184
185 // === RENDER APP ===
186 let mut render_app = SubApp::new();
187 let mut gpu_limits = None;
188 {
189 // Create the wgpu instance
190 render_app.insert_resource(futures_lite::future::block_on(async {
191 let mut system_state: SystemState<
192 Query<&RawHandleWrapperHolder, With<PrimaryWindow>>
193 > = SystemState::new(app.world_mut());
194 let primary_window = system_state.get(app.world()).single().ok().cloned();
195
196 // Create the instance
197 let instance = create_instance("wde_renderer", primary_window.as_ref()).await;
198
199 // Get the GPU limits
200 gpu_limits = Some(instance.device.limits());
201
202 // Wrap the instance in an Arc<RwLock<>>
203 RenderInstance(Arc::new(RwLock::new(instance)))
204 }));
205
206 // Copy the asset server from the main app
207 render_app.insert_resource(app.world().resource::<AssetServer>().clone());
208
209 // Register the extract schedule
210 let mut extract_schedule = Schedule::new(Extract);
211 extract_schedule.set_build_settings(ScheduleBuildSettings {
212 auto_insert_apply_deferred: false,
213 ..Default::default()
214 });
215 extract_schedule.set_apply_final_deferred(false);
216 render_app.add_schedule(extract_schedule);
217
218 // Register the render schedule that executed in parallel with the extract schedule.
219 render_app.update_schedule = Some(Render.intern());
220 render_app.add_schedule(Render::base());
221
222 // Add extract command systems
223 render_app
224 .add_systems(
225 Render,
226 apply_extract_commands.in_set(RenderSet::ExtractAuto)
227 ) // Apply the extract commands
228 .set_extract(main_extract); // Register the extract commands
229
230 // Add render graph system
231 render_app
232 .init_resource::<RenderGraph>()
233 .add_systems(Render, RenderGraph::render.in_set(RenderSet::Render));
234
235 // Init wgpu instance
236 render_app.add_systems(
237 Extract,
238 (init_surface.run_if(run_once), extract_surface_size).chain()
239 );
240
241 // Add present system
242 render_app
243 .add_systems(Render, prepare.in_set(RenderSet::Prepare))
244 .add_systems(Render, present.in_set(RenderSet::Submit));
245
246 // Add render plugins
247 render_app.add_plugins(PipelineManagerPlugin);
248 }
249
250 // Register the render app
251 app.insert_sub_app(RenderApp, render_app);
252
253 // Add the GPU limits
254 app.insert_resource(DeviceLimits(gpu_limits.as_ref().unwrap().clone()));
255 app.get_sub_app_mut(RenderApp)
256 .unwrap()
257 .insert_resource(DeviceLimits(gpu_limits.unwrap()));
258
259 // Add the render pipeline plugins
260 app.add_plugins(PipelinedRenderingPlugin);
261 }
262}