From ffe7f4b7bc353eea80691c042ff307318260ea2a Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Mon, 7 Jul 2025 22:41:07 +0200 Subject: [PATCH 1/3] Move Window creation from plugin build time to PreStartup --- crates/bevy_window/src/lib.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 22e657cf038c0..a73a75d9ba5b4 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -19,6 +19,7 @@ extern crate alloc; use alloc::sync::Arc; +use bevy_ecs::system::Commands; use bevy_platform::sync::Mutex; mod event; @@ -130,14 +131,16 @@ impl Plugin for WindowPlugin { .add_event::(); if let Some(primary_window) = &self.primary_window { - let mut entity_commands = app.world_mut().spawn(primary_window.clone()); - entity_commands.insert(( - PrimaryWindow, - RawHandleWrapperHolder(Arc::new(Mutex::new(None))), - )); - if let Some(primary_cursor_options) = &self.primary_cursor_options { - entity_commands.insert(primary_cursor_options.clone()); - } + let primary_window = primary_window.clone(); + let primary_cursor_options = self.primary_cursor_options.clone().unwrap_or_default(); + app.add_systems(PreStartup, move |mut commands: Commands| { + commands.spawn(( + primary_window.clone(), + PrimaryWindow, + RawHandleWrapperHolder(Arc::new(Mutex::new(None))), + primary_cursor_options.clone(), + )); + }); } match self.exit_condition { From f6c8db44eaa52fad1ee261222ae22f46c4687200 Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Mon, 7 Jul 2025 23:45:24 +0200 Subject: [PATCH 2/3] Move renderer initialization to RenderStartup --- crates/bevy_render/src/lib.rs | 185 ++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 85 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7a2ad060878b0..69d732c316059 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -104,6 +104,7 @@ use sync_world::{ }; use crate::gpu_readback::GpuReadbackPlugin; +use crate::settings::WgpuSettings; use crate::{ camera::CameraPlugin, mesh::{MeshPlugin, MorphPlugin, RenderMesh}, @@ -121,6 +122,7 @@ use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use bevy_utils::WgpuWrapper; use bitflags::bitflags; use core::ops::{Deref, DerefMut}; +use std::panic; use std::sync::Mutex; use tracing::debug; @@ -342,96 +344,12 @@ impl Plugin for RenderPlugin { unsafe { initialize_render_app(app) }; } RenderCreation::Automatic(render_creation) => { - if let Some(backends) = render_creation.backends { + if render_creation.backends.is_some() { let future_render_resources_wrapper = Arc::new(Mutex::new(None)); app.insert_resource(FutureRenderResources( future_render_resources_wrapper.clone(), )); - let primary_window = app - .world_mut() - .query_filtered::<&RawHandleWrapperHolder, With>() - .single(app.world()) - .ok() - .cloned(); - let settings = render_creation.clone(); - let async_renderer = async move { - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends, - flags: settings.instance_flags, - backend_options: wgpu::BackendOptions { - gl: wgpu::GlBackendOptions { - gles_minor_version: settings.gles3_minor_version, - fence_behavior: wgpu::GlFenceBehavior::Normal, - }, - dx12: wgpu::Dx12BackendOptions { - shader_compiler: settings.dx12_shader_compiler.clone(), - }, - noop: wgpu::NoopBackendOptions { enable: false }, - }, - }); - - let surface = primary_window.and_then(|wrapper| { - let maybe_handle = wrapper.0.lock().expect( - "Couldn't get the window handle in time for renderer initialization", - ); - if let Some(wrapper) = maybe_handle.as_ref() { - // SAFETY: Plugins should be set up on the main thread. - let handle = unsafe { wrapper.get_handle() }; - Some( - instance - .create_surface(handle) - .expect("Failed to create wgpu surface"), - ) - } else { - None - } - }); - - let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER") - .map_or(settings.force_fallback_adapter, |v| { - !(v.is_empty() || v == "0" || v == "false") - }); - - let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") - .as_deref() - .map_or(settings.adapter_name.clone(), |x| Some(x.to_lowercase())); - - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: settings.power_preference, - compatible_surface: surface.as_ref(), - force_fallback_adapter, - }; - - let (device, queue, adapter_info, render_adapter) = - renderer::initialize_renderer( - &instance, - &settings, - &request_adapter_options, - desired_adapter_name, - ) - .await; - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_render_resources_inner = - future_render_resources_wrapper.lock().unwrap(); - *future_render_resources_inner = Some(RenderResources( - device, - queue, - adapter_info, - render_adapter, - RenderInstance(Arc::new(WgpuWrapper::new(instance))), - )); - }; - // In wasm, spawn a task and detach it for execution - #[cfg(target_arch = "wasm32")] - bevy_tasks::IoTaskPool::get() - .spawn_local(async_renderer) - .detach(); - // Otherwise, just block for it to complete - #[cfg(not(target_arch = "wasm32"))] - futures_lite::future::block_on(async_renderer); - // SAFETY: Plugins should be set up on the main thread. unsafe { initialize_render_app(app) }; } @@ -465,6 +383,13 @@ impl Plugin for RenderPlugin { Render, reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup), ); + if let RenderCreation::Automatic(render_creation) = &self.render_creation { + if render_creation.backends.is_some() { + render_app + .insert_resource(AutomaticRendererCreationSettings(render_creation.clone())) + .add_systems(RenderStartup, initialize_renderer); + } + } } app.register_type::() @@ -520,6 +445,96 @@ impl Plugin for RenderPlugin { } } +#[derive(Resource)] +struct AutomaticRendererCreationSettings(WgpuSettings); + +fn initialize_renderer( + primary_window: Option>>, + future_render_resources: Res, + render_creation: When>, +) { + let primary_window = primary_window.map(|primary_window| primary_window.clone()); + let settings = render_creation.into_inner().0.clone(); + let Some(backends) = settings.backends else { + return; + }; + let async_renderer = async move { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends, + flags: settings.instance_flags, + backend_options: wgpu::BackendOptions { + gl: wgpu::GlBackendOptions { + gles_minor_version: settings.gles3_minor_version, + fence_behavior: wgpu::GlFenceBehavior::Normal, + }, + dx12: wgpu::Dx12BackendOptions { + shader_compiler: settings.dx12_shader_compiler.clone(), + }, + noop: wgpu::NoopBackendOptions { enable: false }, + }, + }); + + let surface = primary_window.and_then(|wrapper| { + let maybe_handle = wrapper + .0 + .lock() + .expect("Couldn't get the window handle in time for renderer initialization"); + if let Some(wrapper) = maybe_handle.as_ref() { + // SAFETY: Plugins should be set up on the main thread. + let handle = unsafe { wrapper.get_handle() }; + Some( + instance + .create_surface(handle) + .expect("Failed to create wgpu surface"), + ) + } else { + None + } + }); + + let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER") + .map_or(settings.force_fallback_adapter, |v| { + !(v.is_empty() || v == "0" || v == "false") + }); + + let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") + .as_deref() + .map_or(settings.adapter_name.clone(), |x| Some(x.to_lowercase())); + + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: settings.power_preference, + compatible_surface: surface.as_ref(), + force_fallback_adapter, + }; + + let (device, queue, adapter_info, render_adapter) = renderer::initialize_renderer( + &instance, + &settings, + &request_adapter_options, + desired_adapter_name, + ) + .await; + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + let mut future_render_resources_inner = future_render_resources.0.lock().unwrap(); + *future_render_resources_inner = Some(RenderResources( + device, + queue, + adapter_info, + render_adapter, + RenderInstance(Arc::new(WgpuWrapper::new(instance))), + )); + }; + // In wasm, spawn a task and detach it for execution + #[cfg(target_arch = "wasm32")] + bevy_tasks::IoTaskPool::get() + .spawn_local(async_renderer) + .detach(); + // Otherwise, just block for it to complete + #[cfg(not(target_arch = "wasm32"))] + futures_lite::future::block_on(async_renderer); +} + /// A "scratch" world used to avoid allocating new worlds every frame when /// swapping out the [`MainWorld`] for [`ExtractSchedule`]. #[derive(Resource, Default)] From 97437dad18507b14004af8abaaecd59c95e45012 Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Mon, 7 Jul 2025 23:55:07 +0200 Subject: [PATCH 3/3] Revert "Move renderer initialization to RenderStartup" This reverts commit f6c8db44eaa52fad1ee261222ae22f46c4687200. --- crates/bevy_render/src/lib.rs | 185 ++++++++++++++++------------------ 1 file changed, 85 insertions(+), 100 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 69d732c316059..7a2ad060878b0 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -104,7 +104,6 @@ use sync_world::{ }; use crate::gpu_readback::GpuReadbackPlugin; -use crate::settings::WgpuSettings; use crate::{ camera::CameraPlugin, mesh::{MeshPlugin, MorphPlugin, RenderMesh}, @@ -122,7 +121,6 @@ use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use bevy_utils::WgpuWrapper; use bitflags::bitflags; use core::ops::{Deref, DerefMut}; -use std::panic; use std::sync::Mutex; use tracing::debug; @@ -344,12 +342,96 @@ impl Plugin for RenderPlugin { unsafe { initialize_render_app(app) }; } RenderCreation::Automatic(render_creation) => { - if render_creation.backends.is_some() { + if let Some(backends) = render_creation.backends { let future_render_resources_wrapper = Arc::new(Mutex::new(None)); app.insert_resource(FutureRenderResources( future_render_resources_wrapper.clone(), )); + let primary_window = app + .world_mut() + .query_filtered::<&RawHandleWrapperHolder, With>() + .single(app.world()) + .ok() + .cloned(); + let settings = render_creation.clone(); + let async_renderer = async move { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends, + flags: settings.instance_flags, + backend_options: wgpu::BackendOptions { + gl: wgpu::GlBackendOptions { + gles_minor_version: settings.gles3_minor_version, + fence_behavior: wgpu::GlFenceBehavior::Normal, + }, + dx12: wgpu::Dx12BackendOptions { + shader_compiler: settings.dx12_shader_compiler.clone(), + }, + noop: wgpu::NoopBackendOptions { enable: false }, + }, + }); + + let surface = primary_window.and_then(|wrapper| { + let maybe_handle = wrapper.0.lock().expect( + "Couldn't get the window handle in time for renderer initialization", + ); + if let Some(wrapper) = maybe_handle.as_ref() { + // SAFETY: Plugins should be set up on the main thread. + let handle = unsafe { wrapper.get_handle() }; + Some( + instance + .create_surface(handle) + .expect("Failed to create wgpu surface"), + ) + } else { + None + } + }); + + let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER") + .map_or(settings.force_fallback_adapter, |v| { + !(v.is_empty() || v == "0" || v == "false") + }); + + let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") + .as_deref() + .map_or(settings.adapter_name.clone(), |x| Some(x.to_lowercase())); + + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: settings.power_preference, + compatible_surface: surface.as_ref(), + force_fallback_adapter, + }; + + let (device, queue, adapter_info, render_adapter) = + renderer::initialize_renderer( + &instance, + &settings, + &request_adapter_options, + desired_adapter_name, + ) + .await; + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + let mut future_render_resources_inner = + future_render_resources_wrapper.lock().unwrap(); + *future_render_resources_inner = Some(RenderResources( + device, + queue, + adapter_info, + render_adapter, + RenderInstance(Arc::new(WgpuWrapper::new(instance))), + )); + }; + // In wasm, spawn a task and detach it for execution + #[cfg(target_arch = "wasm32")] + bevy_tasks::IoTaskPool::get() + .spawn_local(async_renderer) + .detach(); + // Otherwise, just block for it to complete + #[cfg(not(target_arch = "wasm32"))] + futures_lite::future::block_on(async_renderer); + // SAFETY: Plugins should be set up on the main thread. unsafe { initialize_render_app(app) }; } @@ -383,13 +465,6 @@ impl Plugin for RenderPlugin { Render, reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup), ); - if let RenderCreation::Automatic(render_creation) = &self.render_creation { - if render_creation.backends.is_some() { - render_app - .insert_resource(AutomaticRendererCreationSettings(render_creation.clone())) - .add_systems(RenderStartup, initialize_renderer); - } - } } app.register_type::() @@ -445,96 +520,6 @@ impl Plugin for RenderPlugin { } } -#[derive(Resource)] -struct AutomaticRendererCreationSettings(WgpuSettings); - -fn initialize_renderer( - primary_window: Option>>, - future_render_resources: Res, - render_creation: When>, -) { - let primary_window = primary_window.map(|primary_window| primary_window.clone()); - let settings = render_creation.into_inner().0.clone(); - let Some(backends) = settings.backends else { - return; - }; - let async_renderer = async move { - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends, - flags: settings.instance_flags, - backend_options: wgpu::BackendOptions { - gl: wgpu::GlBackendOptions { - gles_minor_version: settings.gles3_minor_version, - fence_behavior: wgpu::GlFenceBehavior::Normal, - }, - dx12: wgpu::Dx12BackendOptions { - shader_compiler: settings.dx12_shader_compiler.clone(), - }, - noop: wgpu::NoopBackendOptions { enable: false }, - }, - }); - - let surface = primary_window.and_then(|wrapper| { - let maybe_handle = wrapper - .0 - .lock() - .expect("Couldn't get the window handle in time for renderer initialization"); - if let Some(wrapper) = maybe_handle.as_ref() { - // SAFETY: Plugins should be set up on the main thread. - let handle = unsafe { wrapper.get_handle() }; - Some( - instance - .create_surface(handle) - .expect("Failed to create wgpu surface"), - ) - } else { - None - } - }); - - let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER") - .map_or(settings.force_fallback_adapter, |v| { - !(v.is_empty() || v == "0" || v == "false") - }); - - let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") - .as_deref() - .map_or(settings.adapter_name.clone(), |x| Some(x.to_lowercase())); - - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: settings.power_preference, - compatible_surface: surface.as_ref(), - force_fallback_adapter, - }; - - let (device, queue, adapter_info, render_adapter) = renderer::initialize_renderer( - &instance, - &settings, - &request_adapter_options, - desired_adapter_name, - ) - .await; - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_render_resources_inner = future_render_resources.0.lock().unwrap(); - *future_render_resources_inner = Some(RenderResources( - device, - queue, - adapter_info, - render_adapter, - RenderInstance(Arc::new(WgpuWrapper::new(instance))), - )); - }; - // In wasm, spawn a task and detach it for execution - #[cfg(target_arch = "wasm32")] - bevy_tasks::IoTaskPool::get() - .spawn_local(async_renderer) - .detach(); - // Otherwise, just block for it to complete - #[cfg(not(target_arch = "wasm32"))] - futures_lite::future::block_on(async_renderer); -} - /// A "scratch" world used to avoid allocating new worlds every frame when /// swapping out the [`MainWorld`] for [`ExtractSchedule`]. #[derive(Resource, Default)]