Make RenderStage::Extract run on the render world (#4402)

# Objective

- Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource.
- However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource.
- This meant that effectively only one extract which wrote to resources could run at a time.
- We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that.

## Solution

- Move the extract stage to run on the render world.
- Add the main world as a `MainWorld` resource.
- Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`.

## Future work

It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on.
We need to determine how this interacts with https://github.com/bevyengine/bevy/pull/3519
It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too.

## Todo
I still need to add doc comments to `Extract`.

---

## Changelog

### Changed
- The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
   You must use the `Extract` `SystemParam` to access the main world during the extract phase.
   Resources on the render world can now be accessed using `ResMut` during extract.

### Removed
- `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead

## Migration Guide

The `Extract` `RenderStage` now runs on the render world (instead of the main world as before).
You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it.

For example, if previously your extract system looked like:
```rust
fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
    for cloud in clouds.iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
the new version would be:
```rust
fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
    for cloud in clouds.value().iter() {
        commands.get_or_spawn(cloud).insert(Cloud);
    }
}
```
The diff is:
```diff
--- a/src/clouds.rs
+++ b/src/clouds.rs
@@ -1,5 +1,5 @@
-fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) {
-    for cloud in clouds.iter() {
+fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) {
+    for cloud in clouds.value().iter() {
         commands.get_or_spawn(cloud).insert(Cloud);
     }
 }
```
You can now also access resources from the render world using the normal system parameters during `Extract`:
```rust
fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) {
     *render_assets = source_assets.clone();
}
```
Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met.

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Daniel McNab 2022-07-08 23:56:33 +00:00
parent e6faf993b0
commit 7b2cf98896
22 changed files with 375 additions and 192 deletions

View file

@ -25,7 +25,7 @@ use bevy_render::{
DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase, DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
}, },
render_resource::CachedRenderPipelineId, render_resource::CachedRenderPipelineId,
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_utils::FloatOrd; use bevy_utils::FloatOrd;
use std::ops::Range; use std::ops::Range;
@ -123,7 +123,7 @@ impl BatchedPhaseItem for Transparent2d {
pub fn extract_core_2d_camera_phases( pub fn extract_core_2d_camera_phases(
mut commands: Commands, mut commands: Commands,
cameras_2d: Query<(Entity, &Camera), With<Camera2d>>, cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
) { ) {
for (entity, camera) in cameras_2d.iter() { for (entity, camera) in cameras_2d.iter() {
if camera.is_active { if camera.is_active {

View file

@ -34,7 +34,7 @@ use bevy_render::{
renderer::RenderDevice, renderer::RenderDevice,
texture::TextureCache, texture::TextureCache,
view::ViewDepthTexture, view::ViewDepthTexture,
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_utils::{FloatOrd, HashMap}; use bevy_utils::{FloatOrd, HashMap};
@ -208,7 +208,7 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
pub fn extract_core_3d_camera_phases( pub fn extract_core_3d_camera_phases(
mut commands: Commands, mut commands: Commands,
cameras_3d: Query<(Entity, &Camera), With<Camera3d>>, cameras_3d: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
) { ) {
for (entity, camera) in cameras_3d.iter() { for (entity, camera) in cameras_3d.iter() {
if camera.is_active { if camera.is_active {

View file

@ -12,7 +12,10 @@ use crate::{
}, },
world::{World, WorldId}, world::{World, WorldId},
}; };
use bevy_utils::{tracing::info, HashMap, HashSet}; use bevy_utils::{
tracing::{info, warn},
HashMap, HashSet,
};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use fixedbitset::FixedBitSet; use fixedbitset::FixedBitSet;
use std::fmt::Debug; use std::fmt::Debug;
@ -88,6 +91,7 @@ pub struct SystemStage {
last_tick_check: u32, last_tick_check: u32,
/// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied. /// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied.
apply_buffers: bool, apply_buffers: bool,
must_read_resource: Option<ComponentId>,
} }
impl SystemStage { impl SystemStage {
@ -110,6 +114,7 @@ impl SystemStage {
uninitialized_at_end: vec![], uninitialized_at_end: vec![],
last_tick_check: Default::default(), last_tick_check: Default::default(),
apply_buffers: true, apply_buffers: true,
must_read_resource: None,
} }
} }
@ -139,6 +144,10 @@ impl SystemStage {
self.executor = executor; self.executor = executor;
} }
pub fn set_must_read_resource(&mut self, resource_id: ComponentId) {
self.must_read_resource = Some(resource_id);
}
#[must_use] #[must_use]
pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self { pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self {
self.add_system(system); self.add_system(system);
@ -563,6 +572,20 @@ impl SystemStage {
} }
} }
fn check_uses_resource(&self, resource_id: ComponentId, world: &World) {
debug_assert!(!self.systems_modified);
for system in &self.parallel {
let access = system.component_access().unwrap();
if !access.has_read(resource_id) {
let component_name = world.components().get_info(resource_id).unwrap().name();
warn!(
"System {} doesn't access resource {component_name}, despite being required to",
system.name()
);
}
}
}
/// All system and component change ticks are scanned once the world counter has incremented /// All system and component change ticks are scanned once the world counter has incremented
/// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD)
/// times since the previous `check_tick` scan. /// times since the previous `check_tick` scan.
@ -782,6 +805,9 @@ impl Stage for SystemStage {
if world.contains_resource::<ReportExecutionOrderAmbiguities>() { if world.contains_resource::<ReportExecutionOrderAmbiguities>() {
self.report_ambiguities(world); self.report_ambiguities(world);
} }
if let Some(resource_id) = self.must_read_resource {
self.check_uses_resource(resource_id, world);
}
} else if self.executor_modified { } else if self.executor_modified {
self.executor.rebuild_cached_data(&self.parallel); self.executor.rebuild_cached_data(&self.parallel);
self.executor_modified = false; self.executor_modified = false;

View file

@ -145,12 +145,6 @@ impl<'w, 's> Commands<'w, 's> {
} }
} }
/// Spawns a [`Bundle`] without pre-allocating an [`Entity`]. The [`Entity`] will be allocated
/// when this [`Command`] is applied.
pub fn spawn_and_forget(&mut self, bundle: impl Bundle) {
self.queue.push(Spawn { bundle });
}
/// Creates a new entity with the components contained in `bundle`. /// Creates a new entity with the components contained in `bundle`.
/// ///
/// This returns an [`EntityCommands`] builder, which enables inserting more components and /// This returns an [`EntityCommands`] builder, which enables inserting more components and

View file

@ -34,7 +34,7 @@ use bevy_render::{
renderer::RenderDevice, renderer::RenderDevice,
texture::FallbackImage, texture::FallbackImage,
view::{ExtractedView, Msaa, VisibleEntities}, view::{ExtractedView, Msaa, VisibleEntities},
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_utils::{tracing::error, HashMap, HashSet}; use bevy_utils::{tracing::error, HashMap, HashSet};
use std::hash::Hash; use std::hash::Hash;
@ -455,15 +455,15 @@ pub type RenderMaterials<T> = HashMap<Handle<T>, PreparedMaterial<T>>;
/// into the "render world". /// into the "render world".
fn extract_materials<M: Material>( fn extract_materials<M: Material>(
mut commands: Commands, mut commands: Commands,
mut events: EventReader<AssetEvent<M>>, mut events: Extract<EventReader<AssetEvent<M>>>,
assets: Res<Assets<M>>, assets: Extract<Res<Assets<M>>>,
) { ) {
let mut changed_assets = HashSet::default(); let mut changed_assets = HashSet::default();
let mut removed = Vec::new(); let mut removed = Vec::new();
for event in events.iter() { for event in events.iter() {
match event { match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
changed_assets.insert(handle); changed_assets.insert(handle.clone_weak());
} }
AssetEvent::Removed { handle } => { AssetEvent::Removed { handle } => {
changed_assets.remove(handle); changed_assets.remove(handle);
@ -474,8 +474,8 @@ fn extract_materials<M: Material>(
let mut extracted_assets = Vec::new(); let mut extracted_assets = Vec::new();
for handle in changed_assets.drain() { for handle in changed_assets.drain() {
if let Some(asset) = assets.get(handle) { if let Some(asset) = assets.get(&handle) {
extracted_assets.push((handle.clone_weak(), asset.clone())); extracted_assets.push((handle, asset.clone()));
} }
} }

View file

@ -28,6 +28,7 @@ use bevy_render::{
view::{ view::{
ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, VisibleEntities, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, VisibleEntities,
}, },
Extract,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd; use bevy_utils::FloatOrd;
@ -386,7 +387,10 @@ pub struct ExtractedClustersPointLights {
data: Vec<VisiblePointLights>, data: Vec<VisiblePointLights>,
} }
pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters), With<Camera>>) { pub fn extract_clusters(
mut commands: Commands,
views: Extract<Query<(Entity, &Clusters), With<Camera>>>,
) {
for (entity, clusters) in views.iter() { for (entity, clusters) in views.iter() {
commands.get_or_spawn(entity).insert_bundle(( commands.get_or_spawn(entity).insert_bundle((
ExtractedClustersPointLights { ExtractedClustersPointLights {
@ -404,20 +408,22 @@ pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters)
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn extract_lights( pub fn extract_lights(
mut commands: Commands, mut commands: Commands,
point_light_shadow_map: Res<PointLightShadowMap>, point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>, directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
global_point_lights: Res<GlobalVisiblePointLights>, global_point_lights: Extract<Res<GlobalVisiblePointLights>>,
mut point_lights: Query<(&PointLight, &mut CubemapVisibleEntities, &GlobalTransform)>, point_lights: Extract<Query<(&PointLight, &CubemapVisibleEntities, &GlobalTransform)>>,
mut spot_lights: Query<(&SpotLight, &mut VisibleEntities, &GlobalTransform)>, spot_lights: Extract<Query<(&SpotLight, &VisibleEntities, &GlobalTransform)>>,
mut directional_lights: Query< directional_lights: Extract<
( Query<
Entity, (
&DirectionalLight, Entity,
&mut VisibleEntities, &DirectionalLight,
&GlobalTransform, &VisibleEntities,
&Visibility, &GlobalTransform,
), &Visibility,
Without<SpotLight>, ),
Without<SpotLight>,
>,
>, >,
mut previous_point_lights_len: Local<usize>, mut previous_point_lights_len: Local<usize>,
mut previous_spot_lights_len: Local<usize>, mut previous_spot_lights_len: Local<usize>,
@ -441,10 +447,10 @@ pub fn extract_lights(
let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len); let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
for entity in global_point_lights.iter().copied() { for entity in global_point_lights.iter().copied() {
if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get_mut(entity) if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get(entity) {
{ // TODO: This is very much not ideal. We should be able to re-use the vector memory.
let render_cubemap_visible_entities = // However, since exclusive access to the main world in extract is ill-advised, we just clone here.
std::mem::take(cubemap_visible_entities.into_inner()); let render_cubemap_visible_entities = cubemap_visible_entities.clone();
point_lights_values.push(( point_lights_values.push((
entity, entity,
( (
@ -475,8 +481,10 @@ pub fn extract_lights(
let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len); let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
for entity in global_point_lights.iter().copied() { for entity in global_point_lights.iter().copied() {
if let Ok((spot_light, visible_entities, transform)) = spot_lights.get_mut(entity) { if let Ok((spot_light, visible_entities, transform)) = spot_lights.get(entity) {
let render_visible_entities = std::mem::take(visible_entities.into_inner()); // TODO: This is very much not ideal. We should be able to re-use the vector memory.
// However, since exclusive access to the main world in extract is ill-advised, we just clone here.
let render_visible_entities = visible_entities.clone();
let texel_size = let texel_size =
2.0 * spot_light.outer_angle.tan() / directional_light_shadow_map.size as f32; 2.0 * spot_light.outer_angle.tan() / directional_light_shadow_map.size as f32;
@ -512,7 +520,7 @@ pub fn extract_lights(
commands.insert_or_spawn_batch(spot_lights_values); commands.insert_or_spawn_batch(spot_lights_values);
for (entity, directional_light, visible_entities, transform, visibility) in for (entity, directional_light, visible_entities, transform, visibility) in
directional_lights.iter_mut() directional_lights.iter()
{ {
if !visibility.is_visible { if !visibility.is_visible {
continue; continue;
@ -530,7 +538,8 @@ pub fn extract_lights(
); );
let directional_light_texel_size = let directional_light_texel_size =
largest_dimension / directional_light_shadow_map.size as f32; largest_dimension / directional_light_shadow_map.size as f32;
let render_visible_entities = std::mem::take(visible_entities.into_inner()); // TODO: As above
let render_visible_entities = visible_entities.clone();
commands.get_or_spawn(entity).insert_bundle(( commands.get_or_spawn(entity).insert_bundle((
ExtractedDirectionalLight { ExtractedDirectionalLight {
color: directional_light.color, color: directional_light.color,

View file

@ -25,7 +25,7 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
}, },
view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms},
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use std::num::NonZeroU64; use std::num::NonZeroU64;
@ -118,14 +118,16 @@ pub fn extract_meshes(
mut commands: Commands, mut commands: Commands,
mut prev_caster_commands_len: Local<usize>, mut prev_caster_commands_len: Local<usize>,
mut prev_not_caster_commands_len: Local<usize>, mut prev_not_caster_commands_len: Local<usize>,
meshes_query: Query<( meshes_query: Extract<
Entity, Query<(
&ComputedVisibility, Entity,
&GlobalTransform, &ComputedVisibility,
&Handle<Mesh>, &GlobalTransform,
Option<With<NotShadowReceiver>>, &Handle<Mesh>,
Option<With<NotShadowCaster>>, Option<With<NotShadowReceiver>>,
)>, Option<With<NotShadowCaster>>,
)>,
>,
) { ) {
let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len); let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len);
let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len); let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len);
@ -202,12 +204,12 @@ impl SkinnedMeshJoints {
} }
pub fn extract_skinned_meshes( pub fn extract_skinned_meshes(
query: Query<(Entity, &ComputedVisibility, &SkinnedMesh)>,
inverse_bindposes: Res<Assets<SkinnedMeshInverseBindposes>>,
joint_query: Query<&GlobalTransform>,
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
mut previous_joint_len: Local<usize>, mut previous_joint_len: Local<usize>,
query: Extract<Query<(Entity, &ComputedVisibility, &SkinnedMesh)>>,
inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
joint_query: Extract<Query<&GlobalTransform>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
let mut joints = Vec::with_capacity(*previous_joint_len); let mut joints = Vec::with_capacity(*previous_joint_len);

View file

@ -4,6 +4,7 @@ use crate::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_resource::TextureView, render_resource::TextureView,
view::{ExtractedView, ExtractedWindows, VisibleEntities}, view::{ExtractedView, ExtractedWindows, VisibleEntities},
Extract,
}; };
use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
@ -393,13 +394,15 @@ pub struct ExtractedCamera {
pub fn extract_cameras( pub fn extract_cameras(
mut commands: Commands, mut commands: Commands,
query: Query<( query: Extract<
Entity, Query<(
&Camera, Entity,
&CameraRenderGraph, &Camera,
&GlobalTransform, &CameraRenderGraph,
&VisibleEntities, &GlobalTransform,
)>, &VisibleEntities,
)>,
>,
) { ) {
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() { for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
if !camera.is_active { if !camera.is_active {

View file

@ -2,15 +2,15 @@ use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility, view::ComputedVisibility,
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{Asset, Handle}; use bevy_asset::{Asset, Handle};
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
prelude::*, prelude::*,
query::{QueryItem, WorldQuery}, query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
system::{lifetimeless::Read, StaticSystemParam}, system::lifetimeless::Read,
}; };
use std::{marker::PhantomData, ops::Deref}; use std::{marker::PhantomData, ops::Deref};
@ -34,9 +34,9 @@ impl<C: Component> DynamicUniformIndex<C> {
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step. /// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
pub trait ExtractComponent: Component { pub trait ExtractComponent: Component {
/// ECS [`WorldQuery`] to fetch the components to extract. /// ECS [`WorldQuery`] to fetch the components to extract.
type Query: WorldQuery; type Query: WorldQuery + ReadOnlyWorldQuery;
/// Filters the entities with additional constraints. /// Filters the entities with additional constraints.
type Filter: WorldQuery; type Filter: WorldQuery + ReadOnlyWorldQuery;
/// Defines how the component is transferred into the "render world". /// Defines how the component is transferred into the "render world".
fn extract_component(item: QueryItem<Self::Query>) -> Self; fn extract_component(item: QueryItem<Self::Query>) -> Self;
} }
@ -182,7 +182,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
fn extract_components<C: ExtractComponent>( fn extract_components<C: ExtractComponent>(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
mut query: StaticSystemParam<Query<(Entity, C::Query), C::Filter>>, mut query: Extract<Query<(Entity, C::Query), C::Filter>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, query_item) in query.iter_mut() { for (entity, query_item) in query.iter_mut() {
@ -196,7 +196,7 @@ fn extract_components<C: ExtractComponent>(
fn extract_visible_components<C: ExtractComponent>( fn extract_visible_components<C: ExtractComponent>(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
mut query: StaticSystemParam<Query<(Entity, Read<ComputedVisibility>, C::Query), C::Filter>>, mut query: Extract<Query<(Entity, &ComputedVisibility, C::Query), C::Filter>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, query_item) in query.iter_mut() { for (entity, computed_visibility, query_item) in query.iter_mut() {

View file

@ -0,0 +1,120 @@
use crate::MainWorld;
use bevy_ecs::{
prelude::*,
system::{
ReadOnlySystemParamFetch, ResState, SystemMeta, SystemParam, SystemParamFetch,
SystemParamState, SystemState,
},
};
use std::ops::{Deref, DerefMut};
/// A helper for accessing [`MainWorld`] content using a system parameter.
///
/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`]
/// contained in [`MainWorld`]. This parameter only works for systems run
/// during [`RenderStage::Extract`].
///
/// This requires that the contained [`SystemParam`] does not mutate the world, as it
/// uses a read-only reference to [`MainWorld`] internally.
///
/// ## Context
///
/// [`RenderStage::Extract`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
/// render world. The render world drives rendering each frame (generally to a [Window]).
/// This design is used to allow performing calculations related to rendering a prior frame at the same
/// time as the next frame is simulated, which increases throughput (FPS).
///
/// [`Extract`] is used to get data from the main world during [`RenderStage::Extract`].
///
/// ## Examples
///
/// ```rust
/// use bevy_ecs::prelude::*;
/// use bevy_render::Extract;
/// # #[derive(Component)]
/// # struct Cloud;
/// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<Entity, With<Cloud>>>) {
/// for cloud in clouds.iter() {
/// commands.get_or_spawn(cloud).insert(Cloud);
/// }
/// }
/// ```
///
/// [`RenderStage::Extract`]: crate::RenderStage::Extract
/// [Window]: bevy_window::Window
pub struct Extract<'w, 's, P: SystemParam + 'static>
where
P::Fetch: ReadOnlySystemParamFetch,
{
item: <P::Fetch as SystemParamFetch<'w, 's>>::Item,
}
impl<'w, 's, P: SystemParam> SystemParam for Extract<'w, 's, P>
where
P::Fetch: ReadOnlySystemParamFetch,
{
type Fetch = ExtractState<P>;
}
#[doc(hidden)]
pub struct ExtractState<P: SystemParam> {
state: SystemState<P>,
main_world_state: ResState<MainWorld>,
}
// SAFETY: only accesses MainWorld resource with read only system params using ResState,
// which is initialized in init()
unsafe impl<P: SystemParam + 'static> SystemParamState for ExtractState<P> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
let mut main_world = world.resource_mut::<MainWorld>();
Self {
state: SystemState::new(&mut main_world),
main_world_state: ResState::init(world, system_meta),
}
}
}
impl<'w, 's, P: SystemParam + 'static> SystemParamFetch<'w, 's> for ExtractState<P>
where
P::Fetch: ReadOnlySystemParamFetch,
{
type Item = Extract<'w, 's, P>;
unsafe fn get_param(
state: &'s mut Self,
system_meta: &SystemMeta,
world: &'w World,
change_tick: u32,
) -> Self::Item {
let main_world = ResState::<MainWorld>::get_param(
&mut state.main_world_state,
system_meta,
world,
change_tick,
);
let item = state.state.get(main_world.into_inner());
Extract { item }
}
}
impl<'w, 's, P: SystemParam> Deref for Extract<'w, 's, P>
where
P::Fetch: ReadOnlySystemParamFetch,
{
type Target = <P::Fetch as SystemParamFetch<'w, 's>>::Item;
#[inline]
fn deref(&self) -> &Self::Target {
&self.item
}
}
impl<'w, 's, P: SystemParam> DerefMut for Extract<'w, 's, P>
where
P::Fetch: ReadOnlySystemParamFetch,
{
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.item
}
}

