 7b2cf98896
			
		
	
	
		7b2cf98896
		
	
	
	
	
		
			
			# Objective - Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource. - However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource. - This meant that effectively only one extract which wrote to resources could run at a time. - We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that. ## Solution - Move the extract stage to run on the render world. - Add the main world as a `MainWorld` resource. - Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`. ## Future work It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on. We need to determine how this interacts with https://github.com/bevyengine/bevy/pull/3519 It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too. ## Todo I still need to add doc comments to `Extract`. --- ## Changelog ### Changed - The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. Resources on the render world can now be accessed using `ResMut` during extract. ### Removed - `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead ## Migration Guide The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it. For example, if previously your extract system looked like: ```rust fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { for cloud in clouds.iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` the new version would be: ```rust fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` The diff is: ```diff --- a/src/clouds.rs +++ b/src/clouds.rs @@ -1,5 +1,5 @@ -fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { - for cloud in clouds.iter() { +fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { + for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` You can now also access resources from the render world using the normal system parameters during `Extract`: ```rust fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) { *render_assets = source_assets.clone(); } ``` Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
		
			
				
	
	
		
			194 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use crate::{
 | |
|     render_resource::TextureView,
 | |
|     renderer::{RenderDevice, RenderInstance},
 | |
|     texture::BevyDefault,
 | |
|     Extract, RenderApp, RenderStage,
 | |
| };
 | |
| use bevy_app::{App, Plugin};
 | |
| use bevy_ecs::prelude::*;
 | |
| use bevy_utils::{tracing::debug, HashMap, HashSet};
 | |
| use bevy_window::{PresentMode, RawWindowHandleWrapper, WindowClosed, WindowId, Windows};
 | |
| use std::ops::{Deref, DerefMut};
 | |
| use wgpu::TextureFormat;
 | |
| 
 | |
| /// Token to ensure a system runs on the main thread.
 | |
| #[derive(Default)]
 | |
| pub struct NonSendMarker;
 | |
| 
 | |
| pub struct WindowRenderPlugin;
 | |
| 
 | |
| #[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
 | |
| pub enum WindowSystem {
 | |
|     Prepare,
 | |
| }
 | |
| 
 | |
