Allow other plugins to create renderer resources (#9925)

This is a duplicate of #9632, it was created since I forgot to make a
new branch when I first made this PR, so I was having trouble resolving
merge conflicts, meaning I had to rebuild my PR.

# Objective

- Allow other plugins to create the renderer resources. An example of
where this would be required is my [OpenXR
plugin](https://github.com/awtterpip/bevy_openxr)

## Solution

- Changed the bevy RenderPlugin to optionally take precreated render
resources instead of a configuration.

## Migration Guide

The `RenderPlugin` now takes a `RenderCreation` enum instead of
`WgpuSettings`. `RenderSettings::default()` returns
`RenderSettings::Automatic(WgpuSettings::default())`. `RenderSettings`
also implements `From<WgpuSettings>`.

```rust
// before
RenderPlugin {
    wgpu_settings: WgpuSettings {
    ...
    },
}

// now
RenderPlugin {
    render_creation: RenderCreation::Automatic(WgpuSettings {
    ...
    }),
}
// or
RenderPlugin {
    render_creation: WgpuSettings {
    ...
    }.into(),
}
```

---------

Co-authored-by: Malek <pocmalek@gmail.com>
Co-authored-by: Robert Swain <robert.swain@gmail.com>
This commit is contained in:
piper 2023-09-26 14:35:08 -05:00 committed by GitHub
parent bc1f33d50b
commit bc88f33e48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 123 deletions

View File

@ -45,7 +45,6 @@ use bevy_hierarchy::ValidParentCheckPlugin;
use bevy_window::{PrimaryWindow, RawHandleWrapper}; use bevy_window::{PrimaryWindow, RawHandleWrapper};
use globals::GlobalsPlugin; use globals::GlobalsPlugin;
use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use wgpu::Instance;
use crate::{ use crate::{
camera::CameraPlugin, camera::CameraPlugin,
@ -53,7 +52,7 @@ use crate::{
render_asset::prepare_assets, render_asset::prepare_assets,
render_resource::{PipelineCache, Shader, ShaderLoader}, render_resource::{PipelineCache, Shader, ShaderLoader},
renderer::{render_system, RenderInstance}, renderer::{render_system, RenderInstance},
settings::WgpuSettings, settings::RenderCreation,
view::{ViewPlugin, WindowRenderPlugin}, view::{ViewPlugin, WindowRenderPlugin},
}; };
use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_app::{App, AppLabel, Plugin, SubApp};
@ -68,7 +67,7 @@ use std::{
/// Contains the default Bevy rendering backend based on wgpu. /// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)] #[derive(Default)]
pub struct RenderPlugin { pub struct RenderPlugin {
pub wgpu_settings: WgpuSettings, pub render_creation: RenderCreation,
} }
/// The labels of the default App rendering sets. /// The labels of the default App rendering sets.
@ -221,7 +220,7 @@ struct FutureRendererResources(
RenderQueue, RenderQueue,
RenderAdapterInfo, RenderAdapterInfo,
RenderAdapter, RenderAdapter,
Instance, RenderInstance,
)>, )>,
>, >,
>, >,
@ -241,120 +240,84 @@ impl Plugin for RenderPlugin {
app.init_asset::<Shader>() app.init_asset::<Shader>()
.init_asset_loader::<ShaderLoader>(); .init_asset_loader::<ShaderLoader>();
if let Some(backends) = self.wgpu_settings.backends { match &self.render_creation {
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None)); RenderCreation::Manual(device, queue, adapter_info, adapter, instance) => {
app.insert_resource(FutureRendererResources( let future_renderer_resources_wrapper = Arc::new(Mutex::new(Some((
future_renderer_resources_wrapper.clone(), device.clone(),
)); queue.clone(),
adapter_info.clone(),
adapter.clone(),
instance.clone(),
))));
app.insert_resource(FutureRendererResources(
future_renderer_resources_wrapper.clone(),
));
unsafe { initialize_render_app(app) };
}
RenderCreation::Automatic(render_creation) => {
if let Some(backends) = render_creation.backends {
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None));
app.insert_resource(FutureRendererResources(
future_renderer_resources_wrapper.clone(),
));
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> = let mut system_state: SystemState<
SystemState::new(&mut app.world); Query<&RawHandleWrapper, With<PrimaryWindow>>,
let primary_window = system_state.get(&app.world).get_single().ok().cloned(); > = SystemState::new(&mut app.world);
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
let settings = self.wgpu_settings.clone(); let settings = render_creation.clone();
let async_renderer = async move { let async_renderer = async move {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends, backends,
dx12_shader_compiler: settings.dx12_shader_compiler.clone(), dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
}); });
let surface = primary_window.map(|wrapper| unsafe { let surface = primary_window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread. // SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle(); let handle = wrapper.get_handle();
instance instance
.create_surface(&handle) .create_surface(&handle)
.expect("Failed to create wgpu surface") .expect("Failed to create wgpu surface")
}); });
let request_adapter_options = wgpu::RequestAdapterOptions { let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: settings.power_preference, power_preference: settings.power_preference,
compatible_surface: surface.as_ref(), compatible_surface: surface.as_ref(),
..Default::default() ..Default::default()
}; };
let (device, queue, adapter_info, render_adapter) = let (device, queue, adapter_info, render_adapter) =
renderer::initialize_renderer(&instance, &settings, &request_adapter_options) renderer::initialize_renderer(
.await; &instance,
debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); &settings,
debug!("Configured wgpu adapter Features: {:#?}", device.features()); &request_adapter_options,
let mut future_renderer_resources_inner = )
future_renderer_resources_wrapper.lock().unwrap(); .await;
*future_renderer_resources_inner = debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
Some((device, queue, adapter_info, render_adapter, instance)); debug!("Configured wgpu adapter Features: {:#?}", device.features());
}; let mut future_renderer_resources_inner =
// In wasm, spawn a task and detach it for execution future_renderer_resources_wrapper.lock().unwrap();
#[cfg(target_arch = "wasm32")] *future_renderer_resources_inner = Some((
bevy_tasks::IoTaskPool::get() device,
.spawn_local(async_renderer) queue,
.detach(); adapter_info,
// Otherwise, just block for it to complete render_adapter,
#[cfg(not(target_arch = "wasm32"))] RenderInstance(Arc::new(instance)),
futures_lite::future::block_on(async_renderer); ));
};
// 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);
app.init_resource::<ScratchMainWorld>(); unsafe { initialize_render_app(app) };
let mut render_app = App::empty();
render_app.main_schedule_label = Box::new(Render);
let mut extract_schedule = Schedule::new(ExtractSchedule);
extract_schedule.set_apply_final_deferred(false);
render_app
.add_schedule(extract_schedule)
.add_schedule(Render::base_schedule())
.init_resource::<render_graph::RenderGraph>()
.insert_resource(app.world.resource::<AssetServer>().clone())
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
.add_systems(
Render,
(
// This set applies the commands from the extract schedule while the render schedule
// is running in parallel with the main app.
apply_extract_commands.in_set(RenderSet::ExtractCommands),
(
PipelineCache::process_pipeline_queue_system.before(render_system),
render_system,
)
.in_set(RenderSet::Render),
World::clear_entities.in_set(RenderSet::Cleanup),
),
);
let (sender, receiver) = bevy_time::create_time_channels();
app.insert_resource(receiver);
render_app.insert_resource(sender);
app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| {
#[cfg(feature = "trace")]
let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered();
{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("reserve_and_flush")
.entered();
// reserve all existing main world entities for use in render_app
// they can only be spawned using `get_or_spawn()`
let total_count = main_world.entities().total_count();
assert_eq!(
render_app.world.entities().len(),
0,
"An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported",
);
// This is safe given the clear_entities call in the past frame and the assert above
unsafe {
render_app
.world
.entities_mut()
.flush_and_reserve_invalid_assuming_no_entities(total_count);
}
} }
}
// run extract schedule };
extract(main_world, render_app);
}));
}
app.add_plugins(( app.add_plugins((
ValidParentCheckPlugin::<view::InheritedVisibility>::default(), ValidParentCheckPlugin::<view::InheritedVisibility>::default(),
@ -406,7 +369,7 @@ impl Plugin for RenderPlugin {
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.insert_resource(RenderInstance(instance)) .insert_resource(instance)
.insert_resource(PipelineCache::new(device.clone())) .insert_resource(PipelineCache::new(device.clone()))
.insert_resource(device) .insert_resource(device)
.insert_resource(queue) .insert_resource(queue)
@ -437,6 +400,74 @@ fn extract(main_world: &mut World, render_app: &mut App) {
main_world.insert_resource(ScratchMainWorld(scratch_world)); main_world.insert_resource(ScratchMainWorld(scratch_world));
} }
/// SAFETY: this function must be called from the main thread.
unsafe fn initialize_render_app(app: &mut App) {
app.init_resource::<ScratchMainWorld>();
let mut render_app = App::empty();
render_app.main_schedule_label = Box::new(Render);
let mut extract_schedule = Schedule::new(ExtractSchedule);
extract_schedule.set_apply_final_deferred(false);
render_app
.add_schedule(extract_schedule)
.add_schedule(Render::base_schedule())
.init_resource::<render_graph::RenderGraph>()
.insert_resource(app.world.resource::<AssetServer>().clone())
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
.add_systems(
Render,
(
// This set applies the commands from the extract schedule while the render schedule
// is running in parallel with the main app.
apply_extract_commands.in_set(RenderSet::ExtractCommands),
(
PipelineCache::process_pipeline_queue_system.before(render_system),
render_system,
)
.in_set(RenderSet::Render),
World::clear_entities.in_set(RenderSet::Cleanup),
),
);
let (sender, receiver) = bevy_time::create_time_channels();
app.insert_resource(receiver);
render_app.insert_resource(sender);
app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| {
#[cfg(feature = "trace")]
let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered();
{
#[cfg(feature = "trace")]
let _stage_span =
bevy_utils::tracing::info_span!("reserve_and_flush")
.entered();
// reserve all existing main world entities for use in render_app
// they can only be spawned using `get_or_spawn()`
let total_count = main_world.entities().total_count();
assert_eq!(
render_app.world.entities().len(),
0,
"An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported",
);
// This is safe given the clear_entities call in the past frame and the assert above
unsafe {
render_app
.world
.entities_mut()
.flush_and_reserve_invalid_assuming_no_entities(total_count);
}
}
// run extract schedule
extract(main_world, render_app);
}));
}
/// Applies the commands from the extract schedule. This happens during /// Applies the commands from the extract schedule. This happens during
/// the render schedule rather than during extraction to allow the commands to run in parallel with the /// the render schedule rather than during extraction to allow the commands to run in parallel with the
/// main app when pipelined rendering is enabled. /// main app when pipelined rendering is enabled.

View File

@ -104,8 +104,8 @@ pub struct RenderAdapter(pub Arc<Adapter>);
/// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`], /// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`],
/// as well as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces). /// as well as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces).
#[derive(Resource, Deref, DerefMut)] #[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderInstance(pub Instance); pub struct RenderInstance(pub Arc<Instance>);
/// The [`AdapterInfo`] of the adapter in use by the renderer. /// The [`AdapterInfo`] of the adapter in use by the renderer.
#[derive(Resource, Clone, Deref, DerefMut)] #[derive(Resource, Clone, Deref, DerefMut)]

View File

@ -1,3 +1,6 @@
use crate::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
};
use std::borrow::Cow; use std::borrow::Cow;
pub use wgpu::{ pub use wgpu::{
@ -93,6 +96,45 @@ impl Default for WgpuSettings {
} }
} }
/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin).
pub enum RenderCreation {
/// Allows renderer resource initialization to happen outside of the rendering plugin.
Manual(
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
RenderInstance,
),
/// Lets the rendering plugin create resources itself.
Automatic(WgpuSettings),
}
impl RenderCreation {
/// Function to create a [`RenderCreation::Manual`] variant.
pub fn manual(
device: RenderDevice,
queue: RenderQueue,
adapter_info: RenderAdapterInfo,
adapter: RenderAdapter,
instance: RenderInstance,
) -> Self {
Self::Manual(device, queue, adapter_info, adapter, instance)
}
}
impl Default for RenderCreation {
fn default() -> Self {
Self::Automatic(Default::default())
}
}
impl From<WgpuSettings> for RenderCreation {
fn from(value: WgpuSettings) -> Self {
Self::Automatic(value)
}
}
/// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO` /// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO`
pub fn settings_priority_from_env() -> Option<WgpuSettingsPriority> { pub fn settings_priority_from_env() -> Option<WgpuSettingsPriority> {
Some( Some(

View File

@ -10,10 +10,11 @@ fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
DefaultPlugins.set(RenderPlugin { DefaultPlugins.set(RenderPlugin {
wgpu_settings: WgpuSettings { render_creation: WgpuSettings {
features: WgpuFeatures::POLYGON_MODE_LINE, features: WgpuFeatures::POLYGON_MODE_LINE,
..default() ..default()
}, }
.into(),
}), }),
WireframePlugin, WireframePlugin,
)) ))

View File

@ -11,11 +11,14 @@ use bevy::{
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins.set(RenderPlugin { .add_plugins(
wgpu_settings: WgpuSettings { DefaultPlugins.set(RenderPlugin {
backends: None, render_creation: WgpuSettings {
..default() backends: None,
}, ..default()
})) }
.into(),
}),
)
.run(); .run();
} }