From f56530d95c2e09f0d0c0a185ba791ba3d8059b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 3 Feb 2024 19:07:26 +0100 Subject: [PATCH] Allow prepare_windows to run off main thread on all platforms (#11672) # Objective - Allow prepare windows to run off of the main thread on all platforms. - Fixes https://github.com/bevyengine/bevy/issues/9964 on all platforms. ## Solution - Running `prepare_windows` on the main thread on apple platforms is only mandatory to create surface, which is only needed during window creation. Split that part into its own system that happens before `prepare_windows` - Tested on macOS and iOS --- ## Changelog - Allow prepare windows to run off main thread on all platforms. --- crates/bevy_render/src/view/window/mod.rs | 95 +++++++++++++---------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 7f0e7d2f61a76a..d10e354a173169 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -43,7 +43,8 @@ impl Plugin for WindowRenderPlugin { .init_resource::() .init_resource::() .add_systems(ExtractSchedule, extract_windows) - .add_systems(Render, prepare_windows.in_set(RenderSet::ManageViews)); + .add_systems(Render, prepare_windows.in_set(RenderSet::PrepareAssets)) + .add_systems(Render, create_surfaces.in_set(RenderSet::ManageViews)); } } @@ -213,7 +214,7 @@ impl WindowSurfaces { } } -/// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. +/// (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-set systems all @@ -236,55 +237,21 @@ impl WindowSurfaces { /// later. #[allow(clippy::too_many_arguments)] 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 - #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: Option>, mut windows: ResMut, mut window_surfaces: ResMut, render_device: Res, - render_instance: Res, render_adapter: Res, screenshot_pipeline: Res, pipeline_cache: Res, mut pipelines: ResMut>, mut msaa: ResMut, + #[cfg(target_os = "linux")] render_instance: Res, ) { for window in windows.windows.values_mut() { let window_surfaces = window_surfaces.deref_mut(); - let surface_data = window_surfaces - .surfaces - .entry(window.entity) - .or_insert_with(|| { - let surface_target = SurfaceTargetUnsafe::RawHandle { - raw_display_handle: window.handle.display_handle, - raw_window_handle: window.handle.window_handle, - }; - // SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on - let surface = unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - render_instance - .create_surface_unsafe(surface_target) - .expect("Failed to create wgpu surface") - }; - let caps = surface.get_capabilities(&render_adapter); - let formats = caps.formats; - // For future HDR output support, we'll need to request a format that supports HDR, - // but as of wgpu 0.15 that is not yet supported. - // Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available. - let mut format = *formats.first().expect("No supported formats for surface"); - for available_format in formats { - // Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces. - if available_format == TextureFormat::Rgba8UnormSrgb - || available_format == TextureFormat::Bgra8UnormSrgb - { - format = available_format; - break; - } - } - - SurfaceData { surface, format } - }); + let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else { + continue; + }; let surface_configuration = wgpu::SurfaceConfiguration { format: surface_data.format, @@ -451,3 +418,51 @@ pub fn prepare_windows( } } } + +/// Creates window surfaces. +pub fn create_surfaces( + // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, + // which is necessary for some OS's + #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: Option>, + windows: Res, + mut window_surfaces: ResMut, + render_instance: Res, + render_adapter: Res, +) { + for window in windows.windows.values() { + window_surfaces + .surfaces + .entry(window.entity) + .or_insert_with(|| { + let surface_target = SurfaceTargetUnsafe::RawHandle { + raw_display_handle: window.handle.display_handle, + raw_window_handle: window.handle.window_handle, + }; + // SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on + let surface = unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. + render_instance + .create_surface_unsafe(surface_target) + .expect("Failed to create wgpu surface") + }; + let caps = surface.get_capabilities(&render_adapter); + let formats = caps.formats; + // For future HDR output support, we'll need to request a format that supports HDR, + // but as of wgpu 0.15 that is not yet supported. + // Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available. + let mut format = *formats.first().expect("No supported formats for surface"); + for available_format in formats { + // Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces. + if available_format == TextureFormat::Rgba8UnormSrgb + || available_format == TextureFormat::Bgra8UnormSrgb + { + format = available_format; + break; + } + } + + SurfaceData { surface, format } + }); + } +}