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::() .init_resource::() .init_resource::() .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, pub size_changed: bool, } #[derive(Default)] pub struct ExtractedWindows { pub windows: HashMap, } impl Deref for ExtractedWindows { type Target = HashMap; 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, mut closed: Extract>, windows: Extract>, ) { 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, /// List of windows that we have already called the initial `configure_surface` for configured_windows: HashSet, } /// 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, mut windows: ResMut, mut window_surfaces: ResMut, render_device: Res, render_instance: Res, ) { 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)); } }