Add headless mode (#3439)

# Objective

In this PR I added the ability to opt-out graphical backends. Closes #3155.

## Solution

I turned backends into `Option` ~~and removed panicking sub app API to force users handle the error (was suggested by `@cart`)~~.
This commit is contained in:
Hennadii Chernyshchyk 2022-01-08 10:39:43 +00:00
parent 2ee38cb9e0
commit 458cb7a9e9
19 changed files with 283 additions and 230 deletions

View File

@ -249,6 +249,10 @@ path = "examples/app/return_after_run.rs"
name = "thread_pool_resources" name = "thread_pool_resources"
path = "examples/app/thread_pool_resources.rs" path = "examples/app/thread_pool_resources.rs"
[[example]]
name = "headless_defaults"
path = "examples/app/headless_defaults.rs"
[[example]] [[example]]
name = "without_winit" name = "without_winit"
path = "examples/app/without_winit.rs" path = "examples/app/without_winit.rs"

View File

@ -95,7 +95,11 @@ impl Plugin for CorePipelinePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<ClearColor>(); app.init_resource::<ClearColor>();
let render_app = app.sub_app_mut(RenderApp); let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app render_app
.init_resource::<DrawFunctions<Transparent2d>>() .init_resource::<DrawFunctions<Transparent2d>>()
.init_resource::<DrawFunctions<Opaque3d>>() .init_resource::<DrawFunctions<Opaque3d>>()

View File

@ -138,7 +138,11 @@ impl Plugin for PbrPlugin {
}, },
); );
let render_app = app.sub_app_mut(RenderApp); let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app render_app
.add_system_to_stage( .add_system_to_stage(
RenderStage::Extract, RenderStage::Extract,

View File

@ -53,12 +53,14 @@ impl Plugin for MeshRenderPlugin {
app.add_plugin(UniformComponentPlugin::<MeshUniform>::default()); app.add_plugin(UniformComponentPlugin::<MeshUniform>::default());
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<MeshPipeline>() .init_resource::<MeshPipeline>()
.add_system_to_stage(RenderStage::Extract, extract_meshes) .add_system_to_stage(RenderStage::Extract, extract_meshes)
.add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group) .add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups); .add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups);
} }
}
} }
#[derive(Component, AsStd140, Clone)] #[derive(Component, AsStd140, Clone)]

View File

@ -31,7 +31,8 @@ impl Plugin for WireframePlugin {
app.init_resource::<WireframeConfig>(); app.init_resource::<WireframeConfig>();
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.add_render_command::<Opaque3d, DrawWireframes>() .add_render_command::<Opaque3d, DrawWireframes>()
.init_resource::<WireframePipeline>() .init_resource::<WireframePipeline>()
.init_resource::<SpecializedPipelines<WireframePipeline>>() .init_resource::<SpecializedPipelines<WireframePipeline>>()
@ -39,6 +40,7 @@ impl Plugin for WireframePlugin {
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config) .add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
.add_system_to_stage(RenderStage::Queue, queue_wireframes); .add_system_to_stage(RenderStage::Queue, queue_wireframes);
} }
}
} }
fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<WireframeConfig>) { fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<WireframeConfig>) {

View File

@ -53,10 +53,12 @@ impl Plugin for CameraPlugin {
CoreStage::PostUpdate, CoreStage::PostUpdate,
crate::camera::camera_system::<PerspectiveProjection>, crate::camera::camera_system::<PerspectiveProjection>,
); );
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedCameraNames>() .init_resource::<ExtractedCameraNames>()
.add_system_to_stage(RenderStage::Extract, extract_cameras); .add_system_to_stage(RenderStage::Extract, extract_cameras);
} }
}
} }
#[derive(Default)] #[derive(Default)]

View File

@ -113,7 +113,13 @@ impl Plugin for RenderPlugin {
.get_resource::<options::WgpuOptions>() .get_resource::<options::WgpuOptions>()
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
let instance = wgpu::Instance::new(options.backends);
app.add_asset::<Shader>()
.init_asset_loader::<ShaderLoader>()
.register_type::<Color>();
if let Some(backends) = options.backends {
let instance = wgpu::Instance::new(backends);
let surface = { let surface = {
let world = app.world.cell(); let world = app.world.cell();
let windows = world.get_resource_mut::<bevy_window::Windows>().unwrap(); let windows = world.get_resource_mut::<bevy_window::Windows>().unwrap();
@ -138,10 +144,7 @@ impl Plugin for RenderPlugin {
app.insert_resource(device.clone()) app.insert_resource(device.clone())
.insert_resource(queue.clone()) .insert_resource(queue.clone())
.insert_resource(options.clone()) .insert_resource(options.clone())
.add_asset::<Shader>()
.init_asset_loader::<ShaderLoader>()
.init_resource::<ScratchRenderWorld>() .init_resource::<ScratchRenderWorld>()
.register_type::<Color>()
.register_type::<Frustum>() .register_type::<Frustum>()
.register_type::<CubemapFrusta>(); .register_type::<CubemapFrusta>();
let render_pipeline_cache = RenderPipelineCache::new(device.clone()); let render_pipeline_cache = RenderPipelineCache::new(device.clone());
@ -281,6 +284,7 @@ impl Plugin for RenderPlugin {
render_app.world.clear_entities(); render_app.world.clear_entities();
} }
}); });
}
app.add_plugin(WindowRenderPlugin) app.add_plugin(WindowRenderPlugin)
.add_plugin(CameraPlugin) .add_plugin(CameraPlugin)

