Path: blob/main/crates/bevy_render/src/view/window/mod.rs
6598 views
use crate::renderer::WgpuWrapper;1use crate::{2render_resource::{SurfaceTexture, TextureView},3renderer::{RenderAdapter, RenderDevice, RenderInstance},4Extract, ExtractSchedule, Render, RenderApp, RenderSystems,5};6use bevy_app::{App, Plugin};7use bevy_ecs::{entity::EntityHashMap, prelude::*};8use bevy_platform::collections::HashSet;9use bevy_utils::default;10use bevy_window::{11CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,12};13use core::{14num::NonZero,15ops::{Deref, DerefMut},16};17use tracing::{debug, warn};18use wgpu::{19SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor,20};2122pub mod screenshot;2324use screenshot::ScreenshotPlugin;2526pub struct WindowRenderPlugin;2728impl Plugin for WindowRenderPlugin {29fn build(&self, app: &mut App) {30app.add_plugins(ScreenshotPlugin);3132if let Some(render_app) = app.get_sub_app_mut(RenderApp) {33render_app34.init_resource::<ExtractedWindows>()35.init_resource::<WindowSurfaces>()36.add_systems(ExtractSchedule, extract_windows)37.add_systems(38Render,39create_surfaces40.run_if(need_surface_configuration)41.before(prepare_windows),42)43.add_systems(Render, prepare_windows.in_set(RenderSystems::ManageViews));44}45}46}4748pub struct ExtractedWindow {49/// An entity that contains the components in [`Window`].50pub entity: Entity,51pub handle: RawHandleWrapper,52pub physical_width: u32,53pub physical_height: u32,54pub present_mode: PresentMode,55pub desired_maximum_frame_latency: Option<NonZero<u32>>,56/// Note: this will not always be the swap chain texture view. When taking a screenshot,57/// this will point to an alternative texture instead to allow for copying the render result58/// to CPU memory.59pub swap_chain_texture_view: Option<TextureView>,60pub swap_chain_texture: Option<SurfaceTexture>,61pub swap_chain_texture_format: Option<TextureFormat>,62pub size_changed: bool,63pub present_mode_changed: bool,64pub alpha_mode: CompositeAlphaMode,65}6667impl ExtractedWindow {68fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) {69let texture_view_descriptor = TextureViewDescriptor {70format: Some(frame.texture.format().add_srgb_suffix()),71..default()72};73self.swap_chain_texture_view = Some(TextureView::from(74frame.texture.create_view(&texture_view_descriptor),75));76self.swap_chain_texture = Some(SurfaceTexture::from(frame));77}78}7980#[derive(Default, Resource)]81pub struct ExtractedWindows {82pub primary: Option<Entity>,83pub windows: EntityHashMap<ExtractedWindow>,84}8586impl Deref for ExtractedWindows {87type Target = EntityHashMap<ExtractedWindow>;8889fn deref(&self) -> &Self::Target {90&self.windows91}92}9394impl DerefMut for ExtractedWindows {95fn deref_mut(&mut self) -> &mut Self::Target {96&mut self.windows97}98}99100fn extract_windows(101mut extracted_windows: ResMut<ExtractedWindows>,102mut closing: Extract<EventReader<WindowClosing>>,103windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,104mut removed: Extract<RemovedComponents<RawHandleWrapper>>,105mut window_surfaces: ResMut<WindowSurfaces>,106) {107for (entity, window, handle, primary) in windows.iter() {108if primary.is_some() {109extracted_windows.primary = Some(entity);110}111112let (new_width, new_height) = (113window.resolution.physical_width().max(1),114window.resolution.physical_height().max(1),115);116117let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {118entity,119handle: handle.clone(),120physical_width: new_width,121physical_height: new_height,122present_mode: window.present_mode,123desired_maximum_frame_latency: window.desired_maximum_frame_latency,124swap_chain_texture: None,125swap_chain_texture_view: None,126size_changed: false,127swap_chain_texture_format: None,128present_mode_changed: false,129alpha_mode: window.composite_alpha_mode,130});131132// NOTE: Drop the swap chain frame here133extracted_window.swap_chain_texture_view = None;134extracted_window.size_changed = new_width != extracted_window.physical_width135|| new_height != extracted_window.physical_height;136extracted_window.present_mode_changed =137window.present_mode != extracted_window.present_mode;138139if extracted_window.size_changed {140debug!(141"Window size changed from {}x{} to {}x{}",142extracted_window.physical_width,143extracted_window.physical_height,144new_width,145new_height146);147extracted_window.physical_width = new_width;148extracted_window.physical_height = new_height;149}150151if extracted_window.present_mode_changed {152debug!(153"Window Present Mode changed from {:?} to {:?}",154extracted_window.present_mode, window.present_mode155);156extracted_window.present_mode = window.present_mode;157}158}159160for closing_window in closing.read() {161extracted_windows.remove(&closing_window.window);162window_surfaces.remove(&closing_window.window);163}164for removed_window in removed.read() {165extracted_windows.remove(&removed_window);166window_surfaces.remove(&removed_window);167}168}169170struct SurfaceData {171// TODO: what lifetime should this be?172surface: WgpuWrapper<wgpu::Surface<'static>>,173configuration: SurfaceConfiguration,174}175176#[derive(Resource, Default)]177pub struct WindowSurfaces {178surfaces: EntityHashMap<SurfaceData>,179/// List of windows that we have already called the initial `configure_surface` for180configured_windows: HashSet<Entity>,181}182183impl WindowSurfaces {184fn remove(&mut self, window: &Entity) {185self.surfaces.remove(window);186self.configured_windows.remove(window);187}188}189190/// (re)configures window surfaces, and obtains a swapchain texture for rendering.191///192/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is193/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all194/// taking an unusually long time to complete, and all finishing at about the same time as the195/// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it196/// should not but it will still happen as it is easy for a user to create a large GPU workload197/// relative to the GPU performance and/or CPU workload.198/// This can be caused by many reasons, but several of them are:199/// - GPU workload is more than your current GPU can manage200/// - Error / performance bug in your custom shaders201/// - wgpu was unable to detect a proper GPU hardware-accelerated device given the chosen202/// [`Backends`](crate::settings::Backends), [`WgpuLimits`](crate::settings::WgpuLimits),203/// and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). For example, on Windows currently204/// `DirectX 11` is not supported by wgpu 0.12 and so if your GPU/drivers do not support Vulkan,205/// it may be that a software renderer called "Microsoft Basic Render Driver" using `DirectX 12`206/// will be chosen and performance will be very poor. This is visible in a log message that is207/// output during renderer initialization.208/// Another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and209/// [`Backends::GL`](crate::settings::Backends::GL) with the `gles` feature enabled if your210/// GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or later.211pub fn prepare_windows(212mut windows: ResMut<ExtractedWindows>,213mut window_surfaces: ResMut<WindowSurfaces>,214render_device: Res<RenderDevice>,215#[cfg(target_os = "linux")] render_instance: Res<RenderInstance>,216) {217for window in windows.windows.values_mut() {218let window_surfaces = window_surfaces.deref_mut();219let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else {220continue;221};222223// A recurring issue is hitting `wgpu::SurfaceError::Timeout` on certain Linux224// mesa driver implementations. This seems to be a quirk of some drivers.225// We'd rather keep panicking when not on Linux mesa, because in those case,226// the `Timeout` is still probably the symptom of a degraded unrecoverable227// application state.228// see https://github.com/bevyengine/bevy/pull/5957229// and https://github.com/gfx-rs/wgpu/issues/1218230#[cfg(target_os = "linux")]231let may_erroneously_timeout = || {232render_instance233.enumerate_adapters(wgpu::Backends::VULKAN)234.iter()235.any(|adapter| {236let name = adapter.get_info().name;237name.starts_with("Radeon")238|| name.starts_with("AMD")239|| name.starts_with("Intel")240})241};242243let surface = &surface_data.surface;244match surface.get_current_texture() {245Ok(frame) => {246window.set_swapchain_texture(frame);247}248Err(wgpu::SurfaceError::Outdated) => {249render_device.configure_surface(surface, &surface_data.configuration);250let frame = match surface.get_current_texture() {251Ok(frame) => frame,252Err(err) => {253// This is a common occurrence on X11 and Xwayland with NVIDIA drivers254// when opening and resizing the window.255warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'");256continue;257}258};259window.set_swapchain_texture(frame);260}261#[cfg(target_os = "linux")]262Err(wgpu::SurfaceError::Timeout) if may_erroneously_timeout() => {263tracing::trace!(264"Couldn't get swap chain texture. This is probably a quirk \265of your Linux GPU driver, so it can be safely ignored."266);267}268Err(err) => {269panic!("Couldn't get swap chain texture, operation unrecoverable: {err}");270}271}272window.swap_chain_texture_format = Some(surface_data.configuration.format);273}274}275276pub fn need_surface_configuration(277windows: Res<ExtractedWindows>,278window_surfaces: Res<WindowSurfaces>,279) -> bool {280for window in windows.windows.values() {281if !window_surfaces.configured_windows.contains(&window.entity)282|| window.size_changed283|| window.present_mode_changed284{285return true;286}287}288false289}290291// 2 is wgpu's default/what we've been using so far.292// 1 is the minimum, but may cause lower framerates due to the cpu waiting for the gpu to finish293// all work for the previous frame before starting work on the next frame, which then means the gpu294// has to wait for the cpu to finish to start on the next frame.295const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2;296297/// Creates window surfaces.298pub fn create_surfaces(299// By accessing a NonSend resource, we tell the scheduler to put this system on the main thread,300// which is necessary for some OS's301#[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker,302windows: Res<ExtractedWindows>,303mut window_surfaces: ResMut<WindowSurfaces>,304render_instance: Res<RenderInstance>,305render_adapter: Res<RenderAdapter>,306render_device: Res<RenderDevice>,307) {308for window in windows.windows.values() {309let data = window_surfaces310.surfaces311.entry(window.entity)312.or_insert_with(|| {313let surface_target = SurfaceTargetUnsafe::RawHandle {314raw_display_handle: window.handle.get_display_handle(),315raw_window_handle: window.handle.get_window_handle(),316};317// SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on318let surface = unsafe {319// NOTE: On some OSes this MUST be called from the main thread.320// As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails.321render_instance322.create_surface_unsafe(surface_target)323.expect("Failed to create wgpu surface")324};325let caps = surface.get_capabilities(&render_adapter);326let formats = caps.formats;327// For future HDR output support, we'll need to request a format that supports HDR,328// but as of wgpu 0.15 that is not yet supported.329// Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available.330let mut format = *formats.first().expect("No supported formats for surface");331for available_format in formats {332// Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces.333if available_format == TextureFormat::Rgba8UnormSrgb334|| available_format == TextureFormat::Bgra8UnormSrgb335{336format = available_format;337break;338}339}340341let configuration = SurfaceConfiguration {342format,343width: window.physical_width,344height: window.physical_height,345usage: TextureUsages::RENDER_ATTACHMENT,346present_mode: match window.present_mode {347PresentMode::Fifo => wgpu::PresentMode::Fifo,348PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,349PresentMode::Mailbox => wgpu::PresentMode::Mailbox,350PresentMode::Immediate => wgpu::PresentMode::Immediate,351PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,352PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,353},354desired_maximum_frame_latency: window355.desired_maximum_frame_latency356.map(NonZero::<u32>::get)357.unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY),358alpha_mode: match window.alpha_mode {359CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto,360CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque,361CompositeAlphaMode::PreMultiplied => {362wgpu::CompositeAlphaMode::PreMultiplied363}364CompositeAlphaMode::PostMultiplied => {365wgpu::CompositeAlphaMode::PostMultiplied366}367CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,368},369view_formats: if !format.is_srgb() {370vec![format.add_srgb_suffix()]371} else {372vec![]373},374};375376render_device.configure_surface(&surface, &configuration);377378SurfaceData {379surface: WgpuWrapper::new(surface),380configuration,381}382});383384if window.size_changed || window.present_mode_changed {385data.configuration.width = window.physical_width;386data.configuration.height = window.physical_height;387data.configuration.present_mode = match window.present_mode {388PresentMode::Fifo => wgpu::PresentMode::Fifo,389PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,390PresentMode::Mailbox => wgpu::PresentMode::Mailbox,391PresentMode::Immediate => wgpu::PresentMode::Immediate,392PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,393PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,394};395render_device.configure_surface(&data.surface, &data.configuration);396}397398window_surfaces.configured_windows.insert(window.entity);399}400}401402403