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,
},
render_resource::CachedRenderPipelineId,
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
};
use bevy_utils::FloatOrd;
use std::ops::Range;
@ -123,7 +123,7 @@ impl BatchedPhaseItem for Transparent2d {
pub fn extract_core_2d_camera_phases(
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() {
if camera.is_active {

View file

@ -34,7 +34,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::ViewDepthTexture,
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
};
use bevy_utils::{FloatOrd, HashMap};
@ -208,7 +208,7 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
pub fn extract_core_3d_camera_phases(
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() {
if camera.is_active {

View file

@ -12,7 +12,10 @@ use crate::{
},
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 fixedbitset::FixedBitSet;
use std::fmt::Debug;
@ -88,6 +91,7 @@ pub struct SystemStage {
last_tick_check: u32,
/// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied.
apply_buffers: bool,
must_read_resource: Option<ComponentId>,
}
impl SystemStage {
@ -110,6 +114,7 @@ impl SystemStage {
uninitialized_at_end: vec![],
last_tick_check: Default::default(),
apply_buffers: true,
must_read_resource: None,
}
}
@ -139,6 +144,10 @@ impl SystemStage {
self.executor = executor;
}
pub fn set_must_read_resource(&mut self, resource_id: ComponentId) {
self.must_read_resource = Some(resource_id);
}
#[must_use]
pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self {
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
/// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD)
/// times since the previous `check_tick` scan.
@ -782,6 +805,9 @@ impl Stage for SystemStage {
if world.contains_resource::<ReportExecutionOrderAmbiguities>() {
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 {
self.executor.rebuild_cached_data(&self.parallel);
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`.
///
/// This returns an [`EntityCommands`] builder, which enables inserting more components and

View file

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

View file

@ -28,6 +28,7 @@ use bevy_render::{
view::{
ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, VisibleEntities,
},
Extract,
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
@ -386,7 +387,10 @@ pub struct ExtractedClustersPointLights {
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() {
commands.get_or_spawn(entity).insert_bundle((
ExtractedClustersPointLights {
@ -404,20 +408,22 @@ pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters)
#[allow(clippy::too_many_arguments)]
pub fn extract_lights(
mut commands: Commands,
point_light_shadow_map: Res<PointLightShadowMap>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
global_point_lights: Res<GlobalVisiblePointLights>,
mut point_lights: Query<(&PointLight, &mut CubemapVisibleEntities, &GlobalTransform)>,
mut spot_lights: Query<(&SpotLight, &mut VisibleEntities, &GlobalTransform)>,
mut directional_lights: Query<
(
Entity,
&DirectionalLight,
&mut VisibleEntities,
&GlobalTransform,
&Visibility,
),
Without<SpotLight>,
point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
global_point_lights: Extract<Res<GlobalVisiblePointLights>>,
point_lights: Extract<Query<(&PointLight, &CubemapVisibleEntities, &GlobalTransform)>>,
spot_lights: Extract<Query<(&SpotLight, &VisibleEntities, &GlobalTransform)>>,
directional_lights: Extract<
Query<
(
Entity,
&DirectionalLight,
&VisibleEntities,
&GlobalTransform,
&Visibility,
),
Without<SpotLight>,
>,
>,
mut previous_point_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);
for entity in global_point_lights.iter().copied() {
if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get_mut(entity)
{
let render_cubemap_visible_entities =
std::mem::take(cubemap_visible_entities.into_inner());
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.
// However, since exclusive access to the main world in extract is ill-advised, we just clone here.
let render_cubemap_visible_entities = cubemap_visible_entities.clone();
point_lights_values.push((
entity,
(
@ -475,8 +481,10 @@ pub fn extract_lights(
let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
for entity in global_point_lights.iter().copied() {
if let Ok((spot_light, visible_entities, transform)) = spot_lights.get_mut(entity) {
let render_visible_entities = std::mem::take(visible_entities.into_inner());
if let Ok((spot_light, visible_entities, transform)) = spot_lights.get(entity) {
// 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 =
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);
for (entity, directional_light, visible_entities, transform, visibility) in
directional_lights.iter_mut()
directional_lights.iter()
{
if !visibility.is_visible {
continue;
@ -530,7 +538,8 @@ pub fn extract_lights(
);
let directional_light_texel_size =
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((
ExtractedDirectionalLight {
color: directional_light.color,

View file

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

View file

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

View file

@ -2,15 +2,15 @@ use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, Handle};
use bevy_ecs::{
component::Component,
prelude::*,
query::{QueryItem, WorldQuery},
system::{lifetimeless::Read, StaticSystemParam},
query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
system::lifetimeless::Read,
};
use std::{marker::PhantomData, ops::Deref};
@ -34,9 +34,9 @@ impl<C: Component> DynamicUniformIndex<C> {
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
pub trait ExtractComponent: Component {
/// ECS [`WorldQuery`] to fetch the components to extract.
type Query: WorldQuery;
type Query: WorldQuery + ReadOnlyWorldQuery;
/// Filters the entities with additional constraints.
type Filter: WorldQuery;
type Filter: WorldQuery + ReadOnlyWorldQuery;
/// Defines how the component is transferred into the "render world".
fn extract_component(item: QueryItem<Self::Query>) -> Self;
}
@ -182,7 +182,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
fn extract_components<C: ExtractComponent>(
mut commands: Commands,
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);
for (entity, query_item) in query.iter_mut() {
@ -196,7 +196,7 @@ fn extract_components<C: ExtractComponent>(
fn extract_visible_components<C: ExtractComponent>(
mut commands: Commands,
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);
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};
pub use bevy_render_macros::ExtractResource;
use crate::{RenderApp, RenderStage};
use crate::{Extract, RenderApp, RenderStage};
/// 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
/// 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() {
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 color;
pub mod extract_component;
mod extract_param;
pub mod extract_resource;
pub mod mesh;
pub mod primitives;
@ -16,6 +17,8 @@ pub mod settings;
pub mod texture;
pub mod view;
pub use extract_param::Extract;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
@ -45,7 +48,10 @@ use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::prelude::*;
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.
#[derive(Default)]
@ -79,11 +85,14 @@ pub enum RenderStage {
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)]
pub struct RenderWorld(World);
pub struct MainWorld(World);
impl Deref for RenderWorld {
impl Deref for MainWorld {
type Target = World;
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 {
&mut self.0
}
@ -107,11 +116,6 @@ pub mod main_graph {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
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 {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) {
@ -150,7 +154,7 @@ impl Plugin for RenderPlugin {
app.insert_resource(device.clone())
.insert_resource(queue.clone())
.insert_resource(adapter_info.clone())
.init_resource::<ScratchRenderWorld>()
.init_resource::<ScratchMainWorld>()
.register_type::<Frustum>()
.register_type::<CubemapFrusta>();
@ -160,8 +164,20 @@ impl Plugin for RenderPlugin {
let mut render_app = App::empty();
let mut extract_stage =
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
// 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);
render_app
.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.
/// This updates the render world with the extracted ECS data of the current frame.
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)
.unwrap();
// temporarily add the render world to the app world as a resource
let scratch_world = app_world.remove_resource::<ScratchRenderWorld>().unwrap();
let render_world = std::mem::replace(&mut render_app.world, scratch_world.0);
app_world.insert_resource(RenderWorld(render_world));
// temporarily add the app world to the render world as a resource
let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = std::mem::replace(app_world, scratch_world.0);
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
let render_world = app_world.remove_resource::<RenderWorld>().unwrap();
let scratch_world = std::mem::replace(&mut render_app.world, render_world.0);
app_world.insert_resource(ScratchRenderWorld(scratch_world));
extract.apply_buffers(&mut render_app.world);
// Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
// so that in future, pipelining will be able to do this too without any code relying on it.
// see <https://github.com/bevyengine/bevy/issues/5082>
extract.apply_buffers(running_world);
}

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ use bevy_render::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
};
use bevy_transform::components::GlobalTransform;
@ -116,7 +116,7 @@ bitflags::bitflags! {
pub fn extract_mesh2d(
mut commands: Commands,
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);
for (entity, computed_visibility, transform, handle) in query.iter() {

View file

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

View file

@ -10,7 +10,7 @@ use bevy_ecs::{
};
use bevy_math::{Vec2, Vec3};
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_transform::prelude::{GlobalTransform, Transform};
use bevy_utils::HashSet;
@ -61,16 +61,13 @@ pub struct Text2dBundle {
}
pub fn extract_text2d_sprite(
mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
text_pipeline: Res<DefaultTextPipeline>,
windows: Res<Windows>,
text2d_query: Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>,
mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
text_pipeline: Extract<Res<DefaultTextPipeline>>,
windows: Extract<Res<Windows>>,
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;
for (entity, visibility, text, transform, calculated_size) in text2d_query.iter() {
if !visibility.is_visible {
continue;

View file

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

View file

@ -19,7 +19,7 @@ use bevy::{
},
texture::BevyDefault,
view::VisibleEntities,
RenderApp, RenderStage,
Extract, RenderApp, RenderStage,
},
sprite::{
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform,
@ -286,7 +286,9 @@ impl Plugin for ColoredMesh2dPlugin {
pub fn extract_colored_mesh2d(
mut commands: Commands,
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);
for (entity, computed_visibility) in query.iter() {

View file

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

View file

@ -6,7 +6,7 @@ use bevy::{
math::{DVec2, DVec3},
pbr::{ExtractedPointLight, GlobalLightMeta},
prelude::*,
render::{camera::ScalingMode, RenderApp, RenderStage},
render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
window::PresentMode,
};
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
fn print_visible_light_count(
time: Res<Time>,
time: Res<ExtractedTime>,
mut timer: Local<PrintingTimer>,
visible: Query<&ExtractedPointLight>,
global_light_meta: Res<GlobalLightMeta>,
@ -172,8 +172,11 @@ fn print_visible_light_count(
}
}
fn extract_time(mut commands: Commands, time: Res<Time>) {
commands.insert_resource(time.into_inner().clone());
#[derive(Deref, DerefMut)]
pub struct ExtractedTime(Time);
fn extract_time(mut commands: Commands, time: Extract<Res<Time>>) {
commands.insert_resource(ExtractedTime(time.clone()));
}
struct PrintingTimer(Timer);