Skip to content
97 changes: 26 additions & 71 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ use crate::{
texture::TexturePlugin,
view::{ViewPlugin, WindowRenderPlugin},
};
use alloc::sync::Arc;
use batching::gpu_preprocessing::BatchingPlugin;
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_asset::{AssetApp, AssetServer};
Expand All @@ -108,7 +107,6 @@ use render_asset::{
RenderAssetBytesPerFrame, RenderAssetBytesPerFrameLimiter,
};
use settings::RenderResources;
use std::sync::Mutex;
use sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin};

/// Contains the default Bevy rendering backend based on wgpu.
Expand Down Expand Up @@ -285,9 +283,6 @@ pub mod graph {
pub struct CameraDriverLabel;
}

#[derive(Resource)]
struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);

/// A label for the rendering sub-app.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp;
Expand All @@ -298,64 +293,30 @@ impl Plugin for RenderPlugin {
app.init_asset::<Shader>()
.init_asset_loader::<ShaderLoader>();

match &self.render_creation {
RenderCreation::Manual(resources) => {
let future_render_resources_wrapper = Arc::new(Mutex::new(Some(resources.clone())));
app.insert_resource(FutureRenderResources(
future_render_resources_wrapper.clone(),
));
// SAFETY: Plugins should be set up on the main thread.
unsafe { initialize_render_app(app) };
}
RenderCreation::Automatic(render_creation) => {
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<PrimaryWindow>>()
.single(app.world())
.ok()
.cloned();

let settings = render_creation.clone();

#[cfg(feature = "raw_vulkan_init")]
let raw_vulkan_init_settings = app
.world_mut()
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
.cloned()
.unwrap_or_default();

let async_renderer = async move {
let render_resources = renderer::initialize_renderer(
backends,
primary_window,
&settings,
#[cfg(feature = "raw_vulkan_init")]
raw_vulkan_init_settings,
)
.await;

*future_render_resources_wrapper.lock().unwrap() = Some(render_resources);
};

// 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"))]
bevy_tasks::block_on(async_renderer);

// SAFETY: Plugins should be set up on the main thread.
unsafe { initialize_render_app(app) };
}
}
let primary_window = app
.world_mut()
.query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
.single(app.world())
.ok()
.cloned();

#[cfg(feature = "raw_vulkan_init")]
let raw_vulkan_init_settings = app
.world_mut()
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
.cloned()
.unwrap_or_default();

let render_resources = self.render_creation.create_render(
primary_window,
#[cfg(feature = "raw_vulkan_init")]
raw_vulkan_init_settings,
);

if let Some(render_resources) = render_resources {
app.insert_resource(render_resources);
// SAFETY: Plugins should be set up on the main thread.
unsafe { initialize_render_app(app) };
};

app.add_plugins((
Expand Down Expand Up @@ -391,20 +352,14 @@ impl Plugin for RenderPlugin {
}

fn ready(&self, app: &App) -> bool {
app.world()
.get_resource::<FutureRenderResources>()
.and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok())
.unwrap_or(true)
app.world().get_resource::<RenderResources>().is_some()
Copy link
Contributor

@kfc35 kfc35 Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might not help, but I checked out your branch to see why the no_renderer example is failing, and I think it might have something to do with the ready function. If I change this to just return true, the example runs fine / I am able to close the window.

The previous ready seems to return true even if it wasn’t able to lock FutureRenderResources iiuc. I wonder if the example is able to progress because of that rather than progressing because RenderResources is Some

}

fn finish(&self, app: &mut App) {
load_shader_library!(app, "maths.wgsl");
load_shader_library!(app, "color_operations.wgsl");
load_shader_library!(app, "bindless.wgsl");
if let Some(future_render_resources) =
app.world_mut().remove_resource::<FutureRenderResources>()
{
let render_resources = future_render_resources.0.lock().unwrap().take().unwrap();
if let Some(render_resources) = app.world_mut().remove_resource::<RenderResources>() {
let RenderResources(device, queue, adapter_info, render_adapter, instance, ..) =
render_resources;

Expand Down
57 changes: 51 additions & 6 deletions crates/bevy_render/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
};
use alloc::borrow::Cow;
use alloc::{borrow::Cow, sync::Arc};
use bevy_ecs::resource::Resource;
use bevy_window::RawHandleWrapperHolder;
use std::sync::Mutex;

pub use wgpu::{
Backends, Dx12Compiler, Features as WgpuFeatures, Gles3MinorVersion, InstanceFlags,
Expand Down Expand Up @@ -144,15 +147,14 @@ impl Default for WgpuSettings {
}
}

#[derive(Clone)]
#[derive(Clone, Resource)]
pub struct RenderResources(
pub RenderDevice,
pub RenderQueue,
pub RenderAdapterInfo,
pub RenderAdapter,
pub RenderInstance,
#[cfg(feature = "raw_vulkan_init")]
pub crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures,
#[cfg(feature = "raw_vulkan_init")] pub renderer::raw_vulkan_init::AdditionalVulkanFeatures,
);

/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin).
Expand All @@ -176,7 +178,7 @@ impl RenderCreation {
adapter: RenderAdapter,
instance: RenderInstance,
#[cfg(feature = "raw_vulkan_init")]
additional_vulkan_features: crate::renderer::raw_vulkan_init::AdditionalVulkanFeatures,
additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures,
) -> Self {
RenderResources(
device,
Expand All @@ -189,6 +191,49 @@ impl RenderCreation {
)
.into()
}

/// Creates [`RenderResources`] from this [`RenderCreation`] and an optional primary window.
/// Note: [`RenderCreation::Manual`] will ignore the provided primary window.
pub fn create_render(
&self,
primary_window: Option<RawHandleWrapperHolder>,
#[cfg(feature = "raw_vulkan_init")]
raw_vulkan_init_settings: renderer::raw_vulkan_init::RawVulkanInitSettings,
) -> Option<RenderResources> {
match self {
RenderCreation::Manual(resources) => Some(resources.clone()),
RenderCreation::Automatic(render_creation) => {
let backends = render_creation.backends?;
let future_render_resources_wrapper = Arc::new(Mutex::new(None));
let render_resources = future_render_resources_wrapper.clone();

let settings = render_creation.clone();

let async_renderer = async move {
let render_resources = renderer::initialize_renderer(
backends,
primary_window,
&settings,
#[cfg(feature = "raw_vulkan_init")]
raw_vulkan_init_settings,
)
.await;

*future_render_resources_wrapper.lock().unwrap() = Some(render_resources);
};

// 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"))]
bevy_tasks::block_on(async_renderer);
render_resources.lock().unwrap().clone()
}
}
}
}

impl From<RenderResources> for RenderCreation {
Expand Down
Loading