View file

@ -4,7 +4,7 @@ use bevy_app::{App, Plugin};
use bevy_ecs::system::{Commands, Res, Resource}; use bevy_ecs::system::{Commands, Res, Resource};
pub use bevy_render_macros::ExtractResource; pub use bevy_render_macros::ExtractResource;
use crate::{RenderApp, RenderStage}; use crate::{Extract, RenderApp, RenderStage};
/// Describes how a resource gets extracted for rendering. /// Describes how a resource gets extracted for rendering.
/// ///
@ -39,8 +39,11 @@ impl<R: ExtractResource> Plugin for ExtractResourcePlugin<R> {
/// This system extracts the resource of the corresponding [`Resource`] type /// This system extracts the resource of the corresponding [`Resource`] type
/// by cloning it. /// by cloning it.
pub fn extract_resource<R: ExtractResource>(mut commands: Commands, resource: Res<R::Source>) { pub fn extract_resource<R: ExtractResource>(
mut commands: Commands,
resource: Extract<Res<R::Source>>,
) {
if resource.is_changed() { if resource.is_changed() {
commands.insert_resource(R::extract_resource(resource.into_inner())); commands.insert_resource(R::extract_resource(&*resource));
} }
} }

View file

@ -3,6 +3,7 @@ extern crate core;
pub mod camera; pub mod camera;
pub mod color; pub mod color;
pub mod extract_component; pub mod extract_component;
mod extract_param;
pub mod extract_resource; pub mod extract_resource;
pub mod mesh; pub mod mesh;
pub mod primitives; pub mod primitives;
@ -16,6 +17,8 @@ pub mod settings;
pub mod texture; pub mod texture;
pub mod view; pub mod view;
pub use extract_param::Extract;
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
@ -45,7 +48,10 @@ use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::{AddAsset, AssetServer}; use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_utils::tracing::debug; use bevy_utils::tracing::debug;
use std::ops::{Deref, DerefMut}; use std::{
any::TypeId,
ops::{Deref, DerefMut},
};
/// Contains the default Bevy rendering backend based on wgpu. /// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)] #[derive(Default)]
@ -79,11 +85,14 @@ pub enum RenderStage {
Cleanup, Cleanup,
} }
/// The Render App World. This is only available as a resource during the Extract step. /// The simulation [`World`] of the application, stored as a resource.
/// This resource is only available during [`RenderStage::Extract`] and not
/// during command application of that stage.
/// See [`Extract`] for more details.
#[derive(Default)] #[derive(Default)]
pub struct RenderWorld(World); pub struct MainWorld(World);
impl Deref for RenderWorld { impl Deref for MainWorld {
type Target = World; type Target = World;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -91,7 +100,7 @@ impl Deref for RenderWorld {
} }
} }
impl DerefMut for RenderWorld { impl DerefMut for MainWorld {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
@ -107,11 +116,6 @@ pub mod main_graph {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp; pub struct RenderApp;
/// A "scratch" world used to avoid allocating new worlds every frame when
/// swapping out the [`RenderWorld`].
#[derive(Default)]
struct ScratchRenderWorld(World);
impl Plugin for RenderPlugin { impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@ -150,7 +154,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(adapter_info.clone()) .insert_resource(adapter_info.clone())
.init_resource::<ScratchRenderWorld>() .init_resource::<ScratchMainWorld>()
.register_type::<Frustum>() .register_type::<Frustum>()
.register_type::<CubemapFrusta>(); .register_type::<CubemapFrusta>();
@ -160,8 +164,20 @@ impl Plugin for RenderPlugin {
let mut render_app = App::empty(); let mut render_app = App::empty();
let mut extract_stage = let mut extract_stage =
SystemStage::parallel().with_system(PipelineCache::extract_shaders); SystemStage::parallel().with_system(PipelineCache::extract_shaders);
// Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine
render_app.init_resource::<MainWorld>();
render_app.world.remove_resource::<MainWorld>();
let main_world_in_render = render_app
.world
.components()
.get_resource_id(TypeId::of::<MainWorld>());
// `Extract` systems must read from the main world. We want to emit an error when that doesn't occur
// Safe to unwrap: Ensured it existed just above
extract_stage.set_must_read_resource(main_world_in_render.unwrap());
// don't apply buffers when the stage finishes running // don't apply buffers when the stage finishes running
// extract stage runs on the app world, but the buffers are applied to the render world // extract stage runs on the render world, but buffers are applied
// after access to the main world is removed
// See also https://github.com/bevyengine/bevy/issues/5082
extract_stage.set_apply_buffers(false); extract_stage.set_apply_buffers(false);
render_app render_app
.add_stage(RenderStage::Extract, extract_stage) .add_stage(RenderStage::Extract, extract_stage)
@ -300,6 +316,11 @@ impl Plugin for RenderPlugin {
} }
} }
/// A "scratch" world used to avoid allocating new worlds every frame when
/// swapping out the [`MainWorld`] for [`RenderStage::Extract`].
#[derive(Default)]
struct ScratchMainWorld(World);
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. /// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
/// This updates the render world with the extracted ECS data of the current frame. /// This updates the render world with the extracted ECS data of the current frame.
fn extract(app_world: &mut World, render_app: &mut App) { fn extract(app_world: &mut World, render_app: &mut App) {
@ -308,17 +329,20 @@ fn extract(app_world: &mut World, render_app: &mut App) {
.get_stage_mut::<SystemStage>(&RenderStage::Extract) .get_stage_mut::<SystemStage>(&RenderStage::Extract)
.unwrap(); .unwrap();
// temporarily add the render world to the app world as a resource // temporarily add the app world to the render world as a resource
let scratch_world = app_world.remove_resource::<ScratchRenderWorld>().unwrap(); let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
let render_world = std::mem::replace(&mut render_app.world, scratch_world.0); let inserted_world = std::mem::replace(app_world, scratch_world.0);
app_world.insert_resource(RenderWorld(render_world)); let running_world = &mut render_app.world;
running_world.insert_resource(MainWorld(inserted_world));
extract.run(app_world); extract.run(running_world);
// move the app world back, as if nothing happened.
let inserted_world = running_world.remove_resource::<MainWorld>().unwrap();
let scratch_world = std::mem::replace(app_world, inserted_world.0);
app_world.insert_resource(ScratchMainWorld(scratch_world));
// add the render world back to the render app // Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
let render_world = app_world.remove_resource::<RenderWorld>().unwrap(); // so that in future, pipelining will be able to do this too without any code relying on it.
let scratch_world = std::mem::replace(&mut render_app.world, render_world.0); // see <https://github.com/bevyengine/bevy/issues/5082>
app_world.insert_resource(ScratchRenderWorld(scratch_world)); extract.apply_buffers(running_world);
extract.apply_buffers(&mut render_app.world);
} }

View file

@ -1,4 +1,4 @@
use crate::{RenderApp, RenderStage}; use crate::{Extract, RenderApp, RenderStage};
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetEvent, Assets, Handle}; use bevy_asset::{Asset, AssetEvent, Assets, Handle};
use bevy_ecs::{ use bevy_ecs::{
@ -123,15 +123,15 @@ pub type RenderAssets<A> = HashMap<Handle<A>, <A as RenderAsset>::PreparedAsset>
/// into the "render world". /// into the "render world".
fn extract_render_asset<A: RenderAsset>( fn extract_render_asset<A: RenderAsset>(
mut commands: Commands, mut commands: Commands,
mut events: EventReader<AssetEvent<A>>, mut events: Extract<EventReader<AssetEvent<A>>>,
assets: Res<Assets<A>>, assets: Extract<Res<Assets<A>>>,
) { ) {
let mut changed_assets = HashSet::default(); let mut changed_assets = HashSet::default();
let mut removed = Vec::new(); let mut removed = Vec::new();
for event in events.iter() { for event in events.iter() {
match event { match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
changed_assets.insert(handle); changed_assets.insert(handle.clone_weak());
} }
AssetEvent::Removed { handle } => { AssetEvent::Removed { handle } => {
changed_assets.remove(handle); changed_assets.remove(handle);
@ -142,8 +142,8 @@ fn extract_render_asset<A: RenderAsset>(
let mut extracted_assets = Vec::new(); let mut extracted_assets = Vec::new();
for handle in changed_assets.drain() { for handle in changed_assets.drain() {
if let Some(asset) = assets.get(handle) { if let Some(asset) = assets.get(&handle) {
extracted_assets.push((handle.clone_weak(), asset.extract_asset())); extracted_assets.push((handle, asset.extract_asset()));
} }
} }

View file

@ -7,7 +7,7 @@ use crate::{
ShaderProcessor, ShaderReflectError, ShaderProcessor, ShaderReflectError,
}, },
renderer::RenderDevice, renderer::RenderDevice,
RenderWorld, Extract,
}; };
use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::event::EventReader; use bevy_ecs::event::EventReader;
@ -546,11 +546,10 @@ impl PipelineCache {
} }
pub(crate) fn extract_shaders( pub(crate) fn extract_shaders(
mut world: ResMut<RenderWorld>, mut cache: ResMut<Self>,
shaders: Res<Assets<Shader>>, shaders: Extract<Res<Assets<Shader>>>,
mut events: EventReader<AssetEvent<Shader>>, mut events: Extract<EventReader<AssetEvent<Shader>>>,
) { ) {
let mut cache = world.resource_mut::<Self>();
for event in events.iter() { for event in events.iter() {
match event { match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {

View file

@ -2,7 +2,7 @@ use crate::{
render_resource::TextureView, render_resource::TextureView,
renderer::{RenderDevice, RenderInstance}, renderer::{RenderDevice, RenderInstance},
texture::BevyDefault, texture::BevyDefault,
RenderApp, RenderStage, RenderWorld, Extract, RenderApp, RenderStage,
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
@ -68,11 +68,10 @@ impl DerefMut for ExtractedWindows {
} }
fn extract_windows( fn extract_windows(
mut render_world: ResMut<RenderWorld>, mut extracted_windows: ResMut<ExtractedWindows>,
mut closed: EventReader<WindowClosed>, mut closed: Extract<EventReader<WindowClosed>>,
windows: Res<Windows>, windows: Extract<Res<Windows>>,
) { ) {
let mut extracted_windows = render_world.get_resource_mut::<ExtractedWindows>().unwrap();
for window in windows.iter() { for window in windows.iter() {
let (new_width, new_height) = ( let (new_width, new_height) = (
window.physical_width().max(1), window.physical_width().max(1),

View file

@ -17,7 +17,7 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
}, },
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -116,7 +116,7 @@ bitflags::bitflags! {
pub fn extract_mesh2d( pub fn extract_mesh2d(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
query: Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>, query: Extract<Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility, transform, handle) in query.iter() { for (entity, computed_visibility, transform, handle) in query.iter() {

View file

@ -23,7 +23,7 @@ use bevy_render::{
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, Image}, texture::{BevyDefault, Image},
view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility}, view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility},
RenderWorld, Extract,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd; use bevy_utils::FloatOrd;
@ -197,10 +197,9 @@ pub struct SpriteAssetEvents {
} }
pub fn extract_sprite_events( pub fn extract_sprite_events(
mut render_world: ResMut<RenderWorld>, mut events: ResMut<SpriteAssetEvents>,
mut image_events: EventReader<AssetEvent<Image>>, mut image_events: Extract<EventReader<AssetEvent<Image>>>,
) { ) {
let mut events = render_world.resource_mut::<SpriteAssetEvents>();
let SpriteAssetEvents { ref mut images } = *events; let SpriteAssetEvents { ref mut images } = *events;
images.clear(); images.clear();
@ -221,17 +220,18 @@ pub fn extract_sprite_events(
} }
pub fn extract_sprites( pub fn extract_sprites(
mut render_world: ResMut<RenderWorld>, mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Res<Assets<TextureAtlas>>, texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
sprite_query: Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>, sprite_query: Extract<Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>>,
atlas_query: Query<( atlas_query: Extract<
&Visibility, Query<(
&TextureAtlasSprite, &Visibility,
&GlobalTransform, &TextureAtlasSprite,
&Handle<TextureAtlas>, &GlobalTransform,
)>, &Handle<TextureAtlas>,
)>,
>,
) { ) {
let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
extracted_sprites.sprites.clear(); extracted_sprites.sprites.clear();
for (visibility, sprite, transform, handle) in sprite_query.iter() { for (visibility, sprite, transform, handle) in sprite_query.iter() {
if !visibility.is_visible { if !visibility.is_visible {

View file

@ -10,7 +10,7 @@ use bevy_ecs::{
}; };
use bevy_math::{Vec2, Vec3}; use bevy_math::{Vec2, Vec3};
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::{texture::Image, view::Visibility, RenderWorld}; use bevy_render::{texture::Image, view::Visibility, Extract};
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas}; use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_utils::HashSet; use bevy_utils::HashSet;
@ -61,16 +61,13 @@ pub struct Text2dBundle {
} }
pub fn extract_text2d_sprite( pub fn extract_text2d_sprite(
mut render_world: ResMut<RenderWorld>, mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Res<Assets<TextureAtlas>>, texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
text_pipeline: Res<DefaultTextPipeline>, text_pipeline: Extract<Res<DefaultTextPipeline>>,
windows: Res<Windows>, windows: Extract<Res<Windows>>,
text2d_query: Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>, text2d_query: Extract<Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>>,
) { ) {
let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
let scale_factor = windows.scale_factor(WindowId::primary()) as f32; let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
for (entity, visibility, text, transform, calculated_size) in text2d_query.iter() { for (entity, visibility, text, transform, calculated_size) in text2d_query.iter() {
if !visibility.is_visible { if !visibility.is_visible {
continue; continue;

View file

@ -21,7 +21,7 @@ use bevy_render::{
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
texture::Image, texture::Image,
view::{ExtractedView, ViewUniforms, Visibility}, view::{ExtractedView, ViewUniforms, Visibility},
RenderApp, RenderStage, RenderWorld, Extract, RenderApp, RenderStage,
}; };
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas}; use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
use bevy_text::{DefaultTextPipeline, Text}; use bevy_text::{DefaultTextPipeline, Text};
@ -174,18 +174,19 @@ pub struct ExtractedUiNodes {
} }
pub fn extract_uinodes( pub fn extract_uinodes(
mut render_world: ResMut<RenderWorld>, mut extracted_uinodes: ResMut<ExtractedUiNodes>,
images: Res<Assets<Image>>, images: Extract<Res<Assets<Image>>>,
uinode_query: Query<( uinode_query: Extract<
&Node, Query<(
&GlobalTransform, &Node,
&UiColor, &GlobalTransform,
&UiImage, &UiColor,
&Visibility, &UiImage,
Option<&CalculatedClip>, &Visibility,
)>, Option<&CalculatedClip>,
)>,
>,
) { ) {
let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
extracted_uinodes.uinodes.clear(); extracted_uinodes.uinodes.clear();
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
if !visibility.is_visible { if !visibility.is_visible {
@ -226,8 +227,7 @@ pub struct DefaultCameraView(pub Entity);
pub fn extract_default_ui_camera_view<T: Component>( pub fn extract_default_ui_camera_view<T: Component>(
mut commands: Commands, mut commands: Commands,
render_world: Res<RenderWorld>, query: Extract<Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>>,
query: Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>,
) { ) {
for (entity, camera, camera_ui) in query.iter() { for (entity, camera, camera_ui) in query.iter() {
// ignore cameras with disabled ui // ignore cameras with disabled ui
@ -245,10 +245,8 @@ pub fn extract_default_ui_camera_view<T: Component>(
..Default::default() ..Default::default()
}; };
projection.update(logical_size.x, logical_size.y); projection.update(logical_size.x, logical_size.y);
// This roundabout approach is required because spawn().id() won't work in this context let default_camera_view = commands
let default_camera_view = render_world.entities().reserve_entity(); .spawn()
commands
.get_or_spawn(default_camera_view)
.insert(ExtractedView { .insert(ExtractedView {
projection: projection.get_projection_matrix(), projection: projection.get_projection_matrix(),
transform: GlobalTransform::from_xyz( transform: GlobalTransform::from_xyz(
@ -258,7 +256,8 @@ pub fn extract_default_ui_camera_view<T: Component>(
), ),
width: physical_size.x, width: physical_size.x,
height: physical_size.y, height: physical_size.y,
}); })
.id();
commands.get_or_spawn(entity).insert_bundle(( commands.get_or_spawn(entity).insert_bundle((
DefaultCameraView(default_camera_view), DefaultCameraView(default_camera_view),
RenderPhase::<TransparentUi>::default(), RenderPhase::<TransparentUi>::default(),
@ -268,23 +267,22 @@ pub fn extract_default_ui_camera_view<T: Component>(
} }
pub fn extract_text_uinodes( pub fn extract_text_uinodes(
mut render_world: ResMut<RenderWorld>, mut extracted_uinodes: ResMut<ExtractedUiNodes>,
texture_atlases: Res<Assets<TextureAtlas>>, texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
text_pipeline: Res<DefaultTextPipeline>, text_pipeline: Extract<Res<DefaultTextPipeline>>,
windows: Res<Windows>, windows: Extract<Res<Windows>>,
uinode_query: Query<( uinode_query: Extract<
Entity, Query<(
&Node, Entity,
&GlobalTransform, &Node,
&Text, &GlobalTransform,
&Visibility, &Text,
Option<&CalculatedClip>, &Visibility,
)>, Option<&CalculatedClip>,
)>,
>,
) { ) {
let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
let scale_factor = windows.scale_factor(WindowId::primary()) as f32; let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() { for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() {
if !visibility.is_visible { if !visibility.is_visible {
continue; continue;

View file

@ -19,7 +19,7 @@ use bevy::{
}, },
texture::BevyDefault, texture::BevyDefault,
view::VisibleEntities, view::VisibleEntities,
RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}, },
sprite::{ sprite::{
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform,
@ -286,7 +286,9 @@ impl Plugin for ColoredMesh2dPlugin {
pub fn extract_colored_mesh2d( pub fn extract_colored_mesh2d(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
query: Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>, // When extracting, you must use `Extract` to mark the `SystemParam`s
// which should be taken from the main world.
query: Extract<Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, computed_visibility) in query.iter() { for (entity, computed_visibility) in query.iter() {

View file

@ -4,13 +4,18 @@
use bevy::{ use bevy::{
core_pipeline::core_3d::Transparent3d, core_pipeline::core_3d::Transparent3d,
ecs::system::{lifetimeless::SRes, SystemParamItem}, ecs::system::{
lifetimeless::{Read, SRes},
SystemParamItem,
},
pbr::{ pbr::{
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
SetMeshViewBindGroup, SetMeshViewBindGroup,
}, },
prelude::*, prelude::*,
render::{ render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
extract_resource::{ExtractResource, ExtractResourcePlugin},
mesh::MeshVertexBufferLayout, mesh::MeshVertexBufferLayout,
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{ render_phase::{
@ -64,6 +69,8 @@ impl Plugin for CustomMaterialPlugin {
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
app.add_plugin(ExtractComponentPlugin::<CustomMaterial>::default())
.add_plugin(ExtractResourcePlugin::<ExtractedTime>::default());
app.sub_app_mut(RenderApp) app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>() .add_render_command::<Transparent3d, DrawCustom>()
@ -73,26 +80,20 @@ impl Plugin for CustomMaterialPlugin {
}) })
.init_resource::<CustomPipeline>() .init_resource::<CustomPipeline>()
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>() .init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_time)
.add_system_to_stage(RenderStage::Extract, extract_custom_material)
.add_system_to_stage(RenderStage::Prepare, prepare_time) .add_system_to_stage(RenderStage::Prepare, prepare_time)
.add_system_to_stage(RenderStage::Queue, queue_custom) .add_system_to_stage(RenderStage::Queue, queue_custom)
.add_system_to_stage(RenderStage::Queue, queue_time_bind_group); .add_system_to_stage(RenderStage::Queue, queue_time_bind_group);
} }
} }
// extract the `CustomMaterial` component into the render world impl ExtractComponent for CustomMaterial {
fn extract_custom_material( type Query = Read<CustomMaterial>;
mut commands: Commands,
mut previous_len: Local<usize>, type Filter = ();
mut query: Query<Entity, With<CustomMaterial>>,
) { fn extract_component(_: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
let mut values = Vec::with_capacity(*previous_len); CustomMaterial
for entity in query.iter_mut() {
values.push((entity, (CustomMaterial,)));
} }
*previous_len = values.len();
commands.insert_or_spawn_batch(values);
} }
// add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline` // add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline`
@ -138,11 +139,14 @@ struct ExtractedTime {
seconds_since_startup: f32, seconds_since_startup: f32,
} }
// extract the passed time into a resource in the render world impl ExtractResource for ExtractedTime {
fn extract_time(mut commands: Commands, time: Res<Time>) { type Source = Time;
commands.insert_resource(ExtractedTime {
seconds_since_startup: time.seconds_since_startup() as f32, fn extract_resource(time: &Self::Source) -> Self {
}); ExtractedTime {
seconds_since_startup: time.seconds_since_startup() as f32,
}
}
} }
struct TimeMeta { struct TimeMeta {

View file

@ -6,7 +6,7 @@ use bevy::{
math::{DVec2, DVec3}, math::{DVec2, DVec3},
pbr::{ExtractedPointLight, GlobalLightMeta}, pbr::{ExtractedPointLight, GlobalLightMeta},
prelude::*, prelude::*,
render::{camera::ScalingMode, RenderApp, RenderStage}, render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
window::PresentMode, window::PresentMode,
}; };
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -156,7 +156,7 @@ impl Plugin for LogVisibleLights {
// System for printing the number of meshes on every tick of the timer // System for printing the number of meshes on every tick of the timer
fn print_visible_light_count( fn print_visible_light_count(
time: Res<Time>, time: Res<ExtractedTime>,
mut timer: Local<PrintingTimer>, mut timer: Local<PrintingTimer>,
visible: Query<&ExtractedPointLight>, visible: Query<&ExtractedPointLight>,
global_light_meta: Res<GlobalLightMeta>, global_light_meta: Res<GlobalLightMeta>,
@ -172,8 +172,11 @@ fn print_visible_light_count(
} }
} }
fn extract_time(mut commands: Commands, time: Res<Time>) { #[derive(Deref, DerefMut)]
commands.insert_resource(time.into_inner().clone()); pub struct ExtractedTime(Time);
fn extract_time(mut commands: Commands, time: Extract<Res<Time>>) {
commands.insert_resource(ExtractedTime(time.clone()));
} }
struct PrintingTimer(Timer); struct PrintingTimer(Timer);