mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Synchronize removed components with the render world (#15582)
# Objective Fixes #15560 Fixes (most of) #15570 Currently a lot of examples (and presumably some user code) depend on toggling certain render features by adding/removing a single component to an entity, e.g. `SpotLight` to toggle a light. Because of the retained render world this no longer works: Extract will add any new components, but when it is removed the entity persists unchanged in the render world. ## Solution Add `SyncComponentPlugin<C: Component>` that registers `SyncToRenderWorld` as a required component for `C`, and adds a component hook that will clear all components from the render world entity when `C` is removed. We add this plugin to `ExtractComponentPlugin` which fixes most instances of the problem. For custom extraction logic we can manually add `SyncComponentPlugin` for that component. We also rename `WorldSyncPlugin` to `SyncWorldPlugin` so we start a naming convention like all the `Extract` plugins. In this PR I also fixed a bunch of breakage related to the retained render world, stemming from old code that assumed that `Entity` would be the same in both worlds. I found that using the `RenderEntity` wrapper instead of `Entity` in data structures when referring to render world entities makes intent much clearer, so I propose we make this an official pattern. ## Testing Run examples like ``` cargo run --features pbr_multi_layer_material_textures --example clearcoat cargo run --example volumetric_fog ``` and see that they work, and that toggles work correctly. But really we should test every single example, as we might not even have caught all the breakage yet. --- ## Migration Guide The retained render world notes should be updated to explain this edge case and `SyncComponentPlugin` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Trashtalk217 <trashtalk217@gmail.com>
This commit is contained in:
parent
45eff09213
commit
2d1b4939d2
36 changed files with 151 additions and 84 deletions
|
@ -2,7 +2,7 @@ use bevy_ecs::prelude::*;
|
|||
use bevy_render::{
|
||||
render_resource::{StorageBuffer, UniformBuffer},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
world_sync::RenderEntity,
|
||||
sync_world::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_utils::{Entry, HashMap};
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::world_sync::SyncToRenderWorld;
|
||||
use bevy_render::sync_world::SyncToRenderWorld;
|
||||
use bevy_render::{
|
||||
camera::{
|
||||
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
|
||||
|
|
|
@ -55,9 +55,9 @@ use bevy_render::{
|
|||
TextureFormat, TextureUsages,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
sync_world::RenderEntity,
|
||||
texture::TextureCache,
|
||||
view::{Msaa, ViewDepthTexture},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ use bevy_render::{
|
|||
extract_component::ExtractComponent,
|
||||
primitives::Frustum,
|
||||
render_resource::{LoadOp, TextureUsages},
|
||||
sync_world::SyncToRenderWorld,
|
||||
view::{ColorGrading, Msaa, VisibleEntities},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
@ -89,9 +89,9 @@ use bevy_render::{
|
|||
Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
sync_world::RenderEntity,
|
||||
texture::{BevyDefault, ColorAttachment, Image, TextureCache},
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{tracing::warn, HashMap};
|
||||
|
|
|
@ -46,12 +46,12 @@ use bevy_render::{
|
|||
TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
sync_world::RenderEntity,
|
||||
texture::{BevyDefault, CachedTexture, TextureCache},
|
||||
view::{
|
||||
prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
|
||||
ViewUniformOffset, ViewUniforms,
|
||||
},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{info_once, prelude::default, warn_once};
|
||||
|
|
|
@ -32,9 +32,9 @@ use bevy_render::{
|
|||
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
sync_world::RenderEntity,
|
||||
texture::{BevyDefault, CachedTexture, TextureCache},
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
world_sync::RenderEntity,
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::tracing::warn;
|
||||
|
|
|
@ -105,7 +105,7 @@ use {
|
|||
ShaderStages, ShaderType, VertexFormat,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
world_sync::TemporaryRenderEntity,
|
||||
sync_world::TemporaryRenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
},
|
||||
bytemuck::cast_slice,
|
||||
|
|
|
@ -15,8 +15,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|||
use bevy_render::{
|
||||
mesh::Mesh3d,
|
||||
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
|
||||
sync_world::SyncToRenderWorld,
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ use bevy_render::{
|
|||
UniformBuffer,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
world_sync::RenderEntity,
|
||||
sync_world::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_utils::{hashbrown::HashSet, tracing::warn};
|
||||
|
|
|
@ -127,6 +127,7 @@ use bevy_render::{
|
|||
render_asset::prepare_assets,
|
||||
render_graph::RenderGraph,
|
||||
render_resource::Shader,
|
||||
sync_component::SyncComponentPlugin,
|
||||
texture::{GpuImage, Image},
|
||||
view::{check_visibility, VisibilitySystems},
|
||||
ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
|
@ -314,7 +315,6 @@ impl Plugin for PbrPlugin {
|
|||
.register_type::<PointLight>()
|
||||
.register_type::<PointLightShadowMap>()
|
||||
.register_type::<SpotLight>()
|
||||
.register_type::<DistanceFog>()
|
||||
.register_type::<ShadowFilteringMethod>()
|
||||
.init_resource::<AmbientLight>()
|
||||
.init_resource::<GlobalVisibleClusterableObjects>()
|
||||
|
@ -346,6 +346,11 @@ impl Plugin for PbrPlugin {
|
|||
VolumetricFogPlugin,
|
||||
ScreenSpaceReflectionsPlugin,
|
||||
))
|
||||
.add_plugins((
|
||||
SyncComponentPlugin::<DirectionalLight>::default(),
|
||||
SyncComponentPlugin::<PointLight>::default(),
|
||||
SyncComponentPlugin::<SpotLight>::default(),
|
||||
))
|
||||
.configure_sets(
|
||||
PostUpdate,
|
||||
(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
|
||||
use bevy_render::view::Visibility;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -57,8 +57,7 @@ use super::*;
|
|||
CascadeShadowConfig,
|
||||
CascadesVisibleEntities,
|
||||
Transform,
|
||||
Visibility,
|
||||
SyncToRenderWorld
|
||||
Visibility
|
||||
)]
|
||||
pub struct DirectionalLight {
|
||||
/// The color of the light.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
|
||||
use bevy_render::view::Visibility;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -21,13 +21,7 @@ use super::*;
|
|||
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting)
|
||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
#[require(
|
||||
CubemapFrusta,
|
||||
CubemapVisibleEntities,
|
||||
Transform,
|
||||
Visibility,
|
||||
SyncToRenderWorld
|
||||
)]
|
||||
#[require(CubemapFrusta, CubemapVisibleEntities, Transform, Visibility)]
|
||||
pub struct PointLight {
|
||||
/// The color of this light source.
|
||||
pub color: Color,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
|
||||
use bevy_render::view::Visibility;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -9,7 +9,7 @@ use super::*;
|
|||
/// the transform, and can be specified with [`Transform::looking_at`](Transform::looking_at).
|
||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
#[require(Frustum, VisibleMeshEntities, Transform, Visibility, SyncToRenderWorld)]
|
||||
#[require(Frustum, VisibleMeshEntities, Transform, Visibility)]
|
||||
pub struct SpotLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
|
|
|
@ -21,9 +21,9 @@ use bevy_render::{
|
|||
render_resource::{DynamicUniformBuffer, Sampler, Shader, ShaderType, TextureView},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
settings::WgpuFeatures,
|
||||
sync_world::RenderEntity,
|
||||
texture::{FallbackImage, GpuImage, Image},
|
||||
view::ExtractedView,
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
||||
|
|
|
@ -3,7 +3,7 @@ mod prepass_bindings;
|
|||
use bevy_render::{
|
||||
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
|
||||
render_resource::binding_types::uniform_buffer,
|
||||
world_sync::RenderEntity,
|
||||
sync_world::RenderEntity,
|
||||
};
|
||||
pub use prepass_bindings::*;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use bevy_ecs::{
|
|||
system::lifetimeless::Read,
|
||||
};
|
||||
use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_render::world_sync::RenderEntity;
|
||||
use bevy_render::sync_world::RenderEntity;
|
||||
use bevy_render::{
|
||||
diagnostic::RecordDiagnostics,
|
||||
mesh::RenderMesh,
|
||||
|
|
|
@ -30,9 +30,9 @@ use bevy_render::{
|
|||
*,
|
||||
},
|
||||
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
|
||||
sync_world::RenderEntity,
|
||||
texture::{CachedTexture, TextureCache},
|
||||
view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{
|
||||
|
|
|
@ -51,6 +51,7 @@ use bevy_render::{
|
|||
mesh::{Mesh, Meshable},
|
||||
render_graph::{RenderGraphApp, ViewNodeRunner},
|
||||
render_resource::{Shader, SpecializedRenderPipelines},
|
||||
sync_component::SyncComponentPlugin,
|
||||
texture::Image,
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
|
@ -231,6 +232,8 @@ impl Plugin for VolumetricFogPlugin {
|
|||
app.register_type::<VolumetricFog>()
|
||||
.register_type::<VolumetricLight>();
|
||||
|
||||
app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -36,9 +36,9 @@ use bevy_render::{
|
|||
TextureSampleType, TextureUsages, VertexState,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
sync_world::RenderEntity,
|
||||
texture::{BevyDefault as _, GpuImage, Image},
|
||||
view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset},
|
||||
world_sync::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
|
|
|
@ -6,12 +6,12 @@ use crate::{
|
|||
render_asset::RenderAssets,
|
||||
render_graph::{InternedRenderSubGraph, RenderSubGraph},
|
||||
render_resource::TextureView,
|
||||
sync_world::{RenderEntity, SyncToRenderWorld},
|
||||
texture::GpuImage,
|
||||
view::{
|
||||
ColorGrading, ExtractedView, ExtractedWindows, GpuCulling, Msaa, RenderLayers, Visibility,
|
||||
VisibleEntities,
|
||||
},
|
||||
world_sync::{RenderEntity, SyncToRenderWorld},
|
||||
Extract,
|
||||
};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::{
|
||||
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_component::SyncComponentPlugin,
|
||||
sync_world::RenderEntity,
|
||||
view::ViewVisibility,
|
||||
world_sync::{RenderEntity, SyncToRenderWorld},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
|
@ -12,7 +13,6 @@ use bevy_ecs::{
|
|||
prelude::*,
|
||||
query::{QueryFilter, QueryItem, ReadOnlyQueryData},
|
||||
system::lifetimeless::Read,
|
||||
world::OnAdd,
|
||||
};
|
||||
use core::{marker::PhantomData, ops::Deref};
|
||||
|
||||
|
@ -160,15 +160,6 @@ fn prepare_uniform_components<C>(
|
|||
/// This plugin extracts the components into the render world for synced entities.
|
||||
///
|
||||
/// To do so, it sets up the [`ExtractSchedule`] step for the specified [`ExtractComponent`].
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// Be careful when removing the [`ExtractComponent`] from an entity. When an [`ExtractComponent`]
|
||||
/// is added to an entity, that entity is automatically synced with the render world (see also
|
||||
/// [`WorldSyncPlugin`](crate::world_sync::WorldSyncPlugin)). When removing the entity in the main
|
||||
/// world, the synced entity also gets removed. However, if only the [`ExtractComponent`] is removed
|
||||
/// this *doesn't* happen, and the synced entity stays around with the old extracted data.
|
||||
/// We recommend despawning the entire entity, instead of only removing [`ExtractComponent`].
|
||||
pub struct ExtractComponentPlugin<C, F = ()> {
|
||||
only_extract_visible: bool,
|
||||
marker: PhantomData<fn() -> (C, F)>,
|
||||
|
@ -194,10 +185,8 @@ impl<C, F> ExtractComponentPlugin<C, F> {
|
|||
|
||||
impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
|
||||
fn build(&self, app: &mut App) {
|
||||
// TODO: use required components
|
||||
app.observe(|trigger: Trigger<OnAdd, C>, mut commands: Commands| {
|
||||
commands.entity(trigger.entity()).insert(SyncToRenderWorld);
|
||||
});
|
||||
app.add_plugins(SyncComponentPlugin::<C>::default());
|
||||
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
if self.only_extract_visible {
|
||||
render_app.add_systems(ExtractSchedule, extract_visible_components::<C>);
|
||||
|
@ -219,7 +208,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`SyncToRenderWorld`].
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`].
|
||||
fn extract_components<C: ExtractComponent>(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
|
@ -235,7 +224,7 @@ fn extract_components<C: ExtractComponent>(
|
|||
commands.insert_or_spawn_batch(values);
|
||||
}
|
||||
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`SyncToRenderWorld`].
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToRenderWorld`].
|
||||
fn extract_visible_components<C: ExtractComponent>(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
|
|
|
@ -30,7 +30,7 @@ use core::ops::{Deref, DerefMut};
|
|||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_render::Extract;
|
||||
/// use bevy_render::world_sync::RenderEntity;
|
||||
/// use bevy_render::sync_world::RenderEntity;
|
||||
/// # #[derive(Component)]
|
||||
/// // Do make sure to sync the cloud entities before extracting them.
|
||||
/// # struct Cloud;
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
|||
render_resource::{Buffer, BufferUsages, Extent3d, ImageDataLayout, Texture, TextureFormat},
|
||||
renderer::{render_system, RenderDevice},
|
||||
storage::{GpuShaderStorageBuffer, ShaderStorageBuffer},
|
||||
sync_world::MainEntity,
|
||||
texture::{GpuImage, TextureFormatPixelInfo},
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
@ -232,7 +233,7 @@ fn prepare_buffers(
|
|||
mut buffer_pool: ResMut<GpuReadbackBufferPool>,
|
||||
gpu_images: Res<RenderAssets<GpuImage>>,
|
||||
ssbos: Res<RenderAssets<GpuShaderStorageBuffer>>,
|
||||
handles: Query<(Entity, &Readback)>,
|
||||
handles: Query<(&MainEntity, &Readback)>,
|
||||
) {
|
||||
for (entity, readback) in handles.iter() {
|
||||
match readback {
|
||||
|
@ -254,7 +255,7 @@ fn prepare_buffers(
|
|||
);
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
readbacks.requested.push(GpuReadback {
|
||||
entity,
|
||||
entity: entity.id(),
|
||||
src: ReadbackSource::Texture {
|
||||
texture: gpu_image.texture.clone(),
|
||||
layout,
|
||||
|
@ -272,7 +273,7 @@ fn prepare_buffers(
|
|||
let buffer = buffer_pool.get(&render_device, size);
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
readbacks.requested.push(GpuReadback {
|
||||
entity,
|
||||
entity: entity.id(),
|
||||
src: ReadbackSource::Buffer {
|
||||
src_start: 0,
|
||||
dst_start: 0,
|
||||
|
|
|
@ -38,9 +38,10 @@ pub mod renderer;
|
|||
pub mod settings;
|
||||
mod spatial_bundle;
|
||||
pub mod storage;
|
||||
pub mod sync_component;
|
||||
pub mod sync_world;
|
||||
pub mod texture;
|
||||
pub mod view;
|
||||
pub mod world_sync;
|
||||
|
||||
/// The render prelude.
|
||||
///
|
||||
|
@ -77,8 +78,8 @@ use extract_resource::ExtractResourcePlugin;
|
|||
use globals::GlobalsPlugin;
|
||||
use render_asset::RenderAssetBytesPerFrame;
|
||||
use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||
use world_sync::{
|
||||
despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, WorldSyncPlugin,
|
||||
use sync_world::{
|
||||
despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, SyncWorldPlugin,
|
||||
};
|
||||
|
||||
use crate::gpu_readback::GpuReadbackPlugin;
|
||||
|
@ -371,7 +372,7 @@ impl Plugin for RenderPlugin {
|
|||
GlobalsPlugin,
|
||||
MorphPlugin,
|
||||
BatchingPlugin,
|
||||
WorldSyncPlugin,
|
||||
SyncWorldPlugin,
|
||||
StoragePlugin,
|
||||
GpuReadbackPlugin::default(),
|
||||
));
|
||||
|
|
|
@ -81,7 +81,7 @@ impl Drop for RenderAppChannels {
|
|||
/// The plugin is dependent on the [`RenderApp`] added by [`crate::RenderPlugin`] and so must
|
||||
/// be added after that plugin. If it is not added after, the plugin will do nothing.
|
||||
///
|
||||
/// A single frame of execution looks something like below
|
||||
/// A single frame of execution looks something like below
|
||||
///
|
||||
/// ```text
|
||||
/// |---------------------------------------------------------------------------|
|
||||
|
@ -92,7 +92,7 @@ impl Drop for RenderAppChannels {
|
|||
/// ```
|
||||
///
|
||||
/// - `sync` is the step where the entity-entity mapping between the main and render world is updated.
|
||||
/// This is run on the main app's thread. For more information checkout [`WorldSyncPlugin`].
|
||||
/// This is run on the main app's thread. For more information checkout [`SyncWorldPlugin`].
|
||||
/// - `extract` is the step where data is copied from the main world to the render world.
|
||||
/// This is run on the main app's thread.
|
||||
/// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the
|
||||
|
@ -104,7 +104,7 @@ impl Drop for RenderAppChannels {
|
|||
/// - And finally the `main app schedule` is run.
|
||||
/// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again.
|
||||
///
|
||||
/// [`WorldSyncPlugin`]: crate::world_sync::WorldSyncPlugin
|
||||
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
|
||||
#[derive(Default)]
|
||||
pub struct PipelinedRenderingPlugin;
|
||||
|
||||
|
|
42
crates/bevy_render/src/sync_component.rs
Normal file
42
crates/bevy_render/src/sync_component.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use core::marker::PhantomData;
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::component::Component;
|
||||
|
||||
use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld};
|
||||
|
||||
/// Plugin that registers a component for automatic sync to the render world. See [`SyncWorldPlugin`] for more information.
|
||||
///
|
||||
/// This plugin is automatically added by [`ExtractComponentPlugin`], and only needs to be added for manual extraction implementations.
|
||||
///
|
||||
/// # Implementation details
|
||||
///
|
||||
/// It adds [`SyncToRenderWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and
|
||||
/// handles cleanup of the component in the render world when it is removed from an entity.
|
||||
///
|
||||
/// # Warning
|
||||
/// When the component is removed from the main world entity, all components are removed from the entity in the render world.
|
||||
/// This is done in order to handle components with custom extraction logic and derived state.
|
||||
///
|
||||
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
|
||||
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
|
||||
pub struct SyncComponentPlugin<C: Component>(PhantomData<C>);
|
||||
|
||||
impl<C: Component> Default for SyncComponentPlugin<C> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> Plugin for SyncComponentPlugin<C> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_required_components::<C, SyncToRenderWorld>();
|
||||
|
||||
app.world_mut().register_component_hooks::<C>().on_remove(
|
||||
|mut world, entity, _component_id| {
|
||||
let mut pending = world.resource_mut::<PendingSyncEntity>();
|
||||
pending.push(EntityRecord::ComponentRemoved(entity));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,11 +13,18 @@ use bevy_reflect::Reflect;
|
|||
|
||||
/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world.
|
||||
///
|
||||
/// All entities with the [`SyncToRenderWorld`] component are kept in sync. It
|
||||
/// is automatically added as a required component by [`ExtractComponentPlugin`]
|
||||
/// and [`SyncComponentPlugin`], so it doesn't need to be added manually when
|
||||
/// spawning or as a required component when either of these plugins are used.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// Bevy's renderer is architected independently from the main app.
|
||||
/// It operates in its own separate ECS [`World`], so the renderer logic can run in parallel with the main world logic.
|
||||
/// This is called "Pipelined Rendering", see [`PipelinedRenderingPlugin`] for more information.
|
||||
///
|
||||
/// [`WorldSyncPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping
|
||||
/// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping
|
||||
/// between the main world and the render world.
|
||||
/// It does so by spawning and despawning entities in the render world, to match spawned and despawned entities in the main world.
|
||||
/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components.
|
||||
|
@ -66,14 +73,16 @@ use bevy_reflect::Reflect;
|
|||
/// The render world probably cares about a `Position` component, but not a `Velocity` component.
|
||||
/// The extraction happens in its own step, independently from, and after synchronization.
|
||||
///
|
||||
/// Moreover, [`WorldSyncPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled
|
||||
/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled
|
||||
/// differently.
|
||||
///
|
||||
/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin
|
||||
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
|
||||
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
|
||||
#[derive(Default)]
|
||||
pub struct WorldSyncPlugin;
|
||||
pub struct SyncWorldPlugin;
|
||||
|
||||
impl Plugin for WorldSyncPlugin {
|
||||
impl Plugin for SyncWorldPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.init_resource::<PendingSyncEntity>();
|
||||
app.observe(
|
||||
|
@ -86,22 +95,30 @@ impl Plugin for WorldSyncPlugin {
|
|||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.entity()) {
|
||||
pending.push(EntityRecord::Removed(e.id()));
|
||||
pending.push(EntityRecord::Removed(*e));
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world.
|
||||
///
|
||||
/// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`].
|
||||
/// For more information see [`SyncWorldPlugin`].
|
||||
///
|
||||
/// NOTE: This component should persist throughout the entity's entire lifecycle.
|
||||
/// If this component is removed from its entity, the entity will be despawned.
|
||||
///
|
||||
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
|
||||
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
|
||||
#[derive(Component, Clone, Debug, Default, Reflect)]
|
||||
#[reflect[Component]]
|
||||
#[component(storage = "SparseSet")]
|
||||
pub struct SyncToRenderWorld;
|
||||
|
||||
/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity
|
||||
/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity.
|
||||
///
|
||||
/// Can also be used as a newtype wrapper for render world entities.
|
||||
#[derive(Component, Deref, Clone, Debug, Copy)]
|
||||
pub struct RenderEntity(Entity);
|
||||
impl RenderEntity {
|
||||
|
@ -111,7 +128,9 @@ impl RenderEntity {
|
|||
}
|
||||
}
|
||||
|
||||
/// Component added on the render world entities to keep track of the corresponding main world entity
|
||||
/// Component added on the render world entities to keep track of the corresponding main world entity.
|
||||
///
|
||||
/// Can also be used as a newtype wrapper for main world entities.
|
||||
#[derive(Component, Deref, Clone, Debug)]
|
||||
pub struct MainEntity(Entity);
|
||||
impl MainEntity {
|
||||
|
@ -127,13 +146,17 @@ impl MainEntity {
|
|||
pub struct TemporaryRenderEntity;
|
||||
|
||||
/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EntityRecord {
|
||||
/// When an entity is spawned on the main world, notify the render world so that it can spawn a corresponding
|
||||
/// entity. This contains the main world entity.
|
||||
Added(Entity),
|
||||
/// When an entity is despawned on the main world, notify the render world so that the corresponding entity can be
|
||||
/// despawned. This contains the render world entity.
|
||||
Removed(Entity),
|
||||
Removed(RenderEntity),
|
||||
/// When a component is removed from an entity, notify the render world so that the corresponding component can be
|
||||
/// removed. This contains the main world entity.
|
||||
ComponentRemoved(Entity),
|
||||
}
|
||||
|
||||
// Entity Record in MainWorld pending to Sync
|
||||
|
@ -148,8 +171,8 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl
|
|||
for record in pending.drain(..) {
|
||||
match record {
|
||||
EntityRecord::Added(e) => {
|
||||
if let Ok(mut entity) = world.get_entity_mut(e) {
|
||||
match entity.entry::<RenderEntity>() {
|
||||
if let Ok(mut main_entity) = world.get_entity_mut(e) {
|
||||
match main_entity.entry::<RenderEntity>() {
|
||||
bevy_ecs::world::Entry::Occupied(_) => {
|
||||
panic!("Attempting to synchronize an entity that has already been synchronized!");
|
||||
}
|
||||
|
@ -161,11 +184,24 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl
|
|||
};
|
||||
}
|
||||
}
|
||||
EntityRecord::Removed(e) => {
|
||||
if let Ok(ec) = render_world.get_entity_mut(e) {
|
||||
EntityRecord::Removed(render_entity) => {
|
||||
if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) {
|
||||
ec.despawn();
|
||||
};
|
||||
}
|
||||
EntityRecord::ComponentRemoved(main_entity) => {
|
||||
let Some(mut render_entity) = world.get_mut::<RenderEntity>(main_entity) else {
|
||||
continue;
|
||||
};
|
||||
if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) {
|
||||
// In order to handle components that extract to derived components, we clear the entity
|
||||
// and let the extraction system re-add the components.
|
||||
render_world_entity.despawn();
|
||||
|
||||
let id = render_world.spawn(MainEntity(main_entity)).id();
|
||||
render_entity.0 = id;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -207,7 +243,7 @@ mod tests {
|
|||
struct RenderDataComponent;
|
||||
|
||||
#[test]
|
||||
fn world_sync() {
|
||||
fn sync_world() {
|
||||
let mut main_world = World::new();
|
||||
let mut render_world = World::new();
|
||||
main_world.init_resource::<PendingSyncEntity>();
|
||||
|
@ -222,7 +258,7 @@ mod tests {
|
|||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.entity()) {
|
||||
pending.push(EntityRecord::Removed(e.id()));
|
||||
pending.push(EntityRecord::Removed(*e));
|
||||
};
|
||||
},
|
||||
);
|
|
@ -2,9 +2,9 @@ use crate::Sprite;
|
|||
use bevy_asset::Handle;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render::{
|
||||
sync_world::SyncToRenderWorld,
|
||||
texture::Image,
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ use bevy_render::{
|
|||
*,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
texture::{
|
||||
BevyDefault, DefaultImageSampler, FallbackImage, GpuImage, Image, ImageSampler,
|
||||
TextureFormatPixelInfo,
|
||||
|
@ -39,7 +40,6 @@ use bevy_render::{
|
|||
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
|
||||
ViewVisibility, VisibleEntities,
|
||||
},
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
|
|
|
@ -15,7 +15,7 @@ use bevy_ecs::{
|
|||
system::{Commands, Local, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_render::world_sync::TemporaryRenderEntity;
|
||||
use bevy_render::sync_world::TemporaryRenderEntity;
|
||||
use bevy_render::{
|
||||
primitives::Aabb,
|
||||
texture::Image,
|
||||
|
|
|
@ -19,9 +19,9 @@ use bevy_render::{
|
|||
render_phase::*,
|
||||
render_resource::{binding_types::uniform_buffer, *},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
texture::BevyDefault,
|
||||
view::*,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract, ExtractSchedule, Render, RenderSet,
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
|
|
|
@ -32,9 +32,9 @@ use bevy_render::{
|
|||
};
|
||||
use bevy_render::{
|
||||
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
texture::GpuImage,
|
||||
view::ViewVisibility,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
ExtractSchedule, Render,
|
||||
};
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
|
|
|
@ -18,9 +18,9 @@ use bevy_render::{
|
|||
render_phase::*,
|
||||
render_resource::{binding_types::uniform_buffer, *},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
texture::BevyDefault,
|
||||
view::*,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract, ExtractSchedule, Render, RenderSet,
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
|
|
|
@ -16,9 +16,9 @@ use bevy_render::{
|
|||
render_phase::*,
|
||||
render_resource::{binding_types::uniform_buffer, *},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
texture::{BevyDefault, GpuImage, Image, TRANSPARENT_IMAGE_HANDLE},
|
||||
view::*,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract, ExtractSchedule, Render, RenderSet,
|
||||
};
|
||||
use bevy_sprite::{
|
||||
|
|
|
@ -9,7 +9,6 @@ use bevy::{
|
|||
math::vec3,
|
||||
pbr::{FogVolume, VolumetricFog, VolumetricLight},
|
||||
prelude::*,
|
||||
render::world_sync::SyncToRenderWorld,
|
||||
};
|
||||
|
||||
/// Entry point.
|
||||
|
@ -44,9 +43,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
// up.
|
||||
scattering: 1.0,
|
||||
..default()
|
||||
})
|
||||
// indicates that this fog volume needs to be Synchronized to the render world
|
||||
.insert(SyncToRenderWorld);
|
||||
});
|
||||
|
||||
// Spawn a bright directional light that illuminates the fog well.
|
||||
commands.spawn((
|
||||
|
|
Loading…
Reference in a new issue