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}