mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
The Cooler 'Retain Rendering World' (#15320)
- Adopted from #14449 - Still fixes #12144. ## Migration Guide The retained render world is a complex change: migrating might take one of a few different forms depending on the patterns you're using. For every example, we specify in which world the code is run. Most of the changes affect render world code, so for the average Bevy user who's using Bevy's high-level rendering APIs, these changes are unlikely to affect your code. ### Spawning entities in the render world Previously, if you spawned an entity with `world.spawn(...)`, `commands.spawn(...)` or some other method in the rendering world, it would be despawned at the end of each frame. In 0.15, this is no longer the case and so your old code could leak entities. This can be mitigated by either re-architecting your code to no longer continuously spawn entities (like you're used to in the main world), or by adding the `bevy_render::world_sync::TemporaryRenderEntity` component to the entity you're spawning. Entities tagged with `TemporaryRenderEntity` will be removed at the end of each frame (like before). ### Extract components with `ExtractComponentPlugin` ``` // main world app.add_plugins(ExtractComponentPlugin::<ComponentToExtract>::default()); ``` `ExtractComponentPlugin` has been changed to only work with synced entities. Entities are automatically synced if `ComponentToExtract` is added to them. However, entities are not "unsynced" if any given `ComponentToExtract` is removed, because an entity may have multiple components to extract. This would cause the other components to no longer get extracted because the entity is not synced. So be careful when only removing extracted components from entities in the render world, because it might leave an entity behind in the render world. The solution here is to avoid only removing extracted components and instead despawn the entire entity. ### Manual extraction using `Extract<Query<(Entity, ...)>>` ```rust // in render world, inspired by bevy_pbr/src/cluster/mod.rs pub fn extract_clusters( mut commands: Commands, views: Extract<Query<(Entity, &Clusters, &Camera)>>, ) { for (entity, clusters, camera) in &views { // some code commands.get_or_spawn(entity).insert(...); } } ``` One of the primary consequences of the retained rendering world is that there's no longer a one-to-one mapping from entity IDs in the main world to entity IDs in the render world. Unlike in Bevy 0.14, Entity 42 in the main world doesn't necessarily map to entity 42 in the render world. Previous code which called `get_or_spawn(main_world_entity)` in the render world (`Extract<Query<(Entity, ...)>>` returns main world entities). Instead, you should use `&RenderEntity` and `render_entity.id()` to get the correct entity in the render world. Note that this entity does need to be synced first in order to have a `RenderEntity`. When performing manual abstraction, this won't happen automatically (like with `ExtractComponentPlugin`) so add a `SyncToRenderWorld` marker component to the entities you want to extract. This results in the following code: ```rust // in render world, inspired by bevy_pbr/src/cluster/mod.rs pub fn extract_clusters( mut commands: Commands, views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>, ) { for (render_entity, clusters, camera) in &views { // some code commands.get_or_spawn(render_entity.id()).insert(...); } } // in main world, when spawning world.spawn(Clusters::default(), Camera::default(), SyncToRenderWorld) ``` ### Looking up `Entity` ids in the render world As previously stated, there's now no correspondence between main world and render world `Entity` identifiers. Querying for `Entity` in the render world will return the `Entity` id in the render world: query for `MainEntity` (and use its `id()` method) to get the corresponding entity in the main world. This is also a good way to tell the difference between synced and unsynced entities in the render world, because unsynced entities won't have a `MainEntity` component. --------- Co-authored-by: re0312 <re0312@outlook.com> Co-authored-by: re0312 <45868716+re0312@users.noreply.github.com> Co-authored-by: Periwink <charlesbour@gmail.com> Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com> Co-authored-by: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
This commit is contained in:
parent
01dce4742f
commit
56f8e526dd
28 changed files with 690 additions and 241 deletions
|
@ -2,6 +2,7 @@ use bevy_ecs::prelude::*;
|
|||
use bevy_render::{
|
||||
render_resource::{StorageBuffer, UniformBuffer},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
world_sync::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_utils::{Entry, HashMap};
|
||||
|
@ -26,13 +27,13 @@ pub(super) struct ExtractedStateBuffers {
|
|||
|
||||
pub(super) fn extract_buffers(
|
||||
mut commands: Commands,
|
||||
changed: Extract<Query<(Entity, &AutoExposure), Changed<AutoExposure>>>,
|
||||
changed: Extract<Query<(&RenderEntity, &AutoExposure), Changed<AutoExposure>>>,
|
||||
mut removed: Extract<RemovedComponents<AutoExposure>>,
|
||||
) {
|
||||
commands.insert_resource(ExtractedStateBuffers {
|
||||
changed: changed
|
||||
.iter()
|
||||
.map(|(entity, settings)| (entity, settings.clone()))
|
||||
.map(|(entity, settings)| (entity.id(), settings.clone()))
|
||||
.collect(),
|
||||
removed: removed.read().collect(),
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
|||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::world_sync::SyncToRenderWorld;
|
||||
use bevy_render::{
|
||||
camera::{
|
||||
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
|
||||
|
@ -35,6 +36,8 @@ pub struct Camera2dBundle {
|
|||
pub deband_dither: DebandDither,
|
||||
pub main_texture_usages: CameraMainTextureUsages,
|
||||
pub msaa: Msaa,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
||||
impl Default for Camera2dBundle {
|
||||
|
@ -55,6 +58,7 @@ impl Default for Camera2dBundle {
|
|||
deband_dither: DebandDither::Disabled,
|
||||
main_texture_usages: Default::default(),
|
||||
msaa: Default::default(),
|
||||
sync: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +92,7 @@ impl Camera2dBundle {
|
|||
deband_dither: DebandDither::Disabled,
|
||||
main_texture_usages: Default::default(),
|
||||
msaa: Default::default(),
|
||||
sync: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ use bevy_render::{
|
|||
renderer::RenderDevice,
|
||||
texture::TextureCache,
|
||||
view::{Msaa, ViewDepthTexture},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
|
@ -357,11 +358,10 @@ impl CachedRenderPipelinePhaseItem for Transparent2d {
|
|||
}
|
||||
|
||||
pub fn extract_core_2d_camera_phases(
|
||||
mut commands: Commands,
|
||||
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||
mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
||||
mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
|
||||
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
|
||||
cameras_2d: Extract<Query<(&RenderEntity, &Camera), With<Camera2d>>>,
|
||||
mut live_entities: Local<EntityHashSet>,
|
||||
) {
|
||||
live_entities.clear();
|
||||
|
@ -370,8 +370,7 @@ pub fn extract_core_2d_camera_phases(
|
|||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
|
||||
commands.get_or_spawn(entity);
|
||||
let entity = entity.id();
|
||||
transparent_2d_phases.insert_or_clear(entity);
|
||||
opaque_2d_phases.insert_or_clear(entity);
|
||||
alpha_mask_2d_phases.insert_or_clear(entity);
|
||||
|
|
|
@ -10,6 +10,7 @@ use bevy_render::{
|
|||
primitives::Frustum,
|
||||
render_resource::{LoadOp, TextureUsages},
|
||||
view::{ColorGrading, Msaa, VisibleEntities},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -153,6 +154,8 @@ pub struct Camera3dBundle {
|
|||
pub exposure: Exposure,
|
||||
pub main_texture_usages: CameraMainTextureUsages,
|
||||
pub msaa: Msaa,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
||||
// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference
|
||||
|
@ -173,6 +176,7 @@ impl Default for Camera3dBundle {
|
|||
main_texture_usages: Default::default(),
|
||||
deband_dither: DebandDither::Enabled,
|
||||
msaa: Default::default(),
|
||||
sync: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ use bevy_render::{
|
|||
renderer::RenderDevice,
|
||||
texture::{BevyDefault, ColorAttachment, Image, TextureCache},
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{tracing::warn, HashMap};
|
||||
|
@ -504,23 +505,21 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
|
|||
}
|
||||
|
||||
pub fn extract_core_3d_camera_phases(
|
||||
mut commands: Commands,
|
||||
mut opaque_3d_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
|
||||
mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
|
||||
mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
|
||||
mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
|
||||
cameras_3d: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
|
||||
cameras_3d: Extract<Query<(&RenderEntity, &Camera), With<Camera3d>>>,
|
||||
mut live_entities: Local<EntityHashSet>,
|
||||
) {
|
||||
live_entities.clear();
|
||||
|
||||
for (entity, camera) in &cameras_3d {
|
||||
for (render_entity, camera) in &cameras_3d {
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
|
||||
commands.get_or_spawn(entity);
|
||||
|
||||
let entity = render_entity.id();
|
||||
opaque_3d_phases.insert_or_clear(entity);
|
||||
alpha_mask_3d_phases.insert_or_clear(entity);
|
||||
transmissive_3d_phases.insert_or_clear(entity);
|
||||
|
@ -545,7 +544,7 @@ pub fn extract_camera_prepass_phase(
|
|||
cameras_3d: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&RenderEntity,
|
||||
&Camera,
|
||||
Has<DepthPrepass>,
|
||||
Has<NormalPrepass>,
|
||||
|
@ -559,13 +558,20 @@ pub fn extract_camera_prepass_phase(
|
|||
) {
|
||||
live_entities.clear();
|
||||
|
||||
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
|
||||
cameras_3d.iter()
|
||||
for (
|
||||
render_entity,
|
||||
camera,
|
||||
depth_prepass,
|
||||
normal_prepass,
|
||||
motion_vector_prepass,
|
||||
deferred_prepass,
|
||||
) in cameras_3d.iter()
|
||||
{
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entity = render_entity.id();
|
||||
if depth_prepass || normal_prepass || motion_vector_prepass {
|
||||
opaque_3d_prepass_phases.insert_or_clear(entity);
|
||||
alpha_mask_3d_prepass_phases.insert_or_clear(entity);
|
||||
|
@ -581,7 +587,6 @@ pub fn extract_camera_prepass_phase(
|
|||
opaque_3d_deferred_phases.remove(&entity);
|
||||
alpha_mask_3d_deferred_phases.remove(&entity);
|
||||
}
|
||||
|
||||
live_entities.insert(entity);
|
||||
|
||||
commands
|
||||
|
|
|
@ -51,6 +51,7 @@ use bevy_render::{
|
|||
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};
|
||||
|
@ -809,7 +810,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
|
|||
/// Extracts all [`DepthOfField`] components into the render world.
|
||||
fn extract_depth_of_field_settings(
|
||||
mut commands: Commands,
|
||||
mut query: Extract<Query<(Entity, &DepthOfField, &Projection)>>,
|
||||
mut query: Extract<Query<(&RenderEntity, &DepthOfField, &Projection)>>,
|
||||
) {
|
||||
if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
|
||||
info_once!(
|
||||
|
@ -819,6 +820,7 @@ fn extract_depth_of_field_settings(
|
|||
}
|
||||
|
||||
for (entity, depth_of_field, projection) in query.iter_mut() {
|
||||
let entity = entity.id();
|
||||
// Depth of field is nonsensical without a perspective projection.
|
||||
let Projection::Perspective(ref perspective_projection) = *projection else {
|
||||
continue;
|
||||
|
|
|
@ -32,6 +32,7 @@ use bevy_render::{
|
|||
renderer::{RenderContext, RenderDevice},
|
||||
texture::{BevyDefault, CachedTexture, TextureCache},
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
world_sync::RenderEntity,
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::tracing::warn;
|
||||
|
@ -351,20 +352,26 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
|||
}
|
||||
|
||||
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
|
||||
let mut cameras_3d = main_world
|
||||
.query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasing), (
|
||||
With<Camera3d>,
|
||||
With<TemporalJitter>,
|
||||
With<DepthPrepass>,
|
||||
With<MotionVectorPrepass>,
|
||||
)>();
|
||||
let mut cameras_3d = main_world.query_filtered::<(
|
||||
&RenderEntity,
|
||||
&Camera,
|
||||
&Projection,
|
||||
&mut TemporalAntiAliasing,
|
||||
), (
|
||||
With<Camera3d>,
|
||||
With<TemporalJitter>,
|
||||
With<DepthPrepass>,
|
||||
With<MotionVectorPrepass>,
|
||||
)>();
|
||||
|
||||
for (entity, camera, camera_projection, mut taa_settings) in
|
||||
cameras_3d.iter_mut(&mut main_world)
|
||||
{
|
||||
let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_));
|
||||
if camera.is_active && has_perspective_projection {
|
||||
commands.get_or_spawn(entity).insert(taa_settings.clone());
|
||||
commands
|
||||
.get_or_spawn(entity.id())
|
||||
.insert(taa_settings.clone());
|
||||
taa_settings.reset = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ use {
|
|||
ShaderStages, ShaderType, VertexFormat,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
world_sync::TemporaryRenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
},
|
||||
bytemuck::cast_slice,
|
||||
|
@ -113,7 +114,6 @@ use {
|
|||
any(feature = "bevy_pbr", feature = "bevy_sprite"),
|
||||
))]
|
||||
use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode};
|
||||
|
||||
use bevy_time::Fixed;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use config::{
|
||||
|
@ -459,6 +459,7 @@ fn extract_gizmo_data(
|
|||
(*handle).clone_weak(),
|
||||
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
|
||||
config::GizmoMeshConfig::from(config),
|
||||
TemporaryRenderEntity,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use bevy_render::{
|
|||
mesh::Mesh,
|
||||
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
|
@ -108,6 +109,8 @@ pub struct PointLightBundle {
|
|||
pub inherited_visibility: InheritedVisibility,
|
||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
||||
pub view_visibility: ViewVisibility,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
||||
/// A component bundle for spot light entities
|
||||
|
@ -124,6 +127,8 @@ pub struct SpotLightBundle {
|
|||
pub inherited_visibility: InheritedVisibility,
|
||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
||||
pub view_visibility: ViewVisibility,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
||||
/// A component bundle for [`DirectionalLight`] entities.
|
||||
|
@ -142,4 +147,6 @@ pub struct DirectionalLightBundle {
|
|||
pub inherited_visibility: InheritedVisibility,
|
||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
||||
pub view_visibility: ViewVisibility,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use bevy_render::{
|
|||
UniformBuffer,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
world_sync::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_utils::{hashbrown::HashSet, tracing::warn};
|
||||
|
@ -525,7 +526,8 @@ pub(crate) fn clusterable_object_order(
|
|||
/// Extracts clusters from the main world from the render world.
|
||||
pub fn extract_clusters(
|
||||
mut commands: Commands,
|
||||
views: Extract<Query<(Entity, &Clusters, &Camera)>>,
|
||||
views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
|
||||
mapper: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
for (entity, clusters, camera) in &views {
|
||||
if !camera.is_active {
|
||||
|
@ -544,13 +546,15 @@ pub fn extract_clusters(
|
|||
cluster_objects.spot_light_count as u32,
|
||||
));
|
||||
for clusterable_entity in &cluster_objects.entities {
|
||||
data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
|
||||
*clusterable_entity,
|
||||
));
|
||||
if let Ok(entity) = mapper.get(*clusterable_entity) {
|
||||
data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
|
||||
entity.id(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.get_or_spawn(entity).insert((
|
||||
commands.get_or_spawn(entity.id()).insert((
|
||||
ExtractedClusterableObjects { data },
|
||||
ExtractedClusterConfig {
|
||||
near: clusters.near,
|
||||
|
|
|
@ -439,6 +439,9 @@ impl Plugin for PbrPlugin {
|
|||
)
|
||||
.init_resource::<LightMeta>();
|
||||
|
||||
render_app.world_mut().observe(add_light_view_entities);
|
||||
render_app.world_mut().observe(remove_light_view_entities);
|
||||
|
||||
let shadow_pass_node = ShadowPassNode::new(render_app.world_mut());
|
||||
let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();
|
||||
let draw_3d_graph = graph.get_sub_graph_mut(Core3d).unwrap();
|
||||
|
|
|
@ -23,6 +23,7 @@ use bevy_render::{
|
|||
settings::WgpuFeatures,
|
||||
texture::{FallbackImage, GpuImage, Image},
|
||||
view::ExtractedView,
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
||||
|
@ -371,7 +372,7 @@ impl Plugin for LightProbePlugin {
|
|||
/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance
|
||||
/// if one does not already exist.
|
||||
fn gather_environment_map_uniform(
|
||||
view_query: Extract<Query<(Entity, Option<&EnvironmentMapLight>), With<Camera3d>>>,
|
||||
view_query: Extract<Query<(&RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (view_entity, environment_map_light) in view_query.iter() {
|
||||
|
@ -385,7 +386,7 @@ fn gather_environment_map_uniform(
|
|||
EnvironmentMapUniform::default()
|
||||
};
|
||||
commands
|
||||
.get_or_spawn(view_entity)
|
||||
.get_or_spawn(view_entity.id())
|
||||
.insert(environment_map_uniform);
|
||||
}
|
||||
}
|
||||
|
@ -395,7 +396,9 @@ fn gather_environment_map_uniform(
|
|||
fn gather_light_probes<C>(
|
||||
image_assets: Res<RenderAssets<GpuImage>>,
|
||||
light_probe_query: Extract<Query<(&GlobalTransform, &C), With<LightProbe>>>,
|
||||
view_query: Extract<Query<(Entity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>>,
|
||||
view_query: Extract<
|
||||
Query<(&RenderEntity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>,
|
||||
>,
|
||||
mut reflection_probes: Local<Vec<LightProbeInfo<C>>>,
|
||||
mut view_reflection_probes: Local<Vec<LightProbeInfo<C>>>,
|
||||
mut commands: Commands,
|
||||
|
@ -433,14 +436,15 @@ fn gather_light_probes<C>(
|
|||
// Gather up the light probes in the list.
|
||||
render_view_light_probes.maybe_gather_light_probes(&view_reflection_probes);
|
||||
|
||||
let entity = view_entity.id();
|
||||
// Record the per-view light probes.
|
||||
if render_view_light_probes.is_empty() {
|
||||
commands
|
||||
.get_or_spawn(view_entity)
|
||||
.get_or_spawn(entity)
|
||||
.remove::<RenderViewLightProbes<C>>();
|
||||
} else {
|
||||
commands
|
||||
.get_or_spawn(view_entity)
|
||||
.get_or_spawn(entity)
|
||||
.insert(render_view_light_probes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,21 +178,21 @@ pub fn extract_meshlet_mesh_entities(
|
|||
Res<AssetServer>,
|
||||
ResMut<Assets<MeshletMesh>>,
|
||||
EventReader<AssetEvent<MeshletMesh>>,
|
||||
&Entities,
|
||||
)>,
|
||||
>,
|
||||
>,
|
||||
render_entities: &Entities,
|
||||
) {
|
||||
// Get instances query
|
||||
if system_state.is_none() {
|
||||
*system_state = Some(SystemState::new(&mut main_world));
|
||||
}
|
||||
let system_state = system_state.as_mut().unwrap();
|
||||
let (instances_query, asset_server, mut assets, mut asset_events, entities) =
|
||||
let (instances_query, asset_server, mut assets, mut asset_events) =
|
||||
system_state.get_mut(&mut main_world);
|
||||
|
||||
// Reset per-frame data
|
||||
instance_manager.reset(entities);
|
||||
instance_manager.reset(render_entities);
|
||||
|
||||
// Free GPU buffer space for any modified or dropped MeshletMesh assets
|
||||
for asset_event in asset_events.read() {
|
||||
|
|
|
@ -4,6 +4,7 @@ use bevy_render::{
|
|||
mesh::{MeshVertexBufferLayoutRef, RenderMesh},
|
||||
render_resource::binding_types::uniform_buffer,
|
||||
view::WithMesh,
|
||||
world_sync::RenderEntity,
|
||||
};
|
||||
pub use prepass_bindings::*;
|
||||
|
||||
|
@ -580,10 +581,11 @@ where
|
|||
// Extract the render phases for the prepass
|
||||
pub fn extract_camera_previous_view_data(
|
||||
mut commands: Commands,
|
||||
cameras_3d: Extract<Query<(Entity, &Camera, Option<&PreviousViewData>), With<Camera3d>>>,
|
||||
cameras_3d: Extract<Query<(&RenderEntity, &Camera, Option<&PreviousViewData>), With<Camera3d>>>,
|
||||
) {
|
||||
for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() {
|
||||
if camera.is_active {
|
||||
let entity = entity.id();
|
||||
let entity = commands.get_or_spawn(entity);
|
||||
|
||||
if let Some(previous_view_data) = maybe_previous_view_data {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use bevy_asset::UntypedAssetId;
|
||||
use bevy_color::ColorToComponents;
|
||||
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
entity::{EntityHashMap, EntityHashSet},
|
||||
prelude::*,
|
||||
system::lifetimeless::Read,
|
||||
};
|
||||
use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_render::world_sync::RenderEntity;
|
||||
use bevy_render::{
|
||||
diagnostic::RecordDiagnostics,
|
||||
mesh::RenderMesh,
|
||||
|
@ -192,6 +194,7 @@ pub fn extract_lights(
|
|||
global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
|
||||
point_lights: Extract<
|
||||
Query<(
|
||||
&RenderEntity,
|
||||
&PointLight,
|
||||
&CubemapVisibleEntities,
|
||||
&GlobalTransform,
|
||||
|
@ -202,6 +205,7 @@ pub fn extract_lights(
|
|||
>,
|
||||
spot_lights: Extract<
|
||||
Query<(
|
||||
&RenderEntity,
|
||||
&SpotLight,
|
||||
&VisibleMeshEntities,
|
||||
&GlobalTransform,
|
||||
|
@ -213,7 +217,7 @@ pub fn extract_lights(
|
|||
directional_lights: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&RenderEntity,
|
||||
&DirectionalLight,
|
||||
&CascadesVisibleEntities,
|
||||
&Cascades,
|
||||
|
@ -227,6 +231,7 @@ pub fn extract_lights(
|
|||
Without<SpotLight>,
|
||||
>,
|
||||
>,
|
||||
mapper: Extract<Query<&RenderEntity>>,
|
||||
mut previous_point_lights_len: Local<usize>,
|
||||
mut previous_spot_lights_len: Local<usize>,
|
||||
) {
|
||||
|
@ -250,6 +255,7 @@ pub fn extract_lights(
|
|||
let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
|
||||
for entity in global_point_lights.iter().copied() {
|
||||
let Ok((
|
||||
render_entity,
|
||||
point_light,
|
||||
cubemap_visible_entities,
|
||||
transform,
|
||||
|
@ -287,7 +293,7 @@ pub fn extract_lights(
|
|||
volumetric: volumetric_light.is_some(),
|
||||
};
|
||||
point_lights_values.push((
|
||||
entity,
|
||||
render_entity.id(),
|
||||
(
|
||||
extracted_point_light,
|
||||
render_cubemap_visible_entities,
|
||||
|
@ -301,6 +307,7 @@ 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((
|
||||
render_entity,
|
||||
spot_light,
|
||||
visible_entities,
|
||||
transform,
|
||||
|
@ -319,7 +326,7 @@ pub fn extract_lights(
|
|||
2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
|
||||
|
||||
spot_lights_values.push((
|
||||
entity,
|
||||
render_entity.id(),
|
||||
(
|
||||
ExtractedPointLight {
|
||||
color: spot_light.color.into(),
|
||||
|
@ -370,9 +377,33 @@ pub fn extract_lights(
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO: As above
|
||||
let render_visible_entities = visible_entities.clone();
|
||||
commands.get_or_spawn(entity).insert((
|
||||
// TODO: update in place instead of reinserting.
|
||||
let mut extracted_cascades = EntityHashMap::default();
|
||||
let mut extracted_frusta = EntityHashMap::default();
|
||||
let mut cascade_visible_entities = EntityHashMap::default();
|
||||
for (e, v) in cascades.cascades.iter() {
|
||||
if let Ok(entity) = mapper.get(*e) {
|
||||
extracted_cascades.insert(entity.id(), v.clone());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (e, v) in frusta.frusta.iter() {
|
||||
if let Ok(entity) = mapper.get(*e) {
|
||||
extracted_frusta.insert(entity.id(), v.clone());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (e, v) in visible_entities.entities.iter() {
|
||||
if let Ok(entity) = mapper.get(*e) {
|
||||
cascade_visible_entities.insert(entity.id(), v.clone());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
commands.get_or_spawn(entity.id()).insert((
|
||||
ExtractedDirectionalLight {
|
||||
color: directional_light.color.into(),
|
||||
illuminance: directional_light.illuminance,
|
||||
|
@ -385,15 +416,44 @@ pub fn extract_lights(
|
|||
shadow_normal_bias: directional_light.shadow_normal_bias
|
||||
* core::f32::consts::SQRT_2,
|
||||
cascade_shadow_config: cascade_config.clone(),
|
||||
cascades: cascades.cascades.clone(),
|
||||
frusta: frusta.frusta.clone(),
|
||||
cascades: extracted_cascades,
|
||||
frusta: extracted_frusta,
|
||||
render_layers: maybe_layers.unwrap_or_default().clone(),
|
||||
},
|
||||
render_visible_entities,
|
||||
CascadesVisibleEntities {
|
||||
entities: cascade_visible_entities,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Deref, DerefMut)]
|
||||
pub struct LightViewEntities(Vec<Entity>);
|
||||
|
||||
// TODO: using required component
|
||||
pub(crate) fn add_light_view_entities(
|
||||
trigger: Trigger<OnAdd, (ExtractedDirectionalLight, ExtractedPointLight)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
commands
|
||||
.get_entity(trigger.entity())
|
||||
.map(|v| v.insert(LightViewEntities::default()));
|
||||
}
|
||||
|
||||
pub(crate) fn remove_light_view_entities(
|
||||
trigger: Trigger<OnRemove, LightViewEntities>,
|
||||
query: Query<&LightViewEntities>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok(entities) = query.get(trigger.entity()) {
|
||||
for e in entities.0.iter().copied() {
|
||||
if let Some(v) = commands.get_entity(e) {
|
||||
v.despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CubeMapFace {
|
||||
pub(crate) target: Vec3,
|
||||
pub(crate) up: Vec3,
|
||||
|
@ -564,14 +624,17 @@ pub fn prepare_lights(
|
|||
point_light_shadow_map: Res<PointLightShadowMap>,
|
||||
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
|
||||
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
|
||||
mut max_directional_lights_warning_emitted: Local<bool>,
|
||||
mut max_cascades_per_light_warning_emitted: Local<bool>,
|
||||
(mut max_directional_lights_warning_emitted, mut max_cascades_per_light_warning_emitted): (
|
||||
Local<bool>,
|
||||
Local<bool>,
|
||||
),
|
||||
point_lights: Query<(
|
||||
Entity,
|
||||
&ExtractedPointLight,
|
||||
AnyOf<(&CubemapFrusta, &Frustum)>,
|
||||
)>,
|
||||
directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
|
||||
mut light_view_entities: Query<&mut LightViewEntities>,
|
||||
mut live_shadow_mapping_lights: Local<EntityHashSet>,
|
||||
) {
|
||||
let views_iter = views.iter();
|
||||
|
@ -862,8 +925,9 @@ pub fn prepare_lights(
|
|||
|
||||
live_shadow_mapping_lights.clear();
|
||||
|
||||
let mut dir_light_view_offset = 0;
|
||||
// set up light data for each view
|
||||
for (entity, extracted_view, clusters, maybe_layers) in &views {
|
||||
for (offset, (entity, extracted_view, clusters, maybe_layers)) in views.iter().enumerate() {
|
||||
let point_light_depth_texture = texture_cache.get(
|
||||
&render_device,
|
||||
TextureDescriptor {
|
||||
|
@ -949,15 +1013,25 @@ pub fn prepare_lights(
|
|||
// and ignore rotation because we want the shadow map projections to align with the axes
|
||||
let view_translation = GlobalTransform::from_translation(light.transform.translation());
|
||||
|
||||
let Ok(mut light_entities) = light_view_entities.get_mut(light_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// for each face of a cube and each view we spawn a light entity
|
||||
while light_entities.len() < 6 * (offset + 1) {
|
||||
light_entities.push(commands.spawn_empty().id());
|
||||
}
|
||||
|
||||
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
|
||||
core::f32::consts::FRAC_PI_2,
|
||||
1.0,
|
||||
light.shadow_map_near_z,
|
||||
);
|
||||
|
||||
for (face_index, (view_rotation, frustum)) in cube_face_rotations
|
||||
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
|
||||
.iter()
|
||||
.zip(&point_light_frusta.unwrap().frusta)
|
||||
.zip(light_entities.iter().skip(6 * offset).copied())
|
||||
.enumerate()
|
||||
{
|
||||
let depth_texture_view =
|
||||
|
@ -974,36 +1048,35 @@ pub fn prepare_lights(
|
|||
array_layer_count: Some(1u32),
|
||||
});
|
||||
|
||||
let view_light_entity = commands
|
||||
.spawn((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!(
|
||||
"shadow pass point light {} {}",
|
||||
light_index,
|
||||
face_index_to_name(face_index)
|
||||
),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
point_light_shadow_map.size as u32,
|
||||
point_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: view_translation * *view_rotation,
|
||||
clip_from_world: None,
|
||||
clip_from_view: cube_face_projection,
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
*frustum,
|
||||
LightEntity::Point {
|
||||
light_entity,
|
||||
face_index,
|
||||
},
|
||||
))
|
||||
.id();
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!(
|
||||
"shadow pass point light {} {}",
|
||||
light_index,
|
||||
face_index_to_name(face_index)
|
||||
),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
point_light_shadow_map.size as u32,
|
||||
point_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: view_translation * *view_rotation,
|
||||
clip_from_world: None,
|
||||
clip_from_view: cube_face_projection,
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
*frustum,
|
||||
LightEntity::Point {
|
||||
light_entity,
|
||||
face_index,
|
||||
},
|
||||
));
|
||||
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||
|
@ -1021,6 +1094,10 @@ pub fn prepare_lights(
|
|||
let spot_world_from_view = spot_light_world_from_view(&light.transform);
|
||||
let spot_world_from_view = spot_world_from_view.into();
|
||||
|
||||
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let angle = light.spot_light_angles.expect("lights should be sorted so that \
|
||||
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
|
||||
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
|
||||
|
@ -1039,29 +1116,33 @@ pub fn prepare_lights(
|
|||
array_layer_count: Some(1u32),
|
||||
});
|
||||
|
||||
let view_light_entity = commands
|
||||
.spawn((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!("shadow pass spot light {light_index}"),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
directional_light_shadow_map.size as u32,
|
||||
directional_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: spot_world_from_view,
|
||||
clip_from_view: spot_projection,
|
||||
clip_from_world: None,
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
*spot_light_frustum.unwrap(),
|
||||
LightEntity::Spot { light_entity },
|
||||
))
|
||||
.id();
|
||||
while light_view_entities.len() < offset + 1 {
|
||||
light_view_entities.push(commands.spawn_empty().id());
|
||||
}
|
||||
|
||||
let view_light_entity = light_view_entities[offset];
|
||||
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!("shadow pass spot light {light_index}"),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
directional_light_shadow_map.size as u32,
|
||||
directional_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: spot_world_from_view,
|
||||
clip_from_view: spot_projection,
|
||||
clip_from_world: None,
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
*spot_light_frustum.unwrap(),
|
||||
LightEntity::Spot { light_entity },
|
||||
));
|
||||
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
|
@ -1079,6 +1160,9 @@ pub fn prepare_lights(
|
|||
{
|
||||
let gpu_light = &mut gpu_lights.directional_lights[light_index];
|
||||
|
||||
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
|
||||
continue;
|
||||
};
|
||||
// Check if the light intersects with the view.
|
||||
if !view_layers.intersects(&light.render_layers) {
|
||||
gpu_light.skip = 1u32;
|
||||
|
@ -1102,9 +1186,22 @@ pub fn prepare_lights(
|
|||
.unwrap()
|
||||
.iter()
|
||||
.take(MAX_CASCADES_PER_LIGHT);
|
||||
for (cascade_index, ((cascade, frustum), bound)) in cascades
|
||||
|
||||
let iter = cascades
|
||||
.zip(frusta)
|
||||
.zip(&light.cascade_shadow_config.bounds)
|
||||
.zip(&light.cascade_shadow_config.bounds);
|
||||
|
||||
while light_view_entities.len() < dir_light_view_offset + iter.len() {
|
||||
light_view_entities.push(commands.spawn_empty().id());
|
||||
}
|
||||
|
||||
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in iter
|
||||
.zip(
|
||||
light_view_entities
|
||||
.iter()
|
||||
.skip(dir_light_view_offset)
|
||||
.copied(),
|
||||
)
|
||||
.enumerate()
|
||||
{
|
||||
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
|
||||
|
@ -1134,37 +1231,37 @@ pub fn prepare_lights(
|
|||
frustum.half_spaces[4] =
|
||||
HalfSpace::new(frustum.half_spaces[4].normal().extend(f32::INFINITY));
|
||||
|
||||
let view_light_entity = commands
|
||||
.spawn((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!(
|
||||
"shadow pass directional light {light_index} cascade {cascade_index}"),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
directional_light_shadow_map.size as u32,
|
||||
directional_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: GlobalTransform::from(cascade.world_from_cascade),
|
||||
clip_from_view: cascade.clip_from_cascade,
|
||||
clip_from_world: Some(cascade.clip_from_world),
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
frustum,
|
||||
LightEntity::Directional {
|
||||
light_entity,
|
||||
cascade_index,
|
||||
},
|
||||
))
|
||||
.id();
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
pass_name: format!(
|
||||
"shadow pass directional light {light_index} cascade {cascade_index}"
|
||||
),
|
||||
},
|
||||
ExtractedView {
|
||||
viewport: UVec4::new(
|
||||
0,
|
||||
0,
|
||||
directional_light_shadow_map.size as u32,
|
||||
directional_light_shadow_map.size as u32,
|
||||
),
|
||||
world_from_view: GlobalTransform::from(cascade.world_from_cascade),
|
||||
clip_from_view: cascade.clip_from_cascade,
|
||||
clip_from_world: Some(cascade.clip_from_world),
|
||||
hdr: false,
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
frustum,
|
||||
LightEntity::Directional {
|
||||
light_entity,
|
||||
cascade_index,
|
||||
},
|
||||
));
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||
live_shadow_mapping_lights.insert(view_light_entity);
|
||||
dir_light_view_offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ use bevy_render::{
|
|||
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
|
||||
texture::{CachedTexture, TextureCache},
|
||||
view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms},
|
||||
world_sync::RenderEntity,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{
|
||||
|
@ -488,7 +489,7 @@ fn extract_ssao_settings(
|
|||
mut commands: Commands,
|
||||
cameras: Extract<
|
||||
Query<
|
||||
(Entity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
|
||||
(&RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
|
||||
(With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
|
||||
>,
|
||||
>,
|
||||
|
@ -501,9 +502,10 @@ fn extract_ssao_settings(
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if camera.is_active {
|
||||
commands.get_or_spawn(entity).insert(ssao_settings.clone());
|
||||
commands
|
||||
.get_or_spawn(entity.id())
|
||||
.insert(ssao_settings.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ use bevy_render::{
|
|||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::{BevyDefault as _, GpuImage, Image},
|
||||
view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset},
|
||||
world_sync::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
|
@ -270,27 +271,27 @@ impl FromWorld for VolumetricFogPipeline {
|
|||
/// from the main world to the render world.
|
||||
pub fn extract_volumetric_fog(
|
||||
mut commands: Commands,
|
||||
view_targets: Extract<Query<(Entity, &VolumetricFog)>>,
|
||||
fog_volumes: Extract<Query<(Entity, &FogVolume, &GlobalTransform)>>,
|
||||
volumetric_lights: Extract<Query<(Entity, &VolumetricLight)>>,
|
||||
view_targets: Extract<Query<(&RenderEntity, &VolumetricFog)>>,
|
||||
fog_volumes: Extract<Query<(&RenderEntity, &FogVolume, &GlobalTransform)>>,
|
||||
volumetric_lights: Extract<Query<(&RenderEntity, &VolumetricLight)>>,
|
||||
) {
|
||||
if volumetric_lights.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (entity, volumetric_fog) in view_targets.iter() {
|
||||
commands.get_or_spawn(entity).insert(*volumetric_fog);
|
||||
commands.get_or_spawn(entity.id()).insert(*volumetric_fog);
|
||||
}
|
||||
|
||||
for (entity, fog_volume, fog_transform) in fog_volumes.iter() {
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.get_or_spawn(entity.id())
|
||||
.insert((*fog_volume).clone())
|
||||
.insert(*fog_transform);
|
||||
}
|
||||
|
||||
for (entity, volumetric_light) in volumetric_lights.iter() {
|
||||
commands.get_or_spawn(entity).insert(*volumetric_light);
|
||||
commands.get_or_spawn(entity.id()).insert(*volumetric_light);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
view::{
|
||||
ColorGrading, ExtractedView, ExtractedWindows, GpuCulling, RenderLayers, VisibleEntities,
|
||||
},
|
||||
world_sync::RenderEntity,
|
||||
Extract,
|
||||
};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
|
@ -935,7 +936,7 @@ pub fn extract_cameras(
|
|||
mut commands: Commands,
|
||||
query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&RenderEntity,
|
||||
&Camera,
|
||||
&CameraRenderGraph,
|
||||
&GlobalTransform,
|
||||
|
@ -954,7 +955,7 @@ pub fn extract_cameras(
|
|||
) {
|
||||
let primary_window = primary_window.iter().next();
|
||||
for (
|
||||
entity,
|
||||
render_entity,
|
||||
camera,
|
||||
camera_render_graph,
|
||||
transform,
|
||||
|
@ -968,11 +969,10 @@ pub fn extract_cameras(
|
|||
gpu_culling,
|
||||
) in query.iter()
|
||||
{
|
||||
let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
|
||||
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
}
|
||||
let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
|
||||
|
||||
if let (
|
||||
Some(URect {
|
||||
|
@ -990,7 +990,8 @@ pub fn extract_cameras(
|
|||
continue;
|
||||
}
|
||||
|
||||
let mut commands = commands.get_or_spawn(entity).insert((
|
||||
let mut commands = commands.entity(render_entity.id());
|
||||
commands = commands.insert((
|
||||
ExtractedCamera {
|
||||
target: camera.target.normalize(primary_window),
|
||||
viewport: camera.viewport.clone(),
|
||||
|
@ -1036,7 +1037,6 @@ pub fn extract_cameras(
|
|||
if let Some(perspective) = projection {
|
||||
commands = commands.insert(perspective.clone());
|
||||
}
|
||||
|
||||
if gpu_culling {
|
||||
if *gpu_preprocessing_support == GpuPreprocessingSupport::Culling {
|
||||
commands.insert(GpuCulling);
|
||||
|
@ -1046,7 +1046,7 @@ pub fn extract_cameras(
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
|||
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
view::ViewVisibility,
|
||||
world_sync::{RenderEntity, SyncToRenderWorld},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
|
@ -11,6 +12,7 @@ use bevy_ecs::{
|
|||
prelude::*,
|
||||
query::{QueryFilter, QueryItem, ReadOnlyQueryData},
|
||||
system::lifetimeless::Read,
|
||||
world::OnAdd,
|
||||
};
|
||||
use core::{marker::PhantomData, ops::Deref};
|
||||
|
||||
|
@ -155,10 +157,18 @@ fn prepare_uniform_components<C>(
|
|||
commands.insert_or_spawn_batch(entities);
|
||||
}
|
||||
|
||||
/// This plugin extracts the components into the "render world".
|
||||
/// This plugin extracts the components into the render world for synced entities.
|
||||
///
|
||||
/// Therefore it sets up the [`ExtractSchedule`] step
|
||||
/// for the specified [`ExtractComponent`].
|
||||
/// 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)>,
|
||||
|
@ -184,6 +194,10 @@ 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);
|
||||
});
|
||||
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>);
|
||||
|
@ -205,33 +219,33 @@ impl<T: Asset> ExtractComponent for Handle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`] type.
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`SyncToRenderWorld`].
|
||||
fn extract_components<C: ExtractComponent>(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
query: Extract<Query<(Entity, C::QueryData), C::QueryFilter>>,
|
||||
query: Extract<Query<(&RenderEntity, C::QueryData), C::QueryFilter>>,
|
||||
) {
|
||||
let mut values = Vec::with_capacity(*previous_len);
|
||||
for (entity, query_item) in &query {
|
||||
if let Some(component) = C::extract_component(query_item) {
|
||||
values.push((entity, component));
|
||||
values.push((entity.id(), component));
|
||||
}
|
||||
}
|
||||
*previous_len = values.len();
|
||||
commands.insert_or_spawn_batch(values);
|
||||
}
|
||||
|
||||
/// This system extracts all visible components of the corresponding [`ExtractComponent`] type.
|
||||
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`SyncToRenderWorld`].
|
||||
fn extract_visible_components<C: ExtractComponent>(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
query: Extract<Query<(Entity, &ViewVisibility, C::QueryData), C::QueryFilter>>,
|
||||
query: Extract<Query<(&RenderEntity, &ViewVisibility, C::QueryData), C::QueryFilter>>,
|
||||
) {
|
||||
let mut values = Vec::with_capacity(*previous_len);
|
||||
for (entity, view_visibility, query_item) in &query {
|
||||
if view_visibility.get() {
|
||||
if let Some(component) = C::extract_component(query_item) {
|
||||
values.push((entity, component));
|
||||
values.push((entity.id(), component));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ mod spatial_bundle;
|
|||
pub mod storage;
|
||||
pub mod texture;
|
||||
pub mod view;
|
||||
pub mod world_sync;
|
||||
|
||||
/// The render prelude.
|
||||
///
|
||||
|
@ -73,6 +74,9 @@ 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 crate::gpu_readback::GpuReadbackPlugin;
|
||||
use crate::{
|
||||
|
@ -364,6 +368,7 @@ impl Plugin for RenderPlugin {
|
|||
GlobalsPlugin,
|
||||
MorphPlugin,
|
||||
BatchingPlugin,
|
||||
WorldSyncPlugin,
|
||||
StoragePlugin,
|
||||
GpuReadbackPlugin::default(),
|
||||
));
|
||||
|
@ -377,7 +382,8 @@ impl Plugin for RenderPlugin {
|
|||
.register_type::<primitives::Aabb>()
|
||||
.register_type::<primitives::CascadesFrusta>()
|
||||
.register_type::<primitives::CubemapFrusta>()
|
||||
.register_type::<primitives::Frustum>();
|
||||
.register_type::<primitives::Frustum>()
|
||||
.register_type::<SyncToRenderWorld>();
|
||||
}
|
||||
|
||||
fn ready(&self, app: &App) -> bool {
|
||||
|
@ -484,35 +490,15 @@ unsafe fn initialize_render_app(app: &mut App) {
|
|||
render_system,
|
||||
)
|
||||
.in_set(RenderSet::Render),
|
||||
World::clear_entities.in_set(RenderSet::PostCleanup),
|
||||
despawn_temporary_render_entities.in_set(RenderSet::PostCleanup),
|
||||
),
|
||||
);
|
||||
|
||||
render_app.set_extract(|main_world, render_world| {
|
||||
#[cfg(feature = "trace")]
|
||||
let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered();
|
||||
{
|
||||
#[cfg(feature = "trace")]
|
||||
let _stage_span =
|
||||
bevy_utils::tracing::info_span!("reserve_and_flush")
|
||||
.entered();
|
||||
|
||||
// reserve all existing main world entities for use in render_app
|
||||
// they can only be spawned using `get_or_spawn()`
|
||||
let total_count = main_world.entities().total_count();
|
||||
|
||||
assert_eq!(
|
||||
render_world.entities().len(),
|
||||
0,
|
||||
"An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported",
|
||||
);
|
||||
|
||||
// SAFETY: This is safe given the clear_entities call in the past frame and the assert above
|
||||
unsafe {
|
||||
render_world
|
||||
.entities_mut()
|
||||
.flush_and_reserve_invalid_assuming_no_entities(total_count);
|
||||
}
|
||||
let _stage_span = bevy_utils::tracing::info_span!("entity_sync").entered();
|
||||
entity_sync_system(main_world, render_world);
|
||||
}
|
||||
|
||||
// run extract schedule
|
||||
|
|
|
@ -84,13 +84,15 @@ impl Drop for RenderAppChannels {
|
|||
/// A single frame of execution looks something like below
|
||||
///
|
||||
/// ```text
|
||||
/// |--------------------------------------------------------------------|
|
||||
/// | | RenderExtractApp schedule | winit events | main schedule |
|
||||
/// | extract |----------------------------------------------------------|
|
||||
/// | | extract commands | rendering schedule |
|
||||
/// |--------------------------------------------------------------------|
|
||||
/// |---------------------------------------------------------------------------|
|
||||
/// | | | RenderExtractApp schedule | winit events | main schedule |
|
||||
/// | sync | extract |----------------------------------------------------------|
|
||||
/// | | | extract commands | rendering schedule |
|
||||
/// |---------------------------------------------------------------------------|
|
||||
/// ```
|
||||
///
|
||||
/// - `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`].
|
||||
/// - `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
|
||||
|
@ -101,6 +103,8 @@ impl Drop for RenderAppChannels {
|
|||
/// - Next all the `winit events` are processed.
|
||||
/// - 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
|
||||
#[derive(Default)]
|
||||
pub struct PipelinedRenderingPlugin;
|
||||
|
||||
|
|
268
crates/bevy_render/src/world_sync.rs
Normal file
268
crates/bevy_render/src/world_sync.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use bevy_app::Plugin;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
observer::Trigger,
|
||||
query::With,
|
||||
reflect::ReflectComponent,
|
||||
system::{Local, Query, ResMut, Resource, SystemState},
|
||||
world::{Mut, OnAdd, OnRemove, World},
|
||||
};
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world.
|
||||
///
|
||||
/// 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
|
||||
/// 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.
|
||||
/// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains
|
||||
/// the corresponding main world entity of a render world entity.
|
||||
/// The entities can be accessed by calling `.id()` on either component.
|
||||
///
|
||||
/// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main
|
||||
/// to the render world for these entities.
|
||||
///
|
||||
/// ```text
|
||||
/// |--------------------------------------------------------------------|
|
||||
/// | | | Main world update |
|
||||
/// | sync | extract |---------------------------------------------------|
|
||||
/// | | | Render world update |
|
||||
/// |--------------------------------------------------------------------|
|
||||
/// ```
|
||||
///
|
||||
/// An example for synchronized main entities 1v1 and 18v1
|
||||
///
|
||||
/// ```text
|
||||
/// |---------------------------Main World------------------------------|
|
||||
/// | Entity | Component |
|
||||
/// |-------------------------------------------------------------------|
|
||||
/// | ID: 1v1 | PointLight | RenderEntity(ID: 3V1) | SyncToRenderWorld |
|
||||
/// | ID: 18v1 | PointLight | RenderEntity(ID: 5V1) | SyncToRenderWorld |
|
||||
/// |-------------------------------------------------------------------|
|
||||
///
|
||||
/// |----------Render World-----------|
|
||||
/// | Entity | Component |
|
||||
/// |---------------------------------|
|
||||
/// | ID: 3v1 | MainEntity(ID: 1V1) |
|
||||
/// | ID: 5v1 | MainEntity(ID: 18V1) |
|
||||
/// |---------------------------------|
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// Note that this effectively establishes a link between the main world entity and the render world entity.
|
||||
/// Not every entity needs to be synchronized, however; only entities with the [`SyncToRenderWorld`] component are synced.
|
||||
/// Adding [`SyncToRenderWorld`] to a main world component will establish such a link.
|
||||
/// Once a synchronized main entity is despawned, its corresponding render entity will be automatically
|
||||
/// despawned in the next `sync`.
|
||||
///
|
||||
/// The sync step does not copy any of component data between worlds, since its often not necessary to transfer over all
|
||||
/// the components of a main world entity.
|
||||
/// 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
|
||||
/// differently.
|
||||
///
|
||||
/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin
|
||||
#[derive(Default)]
|
||||
pub struct WorldSyncPlugin;
|
||||
|
||||
impl Plugin for WorldSyncPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.init_resource::<PendingSyncEntity>();
|
||||
app.observe(
|
||||
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(trigger.entity()));
|
||||
},
|
||||
);
|
||||
app.observe(
|
||||
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.entity()) {
|
||||
pending.push(EntityRecord::Removed(e.id()));
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
///
|
||||
/// NOTE: This component should persist throughout the entity's entire lifecycle.
|
||||
/// If this component is removed from its entity, the entity will be despawned.
|
||||
#[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
|
||||
#[derive(Component, Deref, Clone, Debug, Copy)]
|
||||
pub struct RenderEntity(Entity);
|
||||
impl RenderEntity {
|
||||
#[inline]
|
||||
pub fn id(&self) -> Entity {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Component added on the render world entities to keep track of the corresponding main world entity
|
||||
#[derive(Component, Deref, Clone, Debug)]
|
||||
pub struct MainEntity(Entity);
|
||||
impl MainEntity {
|
||||
#[inline]
|
||||
pub fn id(&self) -> Entity {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker component that indicates that its entity needs to be despawned at the end of the frame.
|
||||
#[derive(Component, Clone, Debug, Default, Reflect)]
|
||||
#[component(storage = "SparseSet")]
|
||||
pub struct TemporaryRenderEntity;
|
||||
|
||||
/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed.
|
||||
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),
|
||||
}
|
||||
|
||||
// Entity Record in MainWorld pending to Sync
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub(crate) struct PendingSyncEntity {
|
||||
records: Vec<EntityRecord>,
|
||||
}
|
||||
|
||||
pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) {
|
||||
main_world.resource_scope(|world, mut pending: Mut<PendingSyncEntity>| {
|
||||
// TODO : batching record
|
||||
for record in pending.drain(..) {
|
||||
match record {
|
||||
EntityRecord::Added(e) => {
|
||||
if let Some(mut entity) = world.get_entity_mut(e) {
|
||||
match entity.entry::<RenderEntity>() {
|
||||
bevy_ecs::world::Entry::Occupied(_) => {
|
||||
panic!("Attempting to synchronize an entity that has already been synchronized!");
|
||||
}
|
||||
bevy_ecs::world::Entry::Vacant(entry) => {
|
||||
let id = render_world.spawn(MainEntity(e)).id();
|
||||
|
||||
entry.insert(RenderEntity(id));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
EntityRecord::Removed(e) => {
|
||||
if let Some(ec) = render_world.get_entity_mut(e) {
|
||||
ec.despawn();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn despawn_temporary_render_entities(
|
||||
world: &mut World,
|
||||
state: &mut SystemState<Query<Entity, With<TemporaryRenderEntity>>>,
|
||||
mut local: Local<Vec<Entity>>,
|
||||
) {
|
||||
let query = state.get(world);
|
||||
|
||||
local.extend(query.iter());
|
||||
|
||||
// Ensure next frame allocation keeps order
|
||||
local.sort_unstable_by_key(|e| e.index());
|
||||
for e in local.drain(..).rev() {
|
||||
world.despawn(e);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
observer::Trigger,
|
||||
query::With,
|
||||
system::{Query, ResMut},
|
||||
world::{OnAdd, OnRemove, World},
|
||||
};
|
||||
|
||||
use super::{
|
||||
entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity,
|
||||
SyncToRenderWorld,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
struct RenderDataComponent;
|
||||
|
||||
#[test]
|
||||
fn world_sync() {
|
||||
let mut main_world = World::new();
|
||||
let mut render_world = World::new();
|
||||
main_world.init_resource::<PendingSyncEntity>();
|
||||
|
||||
main_world.observe(
|
||||
|trigger: Trigger<OnAdd, SyncToRenderWorld>, mut pending: ResMut<PendingSyncEntity>| {
|
||||
pending.push(EntityRecord::Added(trigger.entity()));
|
||||
},
|
||||
);
|
||||
main_world.observe(
|
||||
|trigger: Trigger<OnRemove, SyncToRenderWorld>,
|
||||
mut pending: ResMut<PendingSyncEntity>,
|
||||
query: Query<&RenderEntity>| {
|
||||
if let Ok(e) = query.get(trigger.entity()) {
|
||||
pending.push(EntityRecord::Removed(e.id()));
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// spawn some empty entities for test
|
||||
for _ in 0..99 {
|
||||
main_world.spawn_empty();
|
||||
}
|
||||
|
||||
// spawn
|
||||
let main_entity = main_world
|
||||
.spawn(RenderDataComponent)
|
||||
// indicates that its entity needs to be synchronized to the render world
|
||||
.insert(SyncToRenderWorld)
|
||||
.id();
|
||||
|
||||
entity_sync_system(&mut main_world, &mut render_world);
|
||||
|
||||
let mut q = render_world.query_filtered::<Entity, With<MainEntity>>();
|
||||
|
||||
// Only one synchronized entity
|
||||
assert!(q.iter(&render_world).count() == 1);
|
||||
|
||||
let render_entity = q.get_single(&render_world).unwrap();
|
||||
let render_entity_component = main_world.get::<RenderEntity>(main_entity).unwrap();
|
||||
|
||||
assert!(render_entity_component.id() == render_entity);
|
||||
|
||||
let main_entity_component = render_world
|
||||
.get::<MainEntity>(render_entity_component.id())
|
||||
.unwrap();
|
||||
|
||||
assert!(main_entity_component.id() == main_entity);
|
||||
|
||||
// despawn
|
||||
main_world.despawn(main_entity);
|
||||
|
||||
entity_sync_system(&mut main_world, &mut render_world);
|
||||
|
||||
// Only one synchronized entity
|
||||
assert!(q.iter(&render_world).count() == 0);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use bevy_ecs::bundle::Bundle;
|
|||
use bevy_render::{
|
||||
texture::Image,
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
world_sync::SyncToRenderWorld,
|
||||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
|
@ -30,4 +31,6 @@ pub struct SpriteBundle {
|
|||
pub inherited_visibility: InheritedVisibility,
|
||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
|
||||
pub view_visibility: ViewVisibility,
|
||||
/// Marker component that indicates that its entity needs to be synchronized to the render world
|
||||
pub sync: SyncToRenderWorld,
|
||||
}
|
||||
|
|
|
@ -221,8 +221,6 @@ pub struct RenderMesh2dInstances(EntityHashMap<RenderMesh2dInstance>);
|
|||
pub struct Mesh2d;
|
||||
|
||||
pub fn extract_mesh2d(
|
||||
mut commands: Commands,
|
||||
mut previous_len: Local<usize>,
|
||||
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
||||
query: Extract<
|
||||
Query<(
|
||||
|
@ -235,15 +233,11 @@ pub fn extract_mesh2d(
|
|||
>,
|
||||
) {
|
||||
render_mesh_instances.clear();
|
||||
let mut entities = Vec::with_capacity(*previous_len);
|
||||
|
||||
for (entity, view_visibility, transform, handle, no_automatic_batching) in &query {
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
// FIXME: Remove this - it is just a workaround to enable rendering to work as
|
||||
// render commands require an entity to exist at the moment.
|
||||
entities.push((entity, Mesh2d));
|
||||
render_mesh_instances.insert(
|
||||
entity,
|
||||
RenderMesh2dInstance {
|
||||
|
@ -257,8 +251,6 @@ pub fn extract_mesh2d(
|
|||
},
|
||||
);
|
||||
}
|
||||
*previous_len = entities.len();
|
||||
commands.insert_or_spawn_batch(entities);
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
|
|
|
@ -39,6 +39,7 @@ use bevy_render::{
|
|||
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
|
||||
ViewVisibility, VisibleEntities,
|
||||
},
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
|
@ -372,6 +373,7 @@ pub fn extract_sprites(
|
|||
sprite_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&RenderEntity,
|
||||
&ViewVisibility,
|
||||
&Sprite,
|
||||
&GlobalTransform,
|
||||
|
@ -382,7 +384,9 @@ pub fn extract_sprites(
|
|||
>,
|
||||
) {
|
||||
extracted_sprites.sprites.clear();
|
||||
for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() {
|
||||
for (original_entity, entity, view_visibility, sprite, transform, handle, sheet, slices) in
|
||||
sprite_query.iter()
|
||||
{
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
|
@ -390,8 +394,8 @@ pub fn extract_sprites(
|
|||
if let Some(slices) = slices {
|
||||
extracted_sprites.sprites.extend(
|
||||
slices
|
||||
.extract_sprites(transform, entity, sprite, handle)
|
||||
.map(|e| (commands.spawn_empty().id(), e)),
|
||||
.extract_sprites(transform, original_entity, sprite, handle)
|
||||
.map(|e| (commands.spawn(TemporaryRenderEntity).id(), e)),
|
||||
);
|
||||
} else {
|
||||
let atlas_rect =
|
||||
|
@ -410,7 +414,7 @@ pub fn extract_sprites(
|
|||
|
||||
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
|
||||
extracted_sprites.sprites.insert(
|
||||
entity,
|
||||
entity.id(),
|
||||
ExtractedSprite {
|
||||
color: sprite.color.into(),
|
||||
transform: *transform,
|
||||
|
@ -421,7 +425,7 @@ pub fn extract_sprites(
|
|||
flip_y: sprite.flip_y,
|
||||
image_handle_id: handle.id(),
|
||||
anchor: sprite.anchor.as_vec(),
|
||||
original_entity: None,
|
||||
original_entity: Some(original_entity),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ use bevy_render::{
|
|||
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
||||
texture::GpuImage,
|
||||
view::ViewVisibility,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
ExtractSchedule, Render,
|
||||
};
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
|
@ -188,12 +189,13 @@ pub struct ExtractedUiNodes {
|
|||
pub uinodes: EntityHashMap<ExtractedUiNode>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_uinode_background_colors(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&ViewVisibility,
|
||||
|
@ -202,22 +204,25 @@ pub fn extract_uinode_background_colors(
|
|||
&BackgroundColor,
|
||||
)>,
|
||||
>,
|
||||
mapping: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in
|
||||
&uinode_query
|
||||
{
|
||||
for (uinode, transform, view_visibility, clip, camera, background_color) in &uinode_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(&camera_entity) = mapping.get(camera_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible backgrounds
|
||||
if !view_visibility.get() || background_color.0.is_fully_transparent() {
|
||||
continue;
|
||||
}
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
|
@ -231,7 +236,7 @@ pub fn extract_uinode_background_colors(
|
|||
atlas_scaling: None,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
camera_entity: camera_entity.id(),
|
||||
border: uinode.border(),
|
||||
border_radius: uinode.border_radius(),
|
||||
node_type: NodeType::Rect,
|
||||
|
@ -260,6 +265,7 @@ pub fn extract_uinode_images(
|
|||
Without<ImageScaleMode>,
|
||||
>,
|
||||
>,
|
||||
mapping: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
for (uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
|
@ -267,6 +273,10 @@ pub fn extract_uinode_images(
|
|||
continue;
|
||||
};
|
||||
|
||||
let Ok(render_camera_entity) = mapping.get(camera_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible images
|
||||
if !view_visibility.get()
|
||||
|| image.color.is_fully_transparent()
|
||||
|
@ -303,7 +313,7 @@ pub fn extract_uinode_images(
|
|||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
|
@ -314,7 +324,7 @@ pub fn extract_uinode_images(
|
|||
atlas_scaling,
|
||||
flip_x: image.flip_x,
|
||||
flip_y: image.flip_y,
|
||||
camera_entity,
|
||||
camera_entity: render_camera_entity.id(),
|
||||
border: uinode.border,
|
||||
border_radius: uinode.border_radius,
|
||||
node_type: NodeType::Rect,
|
||||
|
@ -337,6 +347,7 @@ pub fn extract_uinode_borders(
|
|||
AnyOf<(&BorderColor, &Outline)>,
|
||||
)>,
|
||||
>,
|
||||
mapping: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
let image = AssetId::<Image>::default();
|
||||
|
||||
|
@ -356,6 +367,10 @@ pub fn extract_uinode_borders(
|
|||
continue;
|
||||
};
|
||||
|
||||
let Ok(&camera_entity) = mapping.get(camera_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip invisible borders
|
||||
if !view_visibility.get()
|
||||
|| maybe_border_color.is_some_and(|border_color| border_color.0.is_fully_transparent())
|
||||
|
@ -368,7 +383,7 @@ pub fn extract_uinode_borders(
|
|||
if !uinode.is_empty() && uinode.border() != BorderRect::ZERO {
|
||||
if let Some(border_color) = maybe_border_color {
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: global_transform.compute_matrix(),
|
||||
|
@ -382,7 +397,7 @@ pub fn extract_uinode_borders(
|
|||
clip: maybe_clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
camera_entity: camera_entity.id(),
|
||||
border_radius: uinode.border_radius(),
|
||||
border: uinode.border(),
|
||||
node_type: NodeType::Border,
|
||||
|
@ -408,7 +423,7 @@ pub fn extract_uinode_borders(
|
|||
clip: maybe_clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
camera_entity: camera_entity.id(),
|
||||
border: BorderRect::square(uinode.outline_width()),
|
||||
border_radius: uinode.outline_radius(),
|
||||
node_type: NodeType::Border,
|
||||
|
@ -438,7 +453,7 @@ pub fn extract_default_ui_camera_view(
|
|||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
query: Extract<
|
||||
Query<(Entity, &Camera, Option<&UiAntiAlias>), Or<(With<Camera2d>, With<Camera3d>)>>,
|
||||
Query<(&RenderEntity, &Camera, Option<&UiAntiAlias>), Or<(With<Camera2d>, With<Camera3d>)>>,
|
||||
>,
|
||||
mut live_entities: Local<EntityHashSet>,
|
||||
) {
|
||||
|
@ -463,6 +478,7 @@ pub fn extract_default_ui_camera_view(
|
|||
camera.physical_viewport_rect(),
|
||||
camera.physical_viewport_size(),
|
||||
) {
|
||||
let entity = entity.id();
|
||||
// use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
|
||||
let projection_matrix = Mat4::orthographic_rh(
|
||||
0.0,
|
||||
|
@ -473,23 +489,26 @@ pub fn extract_default_ui_camera_view(
|
|||
UI_CAMERA_FAR,
|
||||
);
|
||||
let default_camera_view = commands
|
||||
.spawn(ExtractedView {
|
||||
clip_from_view: projection_matrix,
|
||||
world_from_view: GlobalTransform::from_xyz(
|
||||
0.0,
|
||||
0.0,
|
||||
UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
|
||||
),
|
||||
clip_from_world: None,
|
||||
hdr: camera.hdr,
|
||||
viewport: UVec4::new(
|
||||
physical_origin.x,
|
||||
physical_origin.y,
|
||||
physical_size.x,
|
||||
physical_size.y,
|
||||
),
|
||||
color_grading: Default::default(),
|
||||
})
|
||||
.spawn((
|
||||
ExtractedView {
|
||||
clip_from_view: projection_matrix,
|
||||
world_from_view: GlobalTransform::from_xyz(
|
||||
0.0,
|
||||
0.0,
|
||||
UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
|
||||
),
|
||||
clip_from_world: None,
|
||||
hdr: camera.hdr,
|
||||
viewport: UVec4::new(
|
||||
physical_origin.x,
|
||||
physical_origin.y,
|
||||
physical_size.x,
|
||||
physical_size.y,
|
||||
),
|
||||
color_grading: Default::default(),
|
||||
},
|
||||
TemporaryRenderEntity,
|
||||
))
|
||||
.id();
|
||||
let entity_commands = commands
|
||||
.get_or_spawn(entity)
|
||||
|
@ -507,10 +526,11 @@ pub fn extract_default_ui_camera_view(
|
|||
}
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_uinode_text(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
camera_query: Extract<Query<&Camera>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
|
@ -525,12 +545,13 @@ pub fn extract_uinode_text(
|
|||
&TextLayoutInfo,
|
||||
)>,
|
||||
>,
|
||||
mapping: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
let default_ui_camera = default_ui_camera.get();
|
||||
for (uinode, global_transform, view_visibility, clip, camera, text, text_layout_info) in
|
||||
&uinode_query
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -542,11 +563,14 @@ pub fn extract_uinode_text(
|
|||
let scale_factor = camera_query
|
||||
.get(camera_entity)
|
||||
.ok()
|
||||
.and_then(|(_, c)| c.target_scaling_factor())
|
||||
.and_then(Camera::target_scaling_factor)
|
||||
.unwrap_or(1.0)
|
||||
* ui_scale.0;
|
||||
let inverse_scale_factor = scale_factor.recip();
|
||||
|
||||
let Ok(&camera_entity) = mapping.get(camera_entity) else {
|
||||
continue;
|
||||
};
|
||||
// Align the text to the nearest physical pixel:
|
||||
// * Translate by minus the text node's half-size
|
||||
// (The transform translates to the center of the node but the text coordinates are relative to the node's top left corner)
|
||||
|
@ -581,8 +605,9 @@ pub fn extract_uinode_text(
|
|||
let mut rect = atlas.textures[atlas_info.location.glyph_index].as_rect();
|
||||
rect.min *= inverse_scale_factor;
|
||||
rect.max *= inverse_scale_factor;
|
||||
let id = commands.spawn(TemporaryRenderEntity).id();
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn_empty().id(),
|
||||
id,
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform
|
||||
|
@ -594,7 +619,7 @@ pub fn extract_uinode_text(
|
|||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
camera_entity,
|
||||
camera_entity: camera_entity.id(),
|
||||
border: BorderRect::ZERO,
|
||||
border_radius: ResolvedBorderRadius::ZERO,
|
||||
node_type: NodeType::Rect,
|
||||
|
|
|
@ -20,6 +20,7 @@ use bevy_render::{
|
|||
renderer::{RenderDevice, RenderQueue},
|
||||
texture::BevyDefault,
|
||||
view::*,
|
||||
world_sync::{RenderEntity, TemporaryRenderEntity},
|
||||
Extract, ExtractSchedule, Render, RenderSet,
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
|
@ -354,13 +355,13 @@ impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> {
|
|||
}
|
||||
|
||||
pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||
mut commands: Commands,
|
||||
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
|
||||
materials: Extract<Res<Assets<M>>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&Handle<M>,
|
||||
|
@ -371,15 +372,20 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
Without<BackgroundColor>,
|
||||
>,
|
||||
>,
|
||||
render_entity_lookup: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
// If there is only one camera, we use it as default
|
||||
let default_single_camera = default_ui_camera.get();
|
||||
|
||||
for (entity, uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() {
|
||||
for (uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() {
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(&camera_entity) = render_entity_lookup.get(camera_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// skip invisible nodes
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
|
@ -398,7 +404,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
];
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiMaterialNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
|
@ -409,7 +415,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
|||
},
|
||||
border,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
camera_entity,
|
||||
camera_entity: camera_entity.id(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use bevy::{
|
|||
math::vec3,
|
||||
pbr::{FogVolume, VolumetricFog, VolumetricLight},
|
||||
prelude::*,
|
||||
render::world_sync::SyncToRenderWorld,
|
||||
};
|
||||
|
||||
/// Entry point.
|
||||
|
@ -43,7 +44,9 @@ 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
|
||||
|
|
Loading…
Reference in a new issue