View File

@ -12,7 +12,7 @@ pub enum WgpuOptionsPriority {
#[derive(Clone)] #[derive(Clone)]
pub struct WgpuOptions { pub struct WgpuOptions {
pub device_label: Option<Cow<'static, str>>, pub device_label: Option<Cow<'static, str>>,
pub backends: Backends, pub backends: Option<Backends>,
pub power_preference: PowerPreference, pub power_preference: PowerPreference,
pub priority: WgpuOptionsPriority, pub priority: WgpuOptionsPriority,
pub features: WgpuFeatures, pub features: WgpuFeatures,
@ -27,7 +27,7 @@ impl Default for WgpuOptions {
Backends::PRIMARY Backends::PRIMARY
}; };
let backends = wgpu::util::backend_bits_from_env().unwrap_or(default_backends); let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(default_backends));
let priority = options_priority_from_env().unwrap_or(WgpuOptionsPriority::Functionality); let priority = options_priority_from_env().unwrap_or(WgpuOptionsPriority::Functionality);

View File

@ -54,7 +54,7 @@ impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> { impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let render_app = app.sub_app_mut(RenderApp); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
let prepare_asset_system = PrepareAssetSystem::<A>::system(&mut render_app.world); let prepare_asset_system = PrepareAssetSystem::<A>::system(&mut render_app.world);
render_app render_app
.init_resource::<ExtractedAssets<A>>() .init_resource::<ExtractedAssets<A>>()
@ -63,6 +63,7 @@ impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
.add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>) .add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>)
.add_system_to_stage(RenderStage::Prepare, prepare_asset_system); .add_system_to_stage(RenderStage::Prepare, prepare_asset_system);
} }
}
} }
/// Temporarily stores the extracted and removed assets of the current frame. /// Temporarily stores the extracted and removed assets of the current frame.

View File

@ -62,13 +62,15 @@ impl<C> Default for UniformComponentPlugin<C> {
impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> { impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.insert_resource(ComponentUniforms::<C>::default()) .insert_resource(ComponentUniforms::<C>::default())
.add_system_to_stage( .add_system_to_stage(
RenderStage::Prepare, RenderStage::Prepare,
prepare_uniform_components::<C>.system(), prepare_uniform_components::<C>.system(),
); );
} }
}
} }
/// Stores all uniforms of the component type. /// Stores all uniforms of the component type.
@ -144,9 +146,10 @@ where
{ {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let system = ExtractComponentSystem::<C>::system(&mut app.world); let system = ExtractComponentSystem::<C>::system(&mut app.world);
let render_app = app.sub_app_mut(RenderApp); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage(RenderStage::Extract, system); render_app.add_system_to_stage(RenderStage::Extract, system);
} }
}
} }
impl<T: Asset> ExtractComponent for Handle<T> { impl<T: Asset> ExtractComponent for Handle<T> {

View File

@ -35,10 +35,12 @@ impl Plugin for ImagePlugin {
.unwrap() .unwrap()
.set_untracked(DEFAULT_IMAGE_HANDLE, Image::default()); .set_untracked(DEFAULT_IMAGE_HANDLE, Image::default());
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<TextureCache>() .init_resource::<TextureCache>()
.add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system);
} }
}
} }
pub trait BevyDefault { pub trait BevyDefault {

View File

@ -26,7 +26,8 @@ impl Plugin for ViewPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<Msaa>().add_plugin(VisibilityPlugin); app.init_resource::<Msaa>().add_plugin(VisibilityPlugin);
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewUniforms>() .init_resource::<ViewUniforms>()
.add_system_to_stage(RenderStage::Extract, extract_msaa) .add_system_to_stage(RenderStage::Extract, extract_msaa)
.add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms) .add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms)
@ -35,6 +36,7 @@ impl Plugin for ViewPlugin {
prepare_view_targets.after(WindowSystem::Prepare), prepare_view_targets.after(WindowSystem::Prepare),
); );
} }
}
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -24,7 +24,8 @@ pub enum WindowSystem {
impl Plugin for WindowRenderPlugin { impl Plugin for WindowRenderPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedWindows>() .init_resource::<ExtractedWindows>()
.init_resource::<WindowSurfaces>() .init_resource::<WindowSurfaces>()
.init_resource::<NonSendMarker>() .init_resource::<NonSendMarker>()
@ -34,6 +35,7 @@ impl Plugin for WindowRenderPlugin {
prepare_windows.label(WindowSystem::Prepare), prepare_windows.label(WindowSystem::Prepare),
); );
} }
}
} }
pub struct ExtractedWindow { pub struct ExtractedWindow {

View File

@ -59,7 +59,8 @@ impl Plugin for SpritePlugin {
.register_type::<Sprite>() .register_type::<Sprite>()
.add_plugin(Mesh2dRenderPlugin) .add_plugin(Mesh2dRenderPlugin)
.add_plugin(ColorMaterialPlugin); .add_plugin(ColorMaterialPlugin);
let render_app = app.sub_app_mut(RenderApp);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app render_app
.init_resource::<ImageBindGroups>() .init_resource::<ImageBindGroups>()
.init_resource::<SpritePipeline>() .init_resource::<SpritePipeline>()
@ -74,5 +75,6 @@ impl Plugin for SpritePlugin {
) )
.add_system_to_stage(RenderStage::Extract, render::extract_sprite_events) .add_system_to_stage(RenderStage::Extract, render::extract_sprite_events)
.add_system_to_stage(RenderStage::Queue, queue_sprites); .add_system_to_stage(RenderStage::Queue, queue_sprites);
};
} }
} }