| impl Plugin for WindowRenderPlugin {
 | |
|     fn build(&self, app: &mut App) {
 | |
|         if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
 | |
|             render_app
 | |
|                 .init_resource::<ExtractedWindows>()
 | |
|                 .init_resource::<WindowSurfaces>()
 | |
|                 .init_resource::<NonSendMarker>()
 | |
|                 .add_system_to_stage(RenderStage::Extract, extract_windows)
 | |
|                 .add_system_to_stage(
 | |
|                     RenderStage::Prepare,
 | |
|                     prepare_windows.label(WindowSystem::Prepare),
 | |
|                 );
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub struct ExtractedWindow {
 | |
|     pub id: WindowId,
 | |
|     pub handle: RawWindowHandleWrapper,
 | |
|     pub physical_width: u32,
 | |
|     pub physical_height: u32,
 | |
|     pub present_mode: PresentMode,
 | |
|     pub swap_chain_texture: Option<TextureView>,
 | |
|     pub size_changed: bool,
 | |
| }
 | |
| 
 | |
| #[derive(Default)]
 | |
| pub struct ExtractedWindows {
 | |
|     pub windows: HashMap<WindowId, ExtractedWindow>,
 | |
| }
 | |
| 
 | |
| impl Deref for ExtractedWindows {
 | |
|     type Target = HashMap<WindowId, ExtractedWindow>;
 | |
| 
 | |
|     fn deref(&self) -> &Self::Target {
 | |
|         &self.windows
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl DerefMut for ExtractedWindows {
 | |
|     fn deref_mut(&mut self) -> &mut Self::Target {
 | |
|         &mut self.windows
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn extract_windows(
 | |
|     mut extracted_windows: ResMut<ExtractedWindows>,
 | |
|     mut closed: Extract<EventReader<WindowClosed>>,
 | |
|     windows: Extract<Res<Windows>>,
 | |
| ) {
 | |
|     for window in windows.iter() {
 | |
|         let (new_width, new_height) = (
 | |
|             window.physical_width().max(1),
 | |
|             window.physical_height().max(1),
 | |
|         );
 | |
| 
 | |
|         let mut extracted_window =
 | |
|             extracted_windows
 | |
|                 .entry(window.id())
 | |
|                 .or_insert(ExtractedWindow {
 | |
|                     id: window.id(),
 | |
|                     handle: window.raw_window_handle(),
 | |
|                     physical_width: new_width,
 | |
|                     physical_height: new_height,
 | |
|                     present_mode: window.present_mode(),
 | |
|                     swap_chain_texture: None,
 | |
|                     size_changed: false,
 | |
|                 });
 | |
| 
 | |
|         // NOTE: Drop the swap chain frame here
 | |
|         extracted_window.swap_chain_texture = None;
 | |
|         extracted_window.size_changed = new_width != extracted_window.physical_width
 | |
|             || new_height != extracted_window.physical_height;
 | |
| 
 | |
|         if extracted_window.size_changed {
 | |
|             debug!(
 | |
|                 "Window size changed from {}x{} to {}x{}",
 | |
|                 extracted_window.physical_width,
 | |
|                 extracted_window.physical_height,
 | |
|                 new_width,
 | |
|                 new_height
 | |
|             );
 | |
|             extracted_window.physical_width = new_width;
 | |
|             extracted_window.physical_height = new_height;
 | |
|         }
 | |
|     }
 | |
|     for closed_window in closed.iter() {
 | |
|         extracted_windows.remove(&closed_window.id);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Default)]
 | |
| pub struct WindowSurfaces {
 | |
|     surfaces: HashMap<WindowId, wgpu::Surface>,
 | |
|     /// List of windows that we have already called the initial `configure_surface` for
 | |
|     configured_windows: HashSet<WindowId>,
 | |
| }
 | |
| 
 | |
| /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering.
 | |
| ///
 | |
| /// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is
 | |
| /// the performance bottleneck. This can be seen in profiles as multiple prepare-stage systems all
 | |
| /// taking an unusually long time to complete, and all finishing at about the same time as the
 | |
| /// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it
 | |
| /// should not but it will still happen as it is easy for a user to create a large GPU workload
 | |
| /// relative to the GPU performance and/or CPU workload.
 | |
| /// This can be caused by many reasons, but several of them are:
 | |
| /// - GPU workload is more than your current GPU can manage
 | |
| /// - Error / performance bug in your custom shaders
 | |
| /// - wgpu was unable to detect a proper GPU hardware-accelerated device given the chosen
 | |
| ///   [`Backends`](crate::settings::Backends), [`WgpuLimits`](crate::settings::WgpuLimits),
 | |
| ///   and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). For example, on Windows currently
 | |
| ///   `DirectX 11` is not supported by wgpu 0.12 and so if your GPU/drivers do not support Vulkan,
 | |
| ///   it may be that a software renderer called "Microsoft Basic Render Driver" using `DirectX 12`
 | |
| ///   will be chosen and performance will be very poor. This is visible in a log message that is
 | |
| ///   output during renderer initialization. Future versions of wgpu will support `DirectX 11`, but
 | |
| ///   another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and
 | |
| ///   [`Backends::GL`](crate::settings::Backends::GL) if your GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or
 | |
| ///   later.
 | |
| pub fn prepare_windows(
 | |
|     // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread,
 | |
|     // which is necessary for some OS s
 | |
|     _marker: NonSend<NonSendMarker>,
 | |
|     mut windows: ResMut<ExtractedWindows>,
 | |
|     mut window_surfaces: ResMut<WindowSurfaces>,
 | |
|     render_device: Res<RenderDevice>,
 | |
|     render_instance: Res<RenderInstance>,
 | |
| ) {
 | |
|     let window_surfaces = window_surfaces.deref_mut();
 | |
|     for window in windows.windows.values_mut() {
 | |
|         let surface = window_surfaces
 | |
|             .surfaces
 | |
|             .entry(window.id)
 | |
|             .or_insert_with(|| unsafe {
 | |
|                 // NOTE: On some OSes this MUST be called from the main thread.
 | |
|                 render_instance.create_surface(&window.handle.get_handle())
 | |
|             });
 | |
| 
 | |
|         let swap_chain_descriptor = wgpu::SurfaceConfiguration {
 | |
|             format: TextureFormat::bevy_default(),
 | |
|             width: window.physical_width,
 | |
|             height: window.physical_height,
 | |
|             usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
 | |
|             present_mode: match window.present_mode {
 | |
|                 PresentMode::Fifo => wgpu::PresentMode::Fifo,
 | |
|                 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
 | |
|                 PresentMode::Immediate => wgpu::PresentMode::Immediate,
 | |
|             },
 | |
|         };
 | |
| 
 | |
|         // Do the initial surface configuration if it hasn't been configured yet
 | |
|         if window_surfaces.configured_windows.insert(window.id) || window.size_changed {
 | |
|             render_device.configure_surface(surface, &swap_chain_descriptor);
 | |
|         }
 | |
| 
 | |
|         let frame = match surface.get_current_texture() {
 | |
|             Ok(swap_chain_frame) => swap_chain_frame,
 | |
|             Err(wgpu::SurfaceError::Outdated) => {
 | |
|                 render_device.configure_surface(surface, &swap_chain_descriptor);
 | |
|                 surface
 | |
|                     .get_current_texture()
 | |
|                     .expect("Error reconfiguring surface")
 | |
|             }
 | |
|             err => err.expect("Failed to acquire next swap chain texture!"),
 | |
|         };
 | |
| 
 | |
|         window.swap_chain_texture = Some(TextureView::from(frame));
 | |
|     }
 | |
| }
 |