View File

@ -61,13 +61,15 @@ impl Plugin for Mesh2dRenderPlugin {
app.add_plugin(UniformComponentPlugin::<Mesh2dUniform>::default()); app.add_plugin(UniformComponentPlugin::<Mesh2dUniform>::default());
app.sub_app_mut(RenderApp) if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<Mesh2dPipeline>() .init_resource::<Mesh2dPipeline>()
.init_resource::<SpecializedPipelines<Mesh2dPipeline>>() .init_resource::<SpecializedPipelines<Mesh2dPipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_mesh2d) .add_system_to_stage(RenderStage::Extract, extract_mesh2d)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group)
.add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups);
} }
}
} }
#[derive(Component, AsStd140, Clone)] #[derive(Component, AsStd140, Clone)]

View File

@ -48,10 +48,11 @@ impl Plugin for TextPlugin {
.insert_resource(DefaultTextPipeline::default()) .insert_resource(DefaultTextPipeline::default())
.add_system_to_stage(CoreStage::PostUpdate, text2d_system); .add_system_to_stage(CoreStage::PostUpdate, text2d_system);
let render_app = app.sub_app_mut(RenderApp); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage( render_app.add_system_to_stage(
RenderStage::Extract, RenderStage::Extract,
extract_text2d_sprite.after(SpriteSystem::ExtractSprites), extract_text2d_sprite.after(SpriteSystem::ExtractSprites),
); );
} }
}
} }

View File

@ -66,7 +66,11 @@ pub fn build_ui_render(app: &mut App) {
let mut active_cameras = app.world.get_resource_mut::<ActiveCameras>().unwrap(); let mut active_cameras = app.world.get_resource_mut::<ActiveCameras>().unwrap();
active_cameras.add(CAMERA_UI); active_cameras.add(CAMERA_UI);
let render_app = app.sub_app_mut(RenderApp); let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app render_app
.init_resource::<UiPipeline>() .init_resource::<UiPipeline>()
.init_resource::<SpecializedPipelines<UiPipeline>>() .init_resource::<SpecializedPipelines<UiPipeline>>()

View File

@ -121,6 +121,7 @@ Example | File | Description
`empty` | [`app/empty.rs`](./app/empty.rs) | An empty application (does nothing) `empty` | [`app/empty.rs`](./app/empty.rs) | An empty application (does nothing)
`empty_defaults` | [`app/empty_defaults.rs`](./app/empty_defaults.rs) | An empty application with default plugins `empty_defaults` | [`app/empty_defaults.rs`](./app/empty_defaults.rs) | An empty application with default plugins
`headless` | [`app/headless.rs`](./app/headless.rs) | An application that runs without default plugins `headless` | [`app/headless.rs`](./app/headless.rs) | An application that runs without default plugins
`headless_defaults` | [`app/headless_defaults.rs`](./app/headless_defaults.rs) | An application that runs with default plugins, but without an actual renderer
`logs` | [`app/logs.rs`](./app/logs.rs) | Illustrate how to use generate log output `logs` | [`app/logs.rs`](./app/logs.rs) | Illustrate how to use generate log output
`plugin` | [`app/plugin.rs`](./app/plugin.rs) | Demonstrates the creation and registration of a custom plugin `plugin` | [`app/plugin.rs`](./app/plugin.rs) | Demonstrates the creation and registration of a custom plugin
`plugin_group` | [`app/plugin_group.rs`](./app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group `plugin_group` | [`app/plugin_group.rs`](./app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group

View File

@ -0,0 +1,11 @@
use bevy::{prelude::*, render::options::WgpuOptions};
fn main() {
App::new()
.insert_resource(WgpuOptions {
backends: None,
..Default::default()
})
.add_plugins(DefaultPlugins)
.run();
}